From 88070d827d6edea3859e32db7a0aeafa23c00f1b Mon Sep 17 00:00:00 2001 From: Shubert Munthali Date: Mon, 4 Nov 2024 18:33:17 +0200 Subject: [PATCH] Fix KtLint issues --- .../main/java/dev/arkbuilders/arkmemo/App.kt | 5 +- .../dev/arkbuilders/arkmemo/graphics/Color.kt | 5 +- .../arkbuilders/arkmemo/graphics/ColorCode.kt | 2 +- .../dev/arkbuilders/arkmemo/graphics/SVG.kt | 97 +-- .../dev/arkbuilders/arkmemo/graphics/Size.kt | 5 +- .../arkmemo/media/ArkAudioRecorderImpl.kt | 110 +-- .../arkmemo/media/ArkMediaPlayer.kt | 11 +- .../arkmemo/media/ArkMediaPlayerImpl.kt | 145 ++-- .../arkbuilders/arkmemo/models/GraphicNote.kt | 4 +- .../arkmemo/models/SaveNoteResult.kt | 4 +- .../arkbuilders/arkmemo/models/TextNote.kt | 6 +- .../arkbuilders/arkmemo/models/VoiceNote.kt | 4 +- .../arkmemo/preferences/MemoPreferences.kt | 2 +- .../preferences/MemoPreferencesImpl.kt | 31 +- .../arkmemo/repo/NotesRepoHelper.kt | 150 ++-- .../arkmemo/repo/graphics/GraphicNotesRepo.kt | 169 +++-- .../arkmemo/repo/text/TextNotesRepo.kt | 185 ++--- .../arkmemo/repo/voices/VoiceNotesRepo.kt | 171 +++-- .../arkmemo/ui/activities/MainActivity.kt | 37 +- .../arkmemo/ui/adapters/BrushAdapter.kt | 67 +- .../arkmemo/ui/adapters/BrushAttribute.kt | 12 +- .../ui/adapters/EqualSpacingItemDecoration.kt | 112 +-- .../arkmemo/ui/adapters/NotesListAdapter.kt | 82 +- .../arkmemo/ui/adapters/TagAdapter.kt | 17 +- .../arkmemo/ui/dialogs/CommonActionDialog.kt | 32 +- .../arkmemo/ui/dialogs/DonateDialog.kt | 30 +- .../ui/fragments/ArkMediaPlayerFragment.kt | 52 +- .../ui/fragments/ArkRecorderFragment.kt | 200 +++-- .../ui/fragments/BaseEditNoteFragment.kt | 90 ++- .../arkmemo/ui/fragments/BaseFragment.kt | 4 +- .../ui/fragments/EditGraphicNotesFragment.kt | 180 +++-- .../ui/fragments/EditTextNotesFragment.kt | 133 ++-- .../arkmemo/ui/fragments/NotesFragment.kt | 232 +++--- .../arkmemo/ui/fragments/SettingsFragment.kt | 18 +- .../ui/viewmodels/ArkMediaPlayerViewModel.kt | 326 ++++---- .../ui/viewmodels/ArkRecorderViewModel.kt | 258 +++---- .../ui/viewmodels/GraphicNotesViewModel.kt | 93 +-- .../arkmemo/ui/viewmodels/NotesViewModel.kt | 244 +++--- .../arkmemo/ui/viewmodels/QRViewModel.kt | 71 +- .../ui/views/GraphicControlTextView.kt | 92 +-- .../arkmemo/ui/views/NotesCanvas.kt | 27 +- .../arkmemo/ui/views/SettingTextView.kt | 42 +- .../arkmemo/ui/views/SupportTextView.kt | 58 +- .../arkmemo/ui/views/SwitchButton.kt | 714 ++++++++++-------- .../arkbuilders/arkmemo/ui/views/WaveView.kt | 14 +- .../arkmemo/ui/views/WebLinkTextView.kt | 35 +- .../arkbuilders/arkmemo/utils/BundleExt.kt | 7 +- .../arkbuilders/arkmemo/utils/ContextExt.kt | 15 +- .../arkbuilders/arkmemo/utils/CoroutineExt.kt | 19 +- .../arkmemo/utils/DimensionUtils.kt | 4 +- .../arkmemo/utils/GraphicBrushExt.kt | 50 +- .../dev/arkbuilders/arkmemo/utils/NoteExt.kt | 16 +- .../arkbuilders/arkmemo/utils/Permission.kt | 7 +- .../arkbuilders/arkmemo/utils/StringExt.kt | 5 +- .../arkbuilders/arkmemo/utils/TextViewExt.kt | 18 +- .../dev/arkbuilders/arkmemo/utils/Utils.kt | 32 +- .../dev/arkbuilders/arkmemo/utils/ViewExt.kt | 34 +- .../arkmemo/utils/StringExtTest.kt | 7 +- 58 files changed, 2541 insertions(+), 2051 deletions(-) diff --git a/app/src/main/java/dev/arkbuilders/arkmemo/App.kt b/app/src/main/java/dev/arkbuilders/arkmemo/App.kt index 13be0d83..fdf2e0c6 100644 --- a/app/src/main/java/dev/arkbuilders/arkmemo/App.kt +++ b/app/src/main/java/dev/arkbuilders/arkmemo/App.kt @@ -2,14 +2,13 @@ package dev.arkbuilders.arkmemo import android.app.Application import dagger.hilt.android.HiltAndroidApp -import dev.arkbuilders.arklib.initArkLib import dev.arkbuilders.arkfilepicker.folders.FoldersRepo +import dev.arkbuilders.arklib.initArkLib import dev.arkbuilders.arkmemo.preferences.MemoPreferences import javax.inject.Inject @HiltAndroidApp -class App: Application() { - +class App : Application() { @Inject lateinit var memoPreferences: MemoPreferences diff --git a/app/src/main/java/dev/arkbuilders/arkmemo/graphics/Color.kt b/app/src/main/java/dev/arkbuilders/arkmemo/graphics/Color.kt index ea2aa063..911f38a0 100644 --- a/app/src/main/java/dev/arkbuilders/arkmemo/graphics/Color.kt +++ b/app/src/main/java/dev/arkbuilders/arkmemo/graphics/Color.kt @@ -1,7 +1,6 @@ package dev.arkbuilders.arkmemo.graphics enum class Color(val code: Int, val value: String) { - BLACK(ColorCode.black, "black"), GRAY(ColorCode.gray, "gray"), @@ -16,5 +15,5 @@ enum class Color(val code: Int, val value: String) { PURPLE(ColorCode.purple, "purple"), - WHITE(ColorCode.white, "white") -} \ No newline at end of file + WHITE(ColorCode.white, "white"), +} diff --git a/app/src/main/java/dev/arkbuilders/arkmemo/graphics/ColorCode.kt b/app/src/main/java/dev/arkbuilders/arkmemo/graphics/ColorCode.kt index c543bc8e..5aff56ed 100644 --- a/app/src/main/java/dev/arkbuilders/arkmemo/graphics/ColorCode.kt +++ b/app/src/main/java/dev/arkbuilders/arkmemo/graphics/ColorCode.kt @@ -10,4 +10,4 @@ internal object ColorCode { val purple by lazy { android.graphics.Color.parseColor("#7A5AF8") } val white by lazy { android.graphics.Color.parseColor("#FFFFFF") } val brown by lazy { android.graphics.Color.parseColor("#B54708") } -} \ No newline at end of file +} diff --git a/app/src/main/java/dev/arkbuilders/arkmemo/graphics/SVG.kt b/app/src/main/java/dev/arkbuilders/arkmemo/graphics/SVG.kt index feacb34f..859d06cd 100644 --- a/app/src/main/java/dev/arkbuilders/arkmemo/graphics/SVG.kt +++ b/app/src/main/java/dev/arkbuilders/arkmemo/graphics/SVG.kt @@ -2,7 +2,6 @@ package dev.arkbuilders.arkmemo.graphics import android.graphics.Paint import android.util.Log -import android.graphics.Path as AndroidDrawPath import android.util.Xml import dev.arkbuilders.arkmemo.ui.viewmodels.DrawPath import dev.arkbuilders.arkmemo.utils.getColorCode @@ -11,6 +10,7 @@ import org.xmlpull.v1.XmlPullParser import java.nio.file.Path import kotlin.io.path.reader import kotlin.io.path.writer +import android.graphics.Path as AndroidDrawPath class SVG { private var strokeColor = Color.BLACK.value @@ -21,14 +21,15 @@ class SVG { private val paths = ArrayDeque() private val paint - get() = Paint().also { - it.color = strokeColor.getColorCode() - it.style = Paint.Style.STROKE - it.strokeWidth = strokeSize.getStrokeSize() - it.strokeCap = Paint.Cap.ROUND - it.strokeJoin = Paint.Join.ROUND - it.isAntiAlias = true - } + get() = + Paint().also { + it.color = strokeColor.getColorCode() + it.style = Paint.Style.STROKE + it.strokeWidth = strokeSize.getStrokeSize() + it.strokeCap = Paint.Cap.ROUND + it.strokeJoin = Paint.Join.ROUND + it.isAntiAlias = true + } fun addCommand(command: SVGCommand) { commands.addLast(command) @@ -61,13 +62,14 @@ class SVG { fun getPaths(): Collection = paths - fun copy(): SVG = SVG().apply { - strokeColor = this@SVG.strokeColor - fill = this@SVG.fill - viewBox = this@SVG.viewBox - commands.addAll(this@SVG.commands) - paths.addAll(this@SVG.paths) - } + fun copy(): SVG = + SVG().apply { + strokeColor = this@SVG.strokeColor + fill = this@SVG.fill + viewBox = this@SVG.viewBox + commands.addAll(this@SVG.commands) + paths.addAll(this@SVG.paths) + } private fun createCanvasPaths() { if (commands.isNotEmpty()) { @@ -90,10 +92,15 @@ class SVG { path.lineTo(command.x, command.y) } } - paths.addLast(DrawPath(path, paint.apply { - color = strokeColor.getColorCode() - strokeWidth = strokeSize.getStrokeSize() - })) + paths.addLast( + DrawPath( + path, + paint.apply { + color = strokeColor.getColorCode() + strokeWidth = strokeSize.getStrokeSize() + }, + ), + ) } } } @@ -116,9 +123,10 @@ class SVG { XmlPullParser.START_TAG -> { when (tag) { SVG_TAG -> { - viewBox = ViewBox.fromString( - getAttributeValue("", Attributes.VIEW_BOX) - ) + viewBox = + ViewBox.fromString( + getAttributeValue("", Attributes.VIEW_BOX), + ) } PATH_TAG -> { pathCount += 1 @@ -149,10 +157,12 @@ class SVG { if (commandElements.size > 4) { strokeSize = commandElements[4].toInt() } - commands.addLast(SVGCommand.MoveTo.fromString(command).apply { - paintColor = strokeColor - brushSizeId = strokeSize - }) + commands.addLast( + SVGCommand.MoveTo.fromString(command).apply { + paintColor = strokeColor + brushSizeId = strokeSize + }, + ) } SVGCommand.AbsLineTo.CODE -> { if (commandElements.size > 3) { @@ -161,10 +171,12 @@ class SVG { if (commandElements.size > 4) { strokeSize = commandElements[4].toInt() } - commands.addLast(SVGCommand.MoveTo.fromString(command).apply { - paintColor = strokeColor - brushSizeId = strokeSize - }) + commands.addLast( + SVGCommand.MoveTo.fromString(command).apply { + paintColor = strokeColor + brushSizeId = strokeSize + }, + ) } SVGCommand.AbsQuadTo.CODE -> { if (commandElements.size > 5) { @@ -173,10 +185,12 @@ class SVG { if (commandElements.size > 6) { strokeSize = commandElements[6].toInt() } - commands.addLast(SVGCommand.AbsQuadTo.fromString(command).apply { - paintColor = strokeColor - brushSizeId = strokeSize - }) + commands.addLast( + SVGCommand.AbsQuadTo.fromString(command).apply { + paintColor = strokeColor + brushSizeId = strokeSize + }, + ) } else -> {} } @@ -208,7 +222,7 @@ data class ViewBox( val x: Float = 0f, val y: Float = 0f, val width: Float = 100f, - val height: Float = 100f + val height: Float = 100f, ) { override fun toString(): String = "$x $y $width $height" @@ -219,20 +233,19 @@ data class ViewBox( viewBox[0].toFloat(), viewBox[1].toFloat(), viewBox[2].toFloat(), - viewBox[3].toFloat() + viewBox[3].toFloat(), ) } } } sealed class SVGCommand { - var paintColor = Color.BLACK.value var brushSizeId = Size.TINY.id class MoveTo( val x: Float, - val y: Float + val y: Float, ) : SVGCommand() { override fun toString(): String = "$CODE $x $y $paintColor $brushSizeId" @@ -255,7 +268,7 @@ sealed class SVGCommand { class AbsLineTo( val x: Float, - val y: Float + val y: Float, ) : SVGCommand() { override fun toString(): String = "$CODE $x $y $paintColor $brushSizeId" @@ -280,7 +293,7 @@ sealed class SVGCommand { val x1: Float, val y1: Float, val x2: Float, - val y2: Float + val y2: Float, ) : SVGCommand() { override fun toString(): String = "$CODE $x1 $y1 $x2 $y2 $paintColor $brushSizeId" @@ -307,4 +320,4 @@ sealed class SVGCommand { private const val COMMA = "," private const val XML_NS_URI = "http://www.w3.org/2000/svg" private const val SVG_TAG = "svg" -private const val PATH_TAG = "path" \ No newline at end of file +private const val PATH_TAG = "path" diff --git a/app/src/main/java/dev/arkbuilders/arkmemo/graphics/Size.kt b/app/src/main/java/dev/arkbuilders/arkmemo/graphics/Size.kt index a3973536..e02026a1 100644 --- a/app/src/main/java/dev/arkbuilders/arkmemo/graphics/Size.kt +++ b/app/src/main/java/dev/arkbuilders/arkmemo/graphics/Size.kt @@ -1,7 +1,6 @@ package dev.arkbuilders.arkmemo.graphics enum class Size(val id: Int, val value: Float) { - TINY(0, 5f), SMALL(1, 10f), @@ -10,5 +9,5 @@ enum class Size(val id: Int, val value: Float) { LARGE(3, 20f), - HUGE(4, 25f) -} \ No newline at end of file + HUGE(4, 25f), +} diff --git a/app/src/main/java/dev/arkbuilders/arkmemo/media/ArkAudioRecorderImpl.kt b/app/src/main/java/dev/arkbuilders/arkmemo/media/ArkAudioRecorderImpl.kt index bd514b9d..32681424 100644 --- a/app/src/main/java/dev/arkbuilders/arkmemo/media/ArkAudioRecorderImpl.kt +++ b/app/src/main/java/dev/arkbuilders/arkmemo/media/ArkAudioRecorderImpl.kt @@ -9,73 +9,77 @@ import java.nio.file.Path import javax.inject.Inject import kotlin.io.path.createTempFile -class ArkAudioRecorderImpl @Inject constructor( - @ApplicationContext private val context: Context -): ArkAudioRecorder { +class ArkAudioRecorderImpl + @Inject + constructor( + @ApplicationContext private val context: Context, + ) : ArkAudioRecorder { + private val tag = "ArkAudioRecorderImpl" - private val TAG = "ArkAudioRecorderImpl" + private var recorder: MediaRecorder? = null + private val tempFile = createTempFile().toFile() - 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.MIC) + setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP) + setOutputFile(tempFile) + setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB) + prepare() + } + } - override fun init() { - recorder = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) - MediaRecorder(context) - else MediaRecorder() - recorder?.apply { - setAudioSource(MediaRecorder.AudioSource.MIC) - setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP) - setOutputFile(tempFile) - setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB) - prepare() + override fun start() { + recorder?.start() } - } - override fun start() { - recorder?.start() - } + override fun pause() { + recorder?.pause() + } - override fun pause() { - recorder?.pause() - } + override fun resume() { + recorder?.resume() + } - override fun resume() { - recorder?.resume() - } + override fun reset() { + recorder?.reset() + } - override fun reset() { - recorder?.reset() - } + override fun stop() { + recorder?.let { + try { + it.stop() + } catch (e: RuntimeException) { + Log.e(tag, "stop exception: " + e.message) + } - override fun stop() { - recorder?.let { - try { - it.stop() - } catch (e: RuntimeException) { - Log.e(TAG, "stop exception: " + e.message) + it.release() } - - it.release() + recorder = null } - recorder = null - } - override fun maxAmplitude(): Int { - return try { - recorder?.maxAmplitude ?: 0 - } catch (e: Exception) { - 0 + override fun maxAmplitude(): Int { + return try { + recorder?.maxAmplitude ?: 0 + } catch (e: Exception) { + 0 + } } - } - override fun getRecording(): Path = tempFile.toPath() + override fun getRecording(): Path = tempFile.toPath() - override suspend fun deleteTempFile(): Boolean { - return try { - tempFile.delete() - } catch (e: Exception) { - Log.e(TAG, "deleteTempFile exception: " + e.message) - false + override suspend fun deleteTempFile(): Boolean { + return try { + tempFile.delete() + } catch (e: Exception) { + Log.e(tag, "deleteTempFile exception: " + e.message) + false + } } } -} \ No newline at end of file diff --git a/app/src/main/java/dev/arkbuilders/arkmemo/media/ArkMediaPlayer.kt b/app/src/main/java/dev/arkbuilders/arkmemo/media/ArkMediaPlayer.kt index 835ac1da..29ea2a10 100644 --- a/app/src/main/java/dev/arkbuilders/arkmemo/media/ArkMediaPlayer.kt +++ b/app/src/main/java/dev/arkbuilders/arkmemo/media/ArkMediaPlayer.kt @@ -2,12 +2,15 @@ package dev.arkbuilders.arkmemo.media import android.media.MediaPlayer -interface ArkMediaPlayer: +interface ArkMediaPlayer : MediaPlayer.OnCompletionListener, MediaPlayer.OnPreparedListener, MediaPlayer.OnSeekCompleteListener { - - fun init(path: String, onCompletion: () -> Unit, onPrepared: () -> Unit) + fun init( + path: String, + onCompletion: () -> Unit, + onPrepared: () -> Unit, + ) fun play() @@ -30,4 +33,4 @@ interface ArkMediaPlayer: fun getMaxAmplitude(): Int fun setMaxAmplitude(maxAmplitude: Int) -} \ No newline at end of file +} diff --git a/app/src/main/java/dev/arkbuilders/arkmemo/media/ArkMediaPlayerImpl.kt b/app/src/main/java/dev/arkbuilders/arkmemo/media/ArkMediaPlayerImpl.kt index de12ba1a..397ec4bb 100644 --- a/app/src/main/java/dev/arkbuilders/arkmemo/media/ArkMediaPlayerImpl.kt +++ b/app/src/main/java/dev/arkbuilders/arkmemo/media/ArkMediaPlayerImpl.kt @@ -5,93 +5,98 @@ import android.media.MediaPlayer import android.util.Log import javax.inject.Inject -class ArkMediaPlayerImpl @Inject constructor(): ArkMediaPlayer { - - private var player: MediaPlayer? = null - - private var onCompletionHandler: () -> Unit = {} - private var onPreparedHandler: () -> Unit = {} +class ArkMediaPlayerImpl + @Inject + constructor() : ArkMediaPlayer { + private var player: MediaPlayer? = null + + private var onCompletionHandler: () -> Unit = {} + private var onPreparedHandler: () -> Unit = {} + + private var maxAmplitude = 0 + + override fun init( + path: String, + onCompletion: () -> Unit, + onPrepared: () -> Unit, + ) { + if (player?.isPlaying == true) { + player?.stop() + onCompletionHandler() + } - private var maxAmplitude = 0 + onCompletionHandler = onCompletion + onPreparedHandler = onPrepared + + player = + MediaPlayer().apply { + setOnCompletionListener(this@ArkMediaPlayerImpl) + setOnPreparedListener(this@ArkMediaPlayerImpl) + + try { + setAudioAttributes( + AudioAttributes.Builder() + .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC) + .setUsage(AudioAttributes.USAGE_MEDIA) + .build(), + ) + setDataSource(path) + prepare() + } catch (e: Exception) { + Log.e("ArkMediaPlayerImpl", "init exception: ${e.message}") + } + } + } - override fun init(path: String, onCompletion: () -> Unit, onPrepared: () -> Unit) { - if (player?.isPlaying == true) { - player?.stop() - onCompletionHandler() + override fun play() { + player?.start() } - onCompletionHandler = onCompletion - onPreparedHandler = onPrepared - - player = MediaPlayer().apply { - setOnCompletionListener(this@ArkMediaPlayerImpl) - setOnPreparedListener(this@ArkMediaPlayerImpl) - - try { - setAudioAttributes( - AudioAttributes.Builder() - .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC) - .setUsage(AudioAttributes.USAGE_MEDIA) - .build() - ) - setDataSource(path) - prepare() - } catch (e: Exception) { - Log.e("ArkMediaPlayerImpl", "init exception: ${e.message}" ) + override fun stop() { + player?.let { + it.stop() + it.release() } - + player = null } - } - override fun play() { - player?.start() - } - - override fun stop() { - player?.let { - it.stop() - it.release() + override fun pause() { + player?.pause() } - player = null - } - override fun pause() { - player?.pause() - } - - override fun seekTo(position: Int) { - player?.seekTo(position) - } + override fun seekTo(position: Int) { + player?.seekTo(position) + } - override fun duration(): Int = player?.duration ?: 0 + override fun duration(): Int = player?.duration ?: 0 - override fun currentPosition(): Int = player?.currentPosition ?: 0 + override fun currentPosition(): Int = player?.currentPosition ?: 0 - override fun isPlaying(): Boolean = player?.isPlaying ?: false + override fun isPlaying(): Boolean = player?.isPlaying ?: false - override fun onCompletion(player: MediaPlayer?) { - onCompletionHandler() - } + override fun onCompletion(player: MediaPlayer?) { + onCompletionHandler() + } - override fun onPrepared(player: MediaPlayer?) { - onPreparedHandler() - } + override fun onPrepared(player: MediaPlayer?) { + onPreparedHandler() + } - override fun onSeekComplete(player: MediaPlayer?) {} + override fun onSeekComplete(player: MediaPlayer?) {} - override fun isInitialized(): Boolean { - return player != null - } + override fun isInitialized(): Boolean { + return player != null + } - override fun getAudioSessionId(): Int { - return player?.audioSessionId ?: -1 - } + override fun getAudioSessionId(): Int { + return player?.audioSessionId ?: -1 + } - override fun getMaxAmplitude(): Int { - return maxAmplitude - } + override fun getMaxAmplitude(): Int { + return maxAmplitude + } - override fun setMaxAmplitude(maxAmplitude: Int) { - this.maxAmplitude = maxAmplitude + override fun setMaxAmplitude(maxAmplitude: Int) { + this.maxAmplitude = maxAmplitude + } } -} \ No newline at end of file diff --git a/app/src/main/java/dev/arkbuilders/arkmemo/models/GraphicNote.kt b/app/src/main/java/dev/arkbuilders/arkmemo/models/GraphicNote.kt index 5ab1dc85..bb571f69 100644 --- a/app/src/main/java/dev/arkbuilders/arkmemo/models/GraphicNote.kt +++ b/app/src/main/java/dev/arkbuilders/arkmemo/models/GraphicNote.kt @@ -14,5 +14,5 @@ data class GraphicNote( val svg: SVG? = null, @IgnoredOnParcel override var resource: Resource? = null, - override var pendingForDelete: Boolean = false -) : Note, Parcelable \ No newline at end of file + override var pendingForDelete: Boolean = false, +) : Note, Parcelable diff --git a/app/src/main/java/dev/arkbuilders/arkmemo/models/SaveNoteResult.kt b/app/src/main/java/dev/arkbuilders/arkmemo/models/SaveNoteResult.kt index e7313a74..23963079 100644 --- a/app/src/main/java/dev/arkbuilders/arkmemo/models/SaveNoteResult.kt +++ b/app/src/main/java/dev/arkbuilders/arkmemo/models/SaveNoteResult.kt @@ -3,5 +3,5 @@ package dev.arkbuilders.arkmemo.models enum class SaveNoteResult { SUCCESS_NEW, SUCCESS_UPDATED, - ERROR_EXISTING -} \ No newline at end of file + ERROR_EXISTING, +} diff --git a/app/src/main/java/dev/arkbuilders/arkmemo/models/TextNote.kt b/app/src/main/java/dev/arkbuilders/arkmemo/models/TextNote.kt index 715e946d..c8af2df8 100644 --- a/app/src/main/java/dev/arkbuilders/arkmemo/models/TextNote.kt +++ b/app/src/main/java/dev/arkbuilders/arkmemo/models/TextNote.kt @@ -6,11 +6,11 @@ import kotlinx.parcelize.IgnoredOnParcel import kotlinx.parcelize.Parcelize @Parcelize -data class TextNote ( +data class TextNote( override val title: String = "", override val description: String = "", val text: String = "", @IgnoredOnParcel override var resource: Resource? = null, - override var pendingForDelete: Boolean = false -): Note, Parcelable + override var pendingForDelete: Boolean = false, +) : Note, Parcelable diff --git a/app/src/main/java/dev/arkbuilders/arkmemo/models/VoiceNote.kt b/app/src/main/java/dev/arkbuilders/arkmemo/models/VoiceNote.kt index df9c7bc6..b3f08a80 100644 --- a/app/src/main/java/dev/arkbuilders/arkmemo/models/VoiceNote.kt +++ b/app/src/main/java/dev/arkbuilders/arkmemo/models/VoiceNote.kt @@ -21,5 +21,5 @@ class VoiceNote( var pendingForPlaybackReset: Boolean = false, var waitToBeResumed: Boolean = false, var currentPlayingPos: Int = 0, - var currentMaxAmplitude: Int = 0 -): Note, Parcelable \ No newline at end of file + var currentMaxAmplitude: Int = 0, +) : Note, Parcelable diff --git a/app/src/main/java/dev/arkbuilders/arkmemo/preferences/MemoPreferences.kt b/app/src/main/java/dev/arkbuilders/arkmemo/preferences/MemoPreferences.kt index b0716ad8..36b4dd80 100644 --- a/app/src/main/java/dev/arkbuilders/arkmemo/preferences/MemoPreferences.kt +++ b/app/src/main/java/dev/arkbuilders/arkmemo/preferences/MemoPreferences.kt @@ -12,4 +12,4 @@ interface MemoPreferences { fun storeCrashReportEnabled(enabled: Boolean) fun getCrashReportEnabled(): Boolean -} \ No newline at end of file +} diff --git a/app/src/main/java/dev/arkbuilders/arkmemo/preferences/MemoPreferencesImpl.kt b/app/src/main/java/dev/arkbuilders/arkmemo/preferences/MemoPreferencesImpl.kt index df2da511..253dd2be 100644 --- a/app/src/main/java/dev/arkbuilders/arkmemo/preferences/MemoPreferencesImpl.kt +++ b/app/src/main/java/dev/arkbuilders/arkmemo/preferences/MemoPreferencesImpl.kt @@ -8,26 +8,29 @@ import java.nio.file.Path import javax.inject.Inject import kotlin.io.path.Path - private const val NAME = "memo_prefs" private const val CURRENT_NOTES_PATH = "current_notes_path" -class MemoPreferencesImpl @Inject constructor(@ApplicationContext context: Context) : +class MemoPreferencesImpl + @Inject + constructor( + @ApplicationContext context: Context, + ) : MemoPreferences { - private val sharedPreferences = context.getSharedPreferences(NAME, MODE_PRIVATE) - private val prefEditor = sharedPreferences.edit() + private val sharedPreferences = context.getSharedPreferences(NAME, MODE_PRIVATE) + private val prefEditor = sharedPreferences.edit() - override fun storePath(path: String) { - prefEditor.putString(CURRENT_NOTES_PATH, path).apply() - } + override fun storePath(path: String) { + prefEditor.putString(CURRENT_NOTES_PATH, path).apply() + } - override fun getPath(): String = sharedPreferences.getString(CURRENT_NOTES_PATH, "") ?: "" + override fun getPath(): String = sharedPreferences.getString(CURRENT_NOTES_PATH, "") ?: "" - override fun getNotesStorage(): Path = Path(getPath()) + override fun getNotesStorage(): Path = Path(getPath()) - override fun storeCrashReportEnabled(enabled: Boolean) { - prefEditor.putBoolean(CRASH_REPORT_ENABLE, enabled).apply() - } + override fun storeCrashReportEnabled(enabled: Boolean) { + prefEditor.putBoolean(CRASH_REPORT_ENABLE, enabled).apply() + } - override fun getCrashReportEnabled(): Boolean = sharedPreferences.getBoolean(CRASH_REPORT_ENABLE, true) -} \ No newline at end of file + override fun getCrashReportEnabled(): Boolean = sharedPreferences.getBoolean(CRASH_REPORT_ENABLE, true) + } diff --git a/app/src/main/java/dev/arkbuilders/arkmemo/repo/NotesRepoHelper.kt b/app/src/main/java/dev/arkbuilders/arkmemo/repo/NotesRepoHelper.kt index 924d6214..85b7ea0d 100644 --- a/app/src/main/java/dev/arkbuilders/arkmemo/repo/NotesRepoHelper.kt +++ b/app/src/main/java/dev/arkbuilders/arkmemo/repo/NotesRepoHelper.kt @@ -21,86 +21,98 @@ import kotlin.io.path.getLastModifiedTime import kotlin.io.path.moveTo import kotlin.io.path.name -class NotesRepoHelper @Inject constructor( - private val memoPreferences: MemoPreferences, - private val propertiesStorageRepo: PropertiesStorageRepo -) { - - private lateinit var root: Path - private lateinit var propertiesStorage: PropertiesStorage +class NotesRepoHelper + @Inject + constructor( + private val memoPreferences: MemoPreferences, + private val propertiesStorageRepo: PropertiesStorageRepo, + ) { + private lateinit var root: Path + private lateinit var propertiesStorage: PropertiesStorage - suspend fun init() { - root = memoPreferences.getNotesStorage() - propertiesStorage = propertiesStorageRepo.provide(RootIndex.provide(root)) - } + suspend fun init() { + root = memoPreferences.getNotesStorage() + propertiesStorage = propertiesStorageRepo.provide(RootIndex.provide(root)) + } - suspend fun persistNoteProperties(resourceId: ResourceId, - noteTitle: String, - description: String? = null): Boolean { - with(propertiesStorage) { - val properties = Properties( - setOf(noteTitle), - mutableSetOf().apply { - description?.let { this.add(description) } - }) - val currentProperties = getProperties(resourceId) - if (currentProperties.isEqual(properties)) { - return false - } else { - setProperties(resourceId, properties) - persist() - return true + suspend fun persistNoteProperties( + resourceId: ResourceId, + noteTitle: String, + description: String? = null, + ): Boolean { + with(propertiesStorage) { + val properties = + Properties( + setOf(noteTitle), + mutableSetOf().apply { + description?.let { this.add(description) } + }, + ) + val currentProperties = getProperties(resourceId) + if (currentProperties.isEqual(properties)) { + return false + } else { + setProperties(resourceId, properties) + persist() + return true + } } } - } - - fun renameResource( - note: Note, - tempPath: Path, - resourcePath: Path, - resourceId: ResourceId, - ) { - tempPath.moveTo(resourcePath) - note.resource = Resource( - id = resourceId, - name = resourcePath.fileName.name, - extension = resourcePath.extension, - modified = resourcePath.getLastModifiedTime() - ) - Log.d("notes-repo", "resource renamed to ${resourcePath.name} successfully") - } - fun readProperties(id: ResourceId, defaultTitle: String): UserNoteProperties { - val title = propertiesStorage.getProperties(id).titles.let { - if (it.isNotEmpty()) it.elementAt(0) else defaultTitle + fun renameResource( + note: Note, + tempPath: Path, + resourcePath: Path, + resourceId: ResourceId, + ) { + tempPath.moveTo(resourcePath) + note.resource = + Resource( + id = resourceId, + name = resourcePath.fileName.name, + extension = resourcePath.extension, + modified = resourcePath.getLastModifiedTime(), + ) + Log.d("notes-repo", "resource renamed to ${resourcePath.name} successfully") } - val description = propertiesStorage.getProperties(id).descriptions.let { - if (it.isNotEmpty()) it.elementAt(0) else "" + + fun readProperties( + id: ResourceId, + defaultTitle: String, + ): UserNoteProperties { + val title = + propertiesStorage.getProperties(id).titles.let { + if (it.isNotEmpty()) it.elementAt(0) else defaultTitle + } + val description = + propertiesStorage.getProperties(id).descriptions.let { + if (it.isNotEmpty()) it.elementAt(0) else "" + } + return UserNoteProperties(title, description) } - return UserNoteProperties(title, description) - } - suspend fun deleteNote(note: Note): Unit = withContext(Dispatchers.IO) { - val id = note.resource?.id + suspend fun deleteNote(note: Note): Unit = + withContext(Dispatchers.IO) { + val id = note.resource?.id - val path = root.resolve("${note.resource?.name}") - path.deleteIfExists() - note.resource?.id?.let { resourceId -> - try { - propertiesStorage.remove(resourceId) - } catch (ex: NullPointerException) { - Log.e("NotesRepoHelper", "deleteNote exception: " + ex.message) - } - } + val path = root.resolve("${note.resource?.name}") + path.deleteIfExists() + note.resource?.id?.let { resourceId -> + try { + propertiesStorage.remove(resourceId) + } catch (ex: NullPointerException) { + Log.e("NotesRepoHelper", "deleteNote exception: " + ex.message) + } + } - propertiesStorage.persist() - note.resource?.name?.let { name -> - Log.d("NotesRepoHelper", "${name} has been deleted. id: " + id) - } + propertiesStorage.persist() + note.resource?.name?.let { name -> + Log.d("NotesRepoHelper", "$name has been deleted. id: " + id) + } + } } -} data class UserNoteProperties( val title: String, - val description: String -) \ No newline at end of file + val description: String, +) diff --git a/app/src/main/java/dev/arkbuilders/arkmemo/repo/graphics/GraphicNotesRepo.kt b/app/src/main/java/dev/arkbuilders/arkmemo/repo/graphics/GraphicNotesRepo.kt index da5bdf47..5853ff87 100644 --- a/app/src/main/java/dev/arkbuilders/arkmemo/repo/graphics/GraphicNotesRepo.kt +++ b/app/src/main/java/dev/arkbuilders/arkmemo/repo/graphics/GraphicNotesRepo.kt @@ -4,10 +4,10 @@ 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.preferences.MemoPreferences import dev.arkbuilders.arkmemo.graphics.SVG +import dev.arkbuilders.arkmemo.models.GraphicNote import dev.arkbuilders.arkmemo.models.SaveNoteResult +import dev.arkbuilders.arkmemo.preferences.MemoPreferences import dev.arkbuilders.arkmemo.repo.NotesRepo import dev.arkbuilders.arkmemo.repo.NotesRepoHelper import dev.arkbuilders.arkmemo.utils.listFiles @@ -16,102 +16,109 @@ import kotlinx.coroutines.withContext import java.nio.file.Path import javax.inject.Inject import javax.inject.Named +import kotlin.io.path.createTempFile +import kotlin.io.path.exists import kotlin.io.path.extension import kotlin.io.path.fileSize import kotlin.io.path.getLastModifiedTime import kotlin.io.path.name -import kotlin.io.path.createTempFile -import kotlin.io.path.exists - -class GraphicNotesRepo @Inject constructor( - private val memoPreferences: MemoPreferences, - @Named(IO_DISPATCHER) private val iODispatcher: CoroutineDispatcher, - private val helper: NotesRepoHelper -): NotesRepo { - - private lateinit var root: Path - override suspend fun init() { - helper.init() - root = memoPreferences.getNotesStorage() - } - - override suspend fun save( - note: GraphicNote, - callback: (SaveNoteResult) -> Unit - ) = withContext(iODispatcher) { - write(note) { callback(it) } - } +class GraphicNotesRepo + @Inject + constructor( + private val memoPreferences: MemoPreferences, + @Named(IO_DISPATCHER) private val iODispatcher: CoroutineDispatcher, + private val helper: NotesRepoHelper, + ) : NotesRepo { + private lateinit var root: Path - override suspend fun delete(note: GraphicNote) = withContext(iODispatcher) { - helper.deleteNote(note) - } + override suspend fun init() { + helper.init() + root = memoPreferences.getNotesStorage() + } - override suspend fun read(): List = withContext(iODispatcher) { - readStorage() - } + override suspend fun save( + note: GraphicNote, + callback: (SaveNoteResult) -> Unit, + ) = withContext(iODispatcher) { + write(note) { callback(it) } + } - private suspend fun write( - note: GraphicNote, - callback: (SaveNoteResult) -> Unit - ) = withContext(iODispatcher) { - val tempPath = createTempFile() - note.svg?.generate(tempPath) - val size = tempPath.fileSize() - val id = computeId(size, tempPath) - Log.d(GRAPHICS_REPO, "initial resource name is ${tempPath.name}") - val isPropertiesChanged = helper.persistNoteProperties( - resourceId = id, - noteTitle = note.title, - description = note.description) + override suspend fun delete(note: GraphicNote) = + withContext(iODispatcher) { + helper.deleteNote(note) + } - val resourcePath = root.resolve("${id}.$SVG_EXT") - if (resourcePath.exists()) { - if (isPropertiesChanged) { - callback(SaveNoteResult.SUCCESS_UPDATED) - } else { - Log.d(GRAPHICS_REPO, "resource with similar content already exists") - callback(SaveNoteResult.ERROR_EXISTING) + override suspend fun read(): List = + withContext(iODispatcher) { + readStorage() } - return@withContext - } - helper.renameResource( - note, - tempPath, - resourcePath, - id - ) - Log.d(GRAPHICS_REPO, "resource renamed to $resourcePath successfully") - callback(SaveNoteResult.SUCCESS_NEW) - } + private suspend fun write( + note: GraphicNote, + callback: (SaveNoteResult) -> Unit, + ) = withContext(iODispatcher) { + val tempPath = createTempFile() + note.svg?.generate(tempPath) + val size = tempPath.fileSize() + val id = computeId(size, tempPath) + Log.d(GRAPHICS_REPO, "initial resource name is ${tempPath.name}") + val isPropertiesChanged = + helper.persistNoteProperties( + resourceId = id, + noteTitle = note.title, + description = note.description, + ) - private suspend fun readStorage() = withContext(iODispatcher) { - root.listFiles(SVG_EXT) { path -> - val svg = SVG.parse(path) - if (svg == null) { - Log.w(GRAPHICS_REPO, "Skipping invalid SVG: " + path) + val resourcePath = root.resolve("$id.$SVG_EXT") + if (resourcePath.exists()) { + if (isPropertiesChanged) { + callback(SaveNoteResult.SUCCESS_UPDATED) + } else { + Log.d(GRAPHICS_REPO, "resource with similar content already exists") + callback(SaveNoteResult.ERROR_EXISTING) + } + return@withContext } - val size = path.fileSize() - val id = computeId(size, path) - val resource = Resource( - id = id, - name = path.fileName.name, - extension = path.extension, - modified = path.getLastModifiedTime() + + helper.renameResource( + note, + tempPath, + resourcePath, + id, ) + Log.d(GRAPHICS_REPO, "resource renamed to $resourcePath successfully") + callback(SaveNoteResult.SUCCESS_NEW) + } - val userNoteProperties = helper.readProperties(id, "") + private suspend fun readStorage() = + withContext(iODispatcher) { + root.listFiles(SVG_EXT) { path -> + val svg = SVG.parse(path) + if (svg == null) { + Log.w(GRAPHICS_REPO, "Skipping invalid SVG: " + path) + } + val size = path.fileSize() + val id = computeId(size, path) + val resource = + Resource( + id = id, + name = path.fileName.name, + extension = path.extension, + modified = path.getLastModifiedTime(), + ) - GraphicNote( - title = userNoteProperties.title, - description = userNoteProperties.description, - svg = svg, - resource = resource - ) - }.filter { graphicNote -> graphicNote.svg != null } + val userNoteProperties = helper.readProperties(id, "") + + GraphicNote( + title = userNoteProperties.title, + description = userNoteProperties.description, + svg = svg, + resource = resource, + ) + }.filter { graphicNote -> graphicNote.svg != null } + } } -} private const val GRAPHICS_REPO = "GraphicNotesRepo" private const val SVG_EXT = "svg" diff --git a/app/src/main/java/dev/arkbuilders/arkmemo/repo/text/TextNotesRepo.kt b/app/src/main/java/dev/arkbuilders/arkmemo/repo/text/TextNotesRepo.kt index b339b866..0ac224bf 100644 --- a/app/src/main/java/dev/arkbuilders/arkmemo/repo/text/TextNotesRepo.kt +++ b/app/src/main/java/dev/arkbuilders/arkmemo/repo/text/TextNotesRepo.kt @@ -5,122 +5,131 @@ import dev.arkbuilders.arklib.computeId import dev.arkbuilders.arklib.data.index.Resource import dev.arkbuilders.arkmemo.di.IO_DISPATCHER import dev.arkbuilders.arkmemo.models.SaveNoteResult -import kotlinx.coroutines.CoroutineDispatcher -import kotlinx.coroutines.withContext import dev.arkbuilders.arkmemo.models.TextNote 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 dev.arkbuilders.arkmemo.utils.readLines +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.createTempFile +import kotlin.io.path.exists import kotlin.io.path.extension import kotlin.io.path.fileSize import kotlin.io.path.getLastModifiedTime import kotlin.io.path.name import kotlin.io.path.writeLines -import kotlin.io.path.createTempFile -import kotlin.io.path.exists - -class TextNotesRepo @Inject constructor( - private val memoPreferences: MemoPreferences, - @Named(IO_DISPATCHER) - private val iODispatcher: CoroutineDispatcher, - private val helper: NotesRepoHelper -): NotesRepo { - private lateinit var root: Path +class TextNotesRepo + @Inject + constructor( + private val memoPreferences: MemoPreferences, + @Named(IO_DISPATCHER) + private val iODispatcher: CoroutineDispatcher, + private val helper: NotesRepoHelper, + ) : NotesRepo { + private lateinit var root: Path - override suspend fun init() { - root = memoPreferences.getNotesStorage() - helper.init() - } + override suspend fun init() { + root = memoPreferences.getNotesStorage() + helper.init() + } - override suspend fun save(note: TextNote, callback: (SaveNoteResult) -> Unit) { - write(note) { callback(it) } - } + override suspend fun save( + note: TextNote, + callback: (SaveNoteResult) -> Unit, + ) { + write(note) { callback(it) } + } - override suspend fun delete(note: TextNote) { - helper.deleteNote(note) - } + override suspend fun delete(note: TextNote) { + helper.deleteNote(note) + } - override suspend fun read(): List = withContext(iODispatcher) { - readStorage() - } + override suspend fun read(): List = + withContext(iODispatcher) { + readStorage() + } - private suspend fun write( - note: TextNote, - callback: (SaveNoteResult) -> Unit - ) = withContext(iODispatcher) { - val tempPath = createTempFile() - val lines = note.text.split('\n') - tempPath.writeLines(lines) - val size = tempPath.fileSize() - val id = computeId(size, tempPath) - Log.d(TEXT_REPO, "initial resource name is ${tempPath.name}") - val isPropertiesChanged = helper.persistNoteProperties( - resourceId = id, - noteTitle = note.title, - description = note.description) + private suspend fun write( + note: TextNote, + callback: (SaveNoteResult) -> Unit, + ) = withContext(iODispatcher) { + val tempPath = createTempFile() + val lines = note.text.split('\n') + tempPath.writeLines(lines) + val size = tempPath.fileSize() + val id = computeId(size, tempPath) + Log.d(TEXT_REPO, "initial resource name is ${tempPath.name}") + val isPropertiesChanged = + helper.persistNoteProperties( + resourceId = id, + noteTitle = note.title, + description = note.description, + ) - val resourcePath = root.resolve("$id.$NOTE_EXT") - if (resourcePath.exists()) { - if (isPropertiesChanged) { - callback(SaveNoteResult.SUCCESS_UPDATED) - } else { - Log.d(TEXT_REPO, "resource with similar content already exists") - callback(SaveNoteResult.ERROR_EXISTING) + val resourcePath = root.resolve("$id.$NOTE_EXT") + if (resourcePath.exists()) { + if (isPropertiesChanged) { + callback(SaveNoteResult.SUCCESS_UPDATED) + } else { + Log.d(TEXT_REPO, "resource with similar content already exists") + callback(SaveNoteResult.ERROR_EXISTING) + } + return@withContext } - return@withContext - } - helper.renameResource( - note = note, - tempPath = tempPath, - resourcePath = resourcePath, - resourceId = id - ) - Log.d(TEXT_REPO, "resource renamed to $resourcePath successfully") - callback(SaveNoteResult.SUCCESS_NEW) - } - - private suspend fun readStorage(): List = withContext(iODispatcher) { - root.listFiles(NOTE_EXT) { path -> - val size = path.fileSize() - val id = computeId(size, path) - val resource = Resource( - id = id, - name = path.fileName.name, - extension = path.extension, - modified = path.getLastModifiedTime() + helper.renameResource( + note = note, + tempPath = tempPath, + resourcePath = resourcePath, + resourceId = id, ) + Log.d(TEXT_REPO, "resource renamed to $resourcePath successfully") + callback(SaveNoteResult.SUCCESS_NEW) + } - try { - path.readLines { data -> - val userNoteProperties = helper.readProperties( - id, - data.substringBefore("\n") - ) + private suspend fun readStorage(): List = + withContext(iODispatcher) { + root.listFiles(NOTE_EXT) { path -> + val size = path.fileSize() + val id = computeId(size, path) + val resource = + Resource( + id = id, + name = path.fileName.name, + extension = path.extension, + modified = path.getLastModifiedTime(), + ) - TextNote( - title = userNoteProperties.title, - description = userNoteProperties.description, - text = data, - resource = resource - ) - } - } catch (e: Exception) { - e.printStackTrace() - TextNote( - text = "", - ) + try { + path.readLines { data -> + val userNoteProperties = + helper.readProperties( + id, + data.substringBefore("\n"), + ) + + TextNote( + title = userNoteProperties.title, + description = userNoteProperties.description, + text = data, + resource = resource, + ) + } + } catch (e: Exception) { + e.printStackTrace() + TextNote( + text = "", + ) + } + }.filter { textNote -> textNote.text.isNotEmpty() } } - }.filter { textNote -> textNote.text.isNotEmpty() } } -} private const val TEXT_REPO = "TextNotesRepo" private const val NOTE_EXT = "note" - diff --git a/app/src/main/java/dev/arkbuilders/arkmemo/repo/voices/VoiceNotesRepo.kt b/app/src/main/java/dev/arkbuilders/arkmemo/repo/voices/VoiceNotesRepo.kt index ad6514a3..abda016f 100644 --- a/app/src/main/java/dev/arkbuilders/arkmemo/repo/voices/VoiceNotesRepo.kt +++ b/app/src/main/java/dev/arkbuilders/arkmemo/repo/voices/VoiceNotesRepo.kt @@ -17,100 +17,109 @@ 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.extension +import kotlin.io.path.fileSize import kotlin.io.path.getLastModifiedTime import kotlin.io.path.name import kotlin.io.path.pathString -class VoiceNotesRepo @Inject constructor( - private val memoPreferences: MemoPreferences, - @Named(IO_DISPATCHER) private val iODispatcher: CoroutineDispatcher, - private val helper: NotesRepoHelper -): NotesRepo { - - private lateinit var root: Path - - override suspend fun init() { - root = memoPreferences.getNotesStorage() - helper.init() - } - - override suspend fun read(): List = 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) - - val isPropertiesChanged = helper.persistNoteProperties( - resourceId = id, - noteTitle = note.title, - description = note.description) +class VoiceNotesRepo + @Inject + constructor( + private val memoPreferences: MemoPreferences, + @Named(IO_DISPATCHER) private val iODispatcher: CoroutineDispatcher, + private val helper: NotesRepoHelper, + ) : NotesRepo { + private lateinit var root: Path + + override suspend fun init() { + root = memoPreferences.getNotesStorage() + helper.init() + } - Log.d(VOICES_REPO, "initial resource name is ${tempPath.name}") + override suspend fun read(): List = + withContext(iODispatcher) { + readStorage() + } - helper.persistNoteProperties(resourceId = id, noteTitle = note.title) + override suspend fun delete(note: VoiceNote) { + helper.deleteNote(note) + } - val resourcePath = root.resolve("${id}.$VOICE_EXT") - if (resourcePath.exists()) { - Log.d( - VOICES_REPO, - "resource with similar content already exists" - ) - if (isPropertiesChanged) { - callback(SaveNoteResult.SUCCESS_UPDATED) - } else { - callback(SaveNoteResult.ERROR_EXISTING) - } - return@withContext + override suspend fun save( + note: VoiceNote, + callback: (SaveNoteResult) -> Unit, + ) { + write(note) { callback(it) } } - helper.renameResource( - note, - tempPath, - resourcePath, - id - ) - note.path = resourcePath - Log.d(VOICES_REPO, "resource renamed to $resourcePath successfully") - callback(SaveNoteResult.SUCCESS_NEW) - } + private suspend fun write( + note: VoiceNote, + callback: (SaveNoteResult) -> Unit, + ) = withContext(iODispatcher) { + val tempPath = note.path + val size = tempPath.fileSize() + val id = computeId(size, tempPath) + + val isPropertiesChanged = + helper.persistNoteProperties( + resourceId = id, + noteTitle = note.title, + description = note.description, + ) + + 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", + ) + if (isPropertiesChanged) { + callback(SaveNoteResult.SUCCESS_UPDATED) + } else { + callback(SaveNoteResult.ERROR_EXISTING) + } + return@withContext + } - private suspend fun readStorage(): List = 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() + helper.renameResource( + note, + tempPath, + resourcePath, + id, ) + note.path = resourcePath + Log.d(VOICES_REPO, "resource renamed to $resourcePath successfully") + callback(SaveNoteResult.SUCCESS_NEW) + } - val userNoteProperties = helper.readProperties(id, "") - VoiceNote( - title = userNoteProperties.title, - description = userNoteProperties.description, - path = path, - duration = extractDuration(path.pathString), - resource = resource - ) - }.filter { voiceNote -> voiceNote.duration.isNotEmpty() } + private suspend fun readStorage(): List = + 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, + duration = extractDuration(path.pathString), + resource = resource, + ) + }.filter { voiceNote -> voiceNote.duration.isNotEmpty() } + } } -} private const val VOICES_REPO = "VoiceNotesRepo" -private const val VOICE_EXT = "3gp" \ No newline at end of file +private const val VOICE_EXT = "3gp" diff --git a/app/src/main/java/dev/arkbuilders/arkmemo/ui/activities/MainActivity.kt b/app/src/main/java/dev/arkbuilders/arkmemo/ui/activities/MainActivity.kt index 95271120..dd2ded54 100644 --- a/app/src/main/java/dev/arkbuilders/arkmemo/ui/activities/MainActivity.kt +++ b/app/src/main/java/dev/arkbuilders/arkmemo/ui/activities/MainActivity.kt @@ -16,8 +16,8 @@ import dev.arkbuilders.arkfilepicker.presentation.onArkPathPicked import dev.arkbuilders.arkmemo.R import dev.arkbuilders.arkmemo.contracts.PermissionContract import dev.arkbuilders.arkmemo.databinding.ActivityMainBinding -import dev.arkbuilders.arkmemo.ui.dialogs.FilePickerDialog import dev.arkbuilders.arkmemo.preferences.MemoPreferences +import dev.arkbuilders.arkmemo.ui.dialogs.FilePickerDialog import dev.arkbuilders.arkmemo.ui.fragments.BaseFragment import dev.arkbuilders.arkmemo.ui.fragments.EditTextNotesFragment import dev.arkbuilders.arkmemo.ui.fragments.NotesFragment @@ -25,7 +25,6 @@ import javax.inject.Inject @AndroidEntryPoint class MainActivity : AppCompatActivity(R.layout.activity_main) { - private val binding by viewBinding(ActivityMainBinding::bind) @Inject @@ -39,14 +38,20 @@ class MainActivity : AppCompatActivity(R.layout.activity_main) { init { FilePickerDialog.readPermLauncher = registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted -> - if (isGranted) FilePickerDialog.show() - else finish() + if (isGranted) { + FilePickerDialog.show() + } else { + finish() + } } - FilePickerDialog.readPermLauncher_SDK_R = + FilePickerDialog.readPermLauncherSdkR = registerForActivityResult(PermissionContract()) { isGranted -> - if (isGranted) FilePickerDialog.show() - else finish() + if (isGranted) { + FilePickerDialog.show() + } else { + finish() + } } } @@ -68,12 +73,12 @@ class MainActivity : AppCompatActivity(R.layout.activity_main) { commit() } } else { - if (savedInstanceState == null) + if (savedInstanceState == null) { supportFragmentManager.beginTransaction().apply { add(fragContainer, fragment, NotesFragment.TAG) commit() } - else { + } else { supportFragmentManager.apply { val tag = savedInstanceState.getString(CURRENT_FRAGMENT_TAG) findFragmentByTag(tag)?.let { @@ -94,8 +99,9 @@ class MainActivity : AppCompatActivity(R.layout.activity_main) { memoPreferences.storePath(it.toString()) showFragment() } + } else { + showFragment() } - else showFragment() } override fun onSaveInstanceState(outState: Bundle) { @@ -112,7 +118,10 @@ class MainActivity : AppCompatActivity(R.layout.activity_main) { supportActionBar?.setDisplayHomeAsUpEnabled(true) } - private fun setStatusBarColor(color: Int, isLight: Boolean) { + private fun setStatusBarColor( + color: Int, + isLight: Boolean, + ) { window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) window.statusBarColor = color WindowInsetsControllerCompat(window, window.decorView).isAppearanceLightStatusBars = isLight @@ -126,13 +135,13 @@ class MainActivity : AppCompatActivity(R.layout.activity_main) { } } - companion object{ + companion object { private const val CURRENT_FRAGMENT_TAG = "current fragment tag" } } -fun AppCompatActivity.resumeFragment(fragment: Fragment){ - supportFragmentManager.beginTransaction().apply{ +fun AppCompatActivity.resumeFragment(fragment: Fragment) { + supportFragmentManager.beginTransaction().apply { show(fragment) commit() } diff --git a/app/src/main/java/dev/arkbuilders/arkmemo/ui/adapters/BrushAdapter.kt b/app/src/main/java/dev/arkbuilders/arkmemo/ui/adapters/BrushAdapter.kt index 364c4a6c..5bdceab3 100644 --- a/app/src/main/java/dev/arkbuilders/arkmemo/ui/adapters/BrushAdapter.kt +++ b/app/src/main/java/dev/arkbuilders/arkmemo/ui/adapters/BrushAdapter.kt @@ -9,57 +9,69 @@ import dev.arkbuilders.arkmemo.databinding.AdapterBrushBinding class BrushAdapter( private val attributes: List, private val onItemClick: (attribute: BrushAttribute, pos: Int) -> Unit, -): RecyclerView.Adapter() { - - private var lastSelectedPos = attributes.indexOfFirst { it.isSelected } - .coerceAtLeast(0) - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BrushTypeViewHolder { +) : RecyclerView.Adapter() { + private var lastSelectedPos = + attributes.indexOfFirst { it.isSelected } + .coerceAtLeast(0) + + override fun onCreateViewHolder( + parent: ViewGroup, + viewType: Int, + ): BrushTypeViewHolder { val binding = AdapterBrushBinding.inflate(LayoutInflater.from(parent.context)) return BrushTypeViewHolder(binding) } - override fun onBindViewHolder(holder: BrushTypeViewHolder, position: Int) { + override fun onBindViewHolder( + holder: BrushTypeViewHolder, + position: Int, + ) { val attribute = attributes[holder.bindingAdapterPosition] val context = holder.itemView.context when (attribute) { is BrushSizeTiny -> { holder.ivBrush.setImageResource(R.drawable.bg_brush_size) - val padding = holder.itemView.context.resources.getDimensionPixelSize( - R.dimen.brush_size_tiny_padding - ) + val padding = + holder.itemView.context.resources.getDimensionPixelSize( + R.dimen.brush_size_tiny_padding, + ) holder.ivBrush.setPadding(padding, padding, padding, padding) } is BrushSizeSmall -> { holder.ivBrush.setImageResource(R.drawable.bg_brush_size) - val padding = holder.itemView.context.resources.getDimensionPixelSize( - R.dimen.brush_size_small_padding - ) + val padding = + holder.itemView.context.resources.getDimensionPixelSize( + R.dimen.brush_size_small_padding, + ) holder.ivBrush.setPadding(padding, padding, padding, padding) } is BrushSizeMedium -> { holder.ivBrush.setImageResource(R.drawable.bg_brush_size) - val padding = holder.itemView.context.resources.getDimensionPixelSize( - R.dimen.brush_size_medium_padding - ) + val padding = + holder.itemView.context.resources.getDimensionPixelSize( + R.dimen.brush_size_medium_padding, + ) holder.ivBrush.setPadding(padding, padding, padding, padding) } is BrushSizeLarge -> { holder.ivBrush.setImageResource(R.drawable.bg_brush_size) - val padding = holder.itemView.context.resources.getDimensionPixelSize( - R.dimen.brush_size_large_padding - ) + val padding = + holder.itemView.context.resources.getDimensionPixelSize( + R.dimen.brush_size_large_padding, + ) holder.ivBrush.setPadding(padding, padding, padding, padding) } is BrushSizeHuge -> { holder.ivBrush.setImageResource(R.drawable.bg_brush_size) - val padding = holder.itemView.context.resources.getDimensionPixelSize( - R.dimen.brush_size_huge_padding - ) + val padding = + holder.itemView.context.resources.getDimensionPixelSize( + R.dimen.brush_size_huge_padding, + ) holder.ivBrush.setPadding(padding, padding, padding, padding) } @@ -82,9 +94,10 @@ class BrushAdapter( if (attribute.isSelected) { holder.rootView.setBackgroundResource(R.drawable.bg_selected_brush) if (attribute is BrushColor) { - val padding = holder.itemView.context.resources.getDimensionPixelSize( - R.dimen.brush_size_huge_padding - ) + val padding = + holder.itemView.context.resources.getDimensionPixelSize( + R.dimen.brush_size_huge_padding, + ) holder.ivBrush.setPadding(padding, padding, padding, padding) } } else { @@ -110,9 +123,9 @@ class BrushAdapter( override fun getItemCount() = attributes.size - inner class BrushTypeViewHolder(binding: AdapterBrushBinding) - : RecyclerView.ViewHolder(binding.root) { + inner class BrushTypeViewHolder(binding: AdapterBrushBinding) : + RecyclerView.ViewHolder(binding.root) { val ivBrush = binding.ivBrush val rootView = binding.root } -} \ No newline at end of file +} diff --git a/app/src/main/java/dev/arkbuilders/arkmemo/ui/adapters/BrushAttribute.kt b/app/src/main/java/dev/arkbuilders/arkmemo/ui/adapters/BrushAttribute.kt index bba900d0..67ae250a 100644 --- a/app/src/main/java/dev/arkbuilders/arkmemo/ui/adapters/BrushAttribute.kt +++ b/app/src/main/java/dev/arkbuilders/arkmemo/ui/adapters/BrushAttribute.kt @@ -13,15 +13,25 @@ sealed class BrushColor : BrushAttribute { } data object BrushSizeTiny : BrushSize() + data object BrushSizeSmall : BrushSize() + data object BrushSizeMedium : BrushSize() + data object BrushSizeLarge : BrushSize() + data object BrushSizeHuge : BrushSize() data object BrushColorBlack : BrushColor() + data object BrushColorGrey : BrushColor() + data object BrushColorRed : BrushColor() + data object BrushColorOrange : BrushColor() + data object BrushColorGreen : BrushColor() + data object BrushColorBlue : BrushColor() -data object BrushColorPurple : BrushColor() \ No newline at end of file + +data object BrushColorPurple : BrushColor() diff --git a/app/src/main/java/dev/arkbuilders/arkmemo/ui/adapters/EqualSpacingItemDecoration.kt b/app/src/main/java/dev/arkbuilders/arkmemo/ui/adapters/EqualSpacingItemDecoration.kt index 46ec462f..451be376 100644 --- a/app/src/main/java/dev/arkbuilders/arkmemo/ui/adapters/EqualSpacingItemDecoration.kt +++ b/app/src/main/java/dev/arkbuilders/arkmemo/ui/adapters/EqualSpacingItemDecoration.kt @@ -5,67 +5,69 @@ import android.view.View import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.RecyclerView -class EqualSpacingItemDecoration @JvmOverloads constructor( - private val spacing: Int, - private var displayMode: Int = -1 -) : RecyclerView.ItemDecoration() { - override fun getItemOffsets( - outRect: Rect, - view: View, - parent: RecyclerView, - state: RecyclerView.State - ) { - val position = parent.getChildViewHolder(view).bindingAdapterPosition - val itemCount = state.itemCount - val layoutManager = parent.layoutManager - setSpacingForDirection(outRect, layoutManager, position, itemCount) - } - - private fun setSpacingForDirection( - outRect: Rect, - layoutManager: RecyclerView.LayoutManager?, - position: Int, - itemCount: Int - ) { - - // Resolve display mode automatically - if (displayMode == -1) { - displayMode = resolveDisplayMode(layoutManager) +class EqualSpacingItemDecoration + @JvmOverloads + constructor( + private val spacing: Int, + private var displayMode: Int = -1, + ) : RecyclerView.ItemDecoration() { + override fun getItemOffsets( + outRect: Rect, + view: View, + parent: RecyclerView, + state: RecyclerView.State, + ) { + val position = parent.getChildViewHolder(view).bindingAdapterPosition + val itemCount = state.itemCount + val layoutManager = parent.layoutManager + setSpacingForDirection(outRect, layoutManager, position, itemCount) } - when (displayMode) { - HORIZONTAL -> { - outRect.left = spacing - outRect.right = if (position == itemCount - 1) spacing else 0 - outRect.top = 0 - outRect.bottom = 0 - } - VERTICAL -> { - outRect.left = spacing - outRect.right = spacing - outRect.top = spacing - outRect.bottom = if (position == itemCount - 1) spacing else 0 + private fun setSpacingForDirection( + outRect: Rect, + layoutManager: RecyclerView.LayoutManager?, + position: Int, + itemCount: Int, + ) { + // Resolve display mode automatically + if (displayMode == -1) { + displayMode = resolveDisplayMode(layoutManager) } + when (displayMode) { + HORIZONTAL -> { + outRect.left = spacing + outRect.right = if (position == itemCount - 1) spacing else 0 + outRect.top = 0 + outRect.bottom = 0 + } + + VERTICAL -> { + outRect.left = spacing + outRect.right = spacing + outRect.top = spacing + outRect.bottom = if (position == itemCount - 1) spacing else 0 + } - GRID -> if (layoutManager is GridLayoutManager) { - val cols = layoutManager.spanCount - val rows = itemCount / cols - outRect.left = spacing - outRect.right = if (position % cols == cols - 1) spacing else 0 - outRect.top = spacing - outRect.bottom = if (position / cols == rows - 1) spacing else 0 + GRID -> + if (layoutManager is GridLayoutManager) { + val cols = layoutManager.spanCount + val rows = itemCount / cols + outRect.left = spacing + outRect.right = if (position % cols == cols - 1) spacing else 0 + outRect.top = spacing + outRect.bottom = if (position / cols == rows - 1) spacing else 0 + } } } - } - private fun resolveDisplayMode(layoutManager: RecyclerView.LayoutManager?): Int { - if (layoutManager is GridLayoutManager) return GRID - return if (layoutManager!!.canScrollHorizontally()) HORIZONTAL else VERTICAL - } + private fun resolveDisplayMode(layoutManager: RecyclerView.LayoutManager?): Int { + if (layoutManager is GridLayoutManager) return GRID + return if (layoutManager!!.canScrollHorizontally()) HORIZONTAL else VERTICAL + } - companion object { - const val HORIZONTAL = 0 - const val VERTICAL = 1 - const val GRID = 2 + companion object { + const val HORIZONTAL = 0 + const val VERTICAL = 1 + const val GRID = 2 + } } -} diff --git a/app/src/main/java/dev/arkbuilders/arkmemo/ui/adapters/NotesListAdapter.kt b/app/src/main/java/dev/arkbuilders/arkmemo/ui/adapters/NotesListAdapter.kt index a4f3cc85..2ff844ec 100644 --- a/app/src/main/java/dev/arkbuilders/arkmemo/ui/adapters/NotesListAdapter.kt +++ b/app/src/main/java/dev/arkbuilders/arkmemo/ui/adapters/NotesListAdapter.kt @@ -31,9 +31,8 @@ import dev.arkbuilders.arkmemo.utils.visible class NotesListAdapter( private var notes: MutableList, private val onPlayPauseClick: (path: String, pos: Int?, stopCallback: ((pos: Int) -> Unit)?) -> Unit, - private val onThumbPrepare : (note: GraphicNote, holder: NotesCanvas) -> Unit -): RecyclerView.Adapter() { - + private val onThumbPrepare: (note: GraphicNote, holder: NotesCanvas) -> Unit, +) : RecyclerView.Adapter() { private lateinit var activity: MainActivity lateinit var observeItemSideEffect: () -> ArkMediaPlayerSideEffect @@ -46,13 +45,19 @@ class NotesListAdapter( this.activity = activity as MainActivity } - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): NoteViewHolder { + override fun onCreateViewHolder( + parent: ViewGroup, + viewType: Int, + ): NoteViewHolder { val binding = AdapterTextNoteBinding.inflate(LayoutInflater.from(parent.context), parent, false) binding.root.clipToOutline = true return NoteViewHolder(binding.root) } - override fun onBindViewHolder(holder: NoteViewHolder, position: Int) { + override fun onBindViewHolder( + holder: NoteViewHolder, + position: Int, + ) { val note = notes[holder.bindingAdapterPosition] holder.title.text = note.getAutoTitle(activity) @@ -103,7 +108,6 @@ class NotesListAdapter( showPlaybackIdleState(holder) } } - } else if (note is GraphicNote) { holder.canvasGraphicThumb.visible() onThumbPrepare(note, holder.canvasGraphicThumb) @@ -120,7 +124,7 @@ class NotesListAdapter( private fun handleMediaPlayerSideEffect( effect: ArkMediaPlayerSideEffect, - holder: NoteViewHolder + holder: NoteViewHolder, ) { when (effect) { is ArkMediaPlayerSideEffect.StartPlaying -> { @@ -139,12 +143,16 @@ class NotesListAdapter( } } - private fun showPlaybackIdleState(holder: NoteViewHolder, isPaused: Boolean = false) { - val playIcon = ResourcesCompat.getDrawable( - activity.resources, - R.drawable.ic_play_circle, - null - ) + private fun showPlaybackIdleState( + holder: NoteViewHolder, + isPaused: Boolean = false, + ) { + val playIcon = + ResourcesCompat.getDrawable( + activity.resources, + R.drawable.ic_play_circle, + null, + ) holder.btnPlayPause.setImageDrawable(playIcon) if (!isPaused) { @@ -156,16 +164,21 @@ class NotesListAdapter( } private fun showPlayingState(holder: NoteViewHolder) { - val playIcon = ResourcesCompat.getDrawable( - activity.resources, - R.drawable.ic_pause_circle, - null - ) + val playIcon = + ResourcesCompat.getDrawable( + activity.resources, + R.drawable.ic_pause_circle, + null, + ) holder.btnPlayPause.setImageDrawable(playIcon) holder.layoutAudioView.animAudioPlaying.background = null } - fun updateData(newNotes: List, fromSearch: Boolean? = null, keyword: String? = null) { + fun updateData( + newNotes: List, + fromSearch: Boolean? = null, + keyword: String? = null, + ) { notes = newNotes.toMutableList() isFromSearch = fromSearch ?: false searchKeyWord = keyword ?: "" @@ -184,7 +197,7 @@ class NotesListAdapter( notes.remove(noteToRemove) } - inner class NoteViewHolder(itemView: View): RecyclerView.ViewHolder(itemView) { + inner class NoteViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { private val binding by viewBinding { AdapterTextNoteBinding.bind(itemView) } @@ -198,24 +211,25 @@ class NotesListAdapter( val tvDelete = binding.tvDelete var isSwiping: Boolean = false - private val clickNoteToEditListener = View.OnClickListener { - var tag = EditTextNotesFragment.TAG - when (val selectedNote = notes[bindingAdapterPosition]) { - is TextNote -> activity.fragment = EditTextNotesFragment.newInstance(selectedNote) - is GraphicNote -> { - activity.fragment = EditGraphicNotesFragment.newInstance(selectedNote) - tag = EditGraphicNotesFragment.TAG - } - is VoiceNote -> { - activity.fragment = ArkRecorderFragment.newInstance(selectedNote) - tag = ArkRecorderFragment.TAG + private val clickNoteToEditListener = + View.OnClickListener { + var tag = EditTextNotesFragment.TAG + when (val selectedNote = notes[bindingAdapterPosition]) { + is TextNote -> activity.fragment = EditTextNotesFragment.newInstance(selectedNote) + is GraphicNote -> { + activity.fragment = EditGraphicNotesFragment.newInstance(selectedNote) + tag = EditGraphicNotesFragment.TAG + } + is VoiceNote -> { + activity.fragment = ArkRecorderFragment.newInstance(selectedNote) + tag = ArkRecorderFragment.TAG + } } + activity.replaceFragment(activity.fragment, tag) } - activity.replaceFragment(activity.fragment, tag) - } init { binding.root.setOnClickListener(clickNoteToEditListener) } } -} \ No newline at end of file +} diff --git a/app/src/main/java/dev/arkbuilders/arkmemo/ui/adapters/TagAdapter.kt b/app/src/main/java/dev/arkbuilders/arkmemo/ui/adapters/TagAdapter.kt index a47549ac..79da53d5 100644 --- a/app/src/main/java/dev/arkbuilders/arkmemo/ui/adapters/TagAdapter.kt +++ b/app/src/main/java/dev/arkbuilders/arkmemo/ui/adapters/TagAdapter.kt @@ -10,24 +10,29 @@ import dev.arkbuilders.arkmemo.models.Tag class TagAdapter( private val tags: List, -): RecyclerView.Adapter() { - - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TagViewHolder { +) : RecyclerView.Adapter() { + override fun onCreateViewHolder( + parent: ViewGroup, + viewType: Int, + ): TagViewHolder { val binding = AdapterTagBinding.inflate(LayoutInflater.from(parent.context)) return TagViewHolder(binding.root) } - override fun onBindViewHolder(holder: TagViewHolder, position: Int) { + override fun onBindViewHolder( + holder: TagViewHolder, + position: Int, + ) { val tag = tags[position] holder.title.text = tag.value } override fun getItemCount() = tags.size - inner class TagViewHolder(itemView: View): RecyclerView.ViewHolder(itemView) { + inner class TagViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { private val binding by viewBinding { AdapterTagBinding.bind(itemView) } val title = binding.tvTag } -} \ No newline at end of file +} diff --git a/app/src/main/java/dev/arkbuilders/arkmemo/ui/dialogs/CommonActionDialog.kt b/app/src/main/java/dev/arkbuilders/arkmemo/ui/dialogs/CommonActionDialog.kt index 1dda9126..0367a678 100644 --- a/app/src/main/java/dev/arkbuilders/arkmemo/ui/dialogs/CommonActionDialog.kt +++ b/app/src/main/java/dev/arkbuilders/arkmemo/ui/dialogs/CommonActionDialog.kt @@ -13,24 +13,26 @@ import dev.arkbuilders.arkmemo.databinding.DialogCommonActionBinding * This is a common action dialog that can be used inside app. * It's a basic dialog with customizable title, message, one positive button and one negative button */ -class CommonActionDialog(@StringRes private val title: Int, - @StringRes private val message: Int, - @StringRes private val positiveText: Int, - @StringRes private val negativeText: Int, - private val isAlert: Boolean = false, - private val onPositiveClick: (() -> Unit)? = null, - private val onNegativeClicked: (() -> Unit)? = null, - private val onCloseClicked: (() -> Unit)? = null): DialogFragment() { - +class CommonActionDialog( + @StringRes private val title: Int, + @StringRes private val message: Int, + @StringRes private val positiveText: Int, + @StringRes private val negativeText: Int, + private val isAlert: Boolean = false, + private val onPositiveClick: (() -> Unit)? = null, + private val onNegativeClicked: (() -> Unit)? = null, + private val onCloseClicked: (() -> Unit)? = null, +) : DialogFragment() { companion object { val TAG = CommonActionDialog::class.java.name } + private lateinit var mBinding: DialogCommonActionBinding override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, - savedInstanceState: Bundle? + savedInstanceState: Bundle?, ): View { mBinding = DialogCommonActionBinding.inflate(inflater) initViews() @@ -38,7 +40,6 @@ class CommonActionDialog(@StringRes private val title: Int, } private fun initViews() { - dialog?.setCanceledOnTouchOutside(false) if (isAlert) { @@ -68,12 +69,13 @@ class CommonActionDialog(@StringRes private val title: Int, override fun onResume() { super.onResume() - dialog?.window?.setLayout(ViewGroup.LayoutParams.MATCH_PARENT, - ViewGroup.LayoutParams.WRAP_CONTENT) + dialog?.window?.setLayout( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT, + ) } override fun getTheme(): Int { return R.style.MemoDialog } - -} \ No newline at end of file +} diff --git a/app/src/main/java/dev/arkbuilders/arkmemo/ui/dialogs/DonateDialog.kt b/app/src/main/java/dev/arkbuilders/arkmemo/ui/dialogs/DonateDialog.kt index dbd639e9..51c51491 100644 --- a/app/src/main/java/dev/arkbuilders/arkmemo/ui/dialogs/DonateDialog.kt +++ b/app/src/main/java/dev/arkbuilders/arkmemo/ui/dialogs/DonateDialog.kt @@ -13,16 +13,16 @@ import dev.arkbuilders.arkmemo.ui.viewmodels.QRViewModel import dev.arkbuilders.arkmemo.ui.views.toast import dev.arkbuilders.arkmemo.utils.copyToClipboard - -class DonateDialog(private val walletAddress: String, - private val title: String, - private val onPositiveClick: (() -> Unit)? = null, - private val onCloseClicked: (() -> Unit)? = null, -): DialogFragment() { - +class DonateDialog( + private val walletAddress: String, + private val title: String, + private val onPositiveClick: (() -> Unit)? = null, + private val onCloseClicked: (() -> Unit)? = null, +) : DialogFragment() { companion object { val TAG: String = DonateDialog::class.java.name } + private lateinit var binding: DialogDonateQrBinding private val qrViewModel: QRViewModel by activityViewModels() @@ -37,7 +37,6 @@ class DonateDialog(private val walletAddress: String, } private fun initViews() { - dialog?.setCanceledOnTouchOutside(false) binding.ivClose.setOnClickListener { @@ -54,8 +53,10 @@ class DonateDialog(private val walletAddress: String, binding.tvAddress.text = walletAddress binding.layoutCopy.setOnClickListener { - context?.copyToClipboard(getString(R.string.setting_donate_wallet_clipboard_label), - walletAddress) + context?.copyToClipboard( + getString(R.string.setting_donate_wallet_clipboard_label), + walletAddress, + ) } initQRImage() @@ -76,12 +77,13 @@ class DonateDialog(private val walletAddress: String, override fun onResume() { super.onResume() - dialog?.window?.setLayout(ViewGroup.LayoutParams.MATCH_PARENT, - ViewGroup.LayoutParams.WRAP_CONTENT) + dialog?.window?.setLayout( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT, + ) } override fun getTheme(): Int { return R.style.MemoDialog } - -} \ No newline at end of file +} diff --git a/app/src/main/java/dev/arkbuilders/arkmemo/ui/fragments/ArkMediaPlayerFragment.kt b/app/src/main/java/dev/arkbuilders/arkmemo/ui/fragments/ArkMediaPlayerFragment.kt index e8d3f3ad..432d03a1 100644 --- a/app/src/main/java/dev/arkbuilders/arkmemo/ui/fragments/ArkMediaPlayerFragment.kt +++ b/app/src/main/java/dev/arkbuilders/arkmemo/ui/fragments/ArkMediaPlayerFragment.kt @@ -24,8 +24,7 @@ import java.time.LocalDate import java.time.format.DateTimeFormatter @AndroidEntryPoint -class ArkMediaPlayerFragment: BaseEditNoteFragment() { - +class ArkMediaPlayerFragment : BaseEditNoteFragment() { private val activity by lazy { requireActivity() as MainActivity } @@ -34,7 +33,10 @@ class ArkMediaPlayerFragment: BaseEditNoteFragment() { private lateinit var note: VoiceNote - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + override fun onViewCreated( + view: View, + savedInstanceState: Bundle?, + ) { super.onViewCreated(view, savedInstanceState) activity.initEditUI() initUI() @@ -58,15 +60,15 @@ class ArkMediaPlayerFragment: BaseEditNoteFragment() { } private fun initUI() { - binding.toolbar.ivRightActionIcon.setOnClickListener { showDeleteNoteDialog(note) } - val defaultTitle = getString( - R.string.ark_memo_voice_note, - LocalDate.now().format(DateTimeFormatter.ISO_DATE) - ) + val defaultTitle = + getString( + R.string.ark_memo_voice_note, + LocalDate.now().format(DateTimeFormatter.ISO_DATE), + ) binding.edtTitle.hint = defaultTitle binding.edtTitle.setText(note.title) @@ -79,7 +81,6 @@ class ArkMediaPlayerFragment: BaseEditNoteFragment() { arkMediaPlayerViewModel.getDurationString { duration -> binding.layoutAudioView.tvDuration.text = duration } - } else { binding.layoutAudioView.root.gone() binding.layoutAudioRecord.root.gone() @@ -91,7 +92,6 @@ class ArkMediaPlayerFragment: BaseEditNoteFragment() { arkMediaPlayerViewModel.onPlayOrPauseClick(recordingPath) binding.layoutAudioView.tvPlayingPosition.visible() } - } private fun showState(state: ArkMediaPlayerState) { @@ -148,29 +148,31 @@ class ArkMediaPlayerFragment: BaseEditNoteFragment() { } private fun showPlayIcon() { - val playIcon = ResourcesCompat.getDrawable( - activity.resources, - R.drawable.ic_play_circle, - null - ) + val playIcon = + ResourcesCompat.getDrawable( + activity.resources, + R.drawable.ic_play_circle, + null, + ) binding.layoutAudioView.ivPlayAudio.setImageDrawable(playIcon) } private fun showPauseIcon() { - val playIcon = ResourcesCompat.getDrawable( - activity.resources, - R.drawable.ic_pause_circle, - null - ) + val playIcon = + ResourcesCompat.getDrawable( + activity.resources, + R.drawable.ic_pause_circle, + null, + ) binding.layoutAudioView.ivPlayAudio.setImageDrawable(playIcon) } companion object { - const val TAG = "ark-media-player-fragment" - fun newInstance(note: VoiceNote) = ArkMediaPlayerFragment().apply { - setNote(note) - } + fun newInstance(note: VoiceNote) = + ArkMediaPlayerFragment().apply { + setNote(note) + } } -} \ No newline at end of file +} diff --git a/app/src/main/java/dev/arkbuilders/arkmemo/ui/fragments/ArkRecorderFragment.kt b/app/src/main/java/dev/arkbuilders/arkmemo/ui/fragments/ArkRecorderFragment.kt index c372bdab..fc5e0d44 100644 --- a/app/src/main/java/dev/arkbuilders/arkmemo/ui/fragments/ArkRecorderFragment.kt +++ b/app/src/main/java/dev/arkbuilders/arkmemo/ui/fragments/ArkRecorderFragment.kt @@ -29,9 +29,9 @@ import dev.arkbuilders.arkmemo.ui.dialogs.CommonActionDialog import dev.arkbuilders.arkmemo.ui.viewmodels.ArkMediaPlayerSideEffect import dev.arkbuilders.arkmemo.ui.viewmodels.ArkMediaPlayerState import dev.arkbuilders.arkmemo.ui.viewmodels.ArkMediaPlayerViewModel +import dev.arkbuilders.arkmemo.ui.viewmodels.ArkRecorderViewModel import dev.arkbuilders.arkmemo.ui.viewmodels.RecorderSideEffect import dev.arkbuilders.arkmemo.ui.viewmodels.RecorderState -import dev.arkbuilders.arkmemo.ui.viewmodels.ArkRecorderViewModel import dev.arkbuilders.arkmemo.ui.views.toast import dev.arkbuilders.arkmemo.utils.Permission import dev.arkbuilders.arkmemo.utils.gone @@ -47,14 +47,15 @@ import java.time.format.DateTimeFormatter import kotlin.io.path.Path @AndroidEntryPoint -class ArkRecorderFragment: BaseEditNoteFragment() { - - private val settingActivityLauncher = registerForActivityResult( - ActivityResultContracts.StartActivityForResult()) { _ -> - if (Permission.hasPermission(activity, Manifest.permission.RECORD_AUDIO)) { - observeDataStates() +class ArkRecorderFragment : BaseEditNoteFragment() { + private val settingActivityLauncher = + registerForActivityResult( + ActivityResultContracts.StartActivityForResult(), + ) { _ -> + if (Permission.hasPermission(activity, Manifest.permission.RECORD_AUDIO)) { + observeDataStates() + } } - } private val activity by lazy { requireActivity() as MainActivity } @@ -79,12 +80,17 @@ class ArkRecorderFragment: BaseEditNoteFragment() { private var note: VoiceNote? = null - private val defaultNoteTitle by lazy { getString( - R.string.ark_memo_voice_note, - LocalDate.now().format(DateTimeFormatter.ISO_DATE) - ) } + private val defaultNoteTitle by lazy { + getString( + R.string.ark_memo_voice_note, + LocalDate.now().format(DateTimeFormatter.ISO_DATE), + ) + } - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + override fun onViewCreated( + view: View, + savedInstanceState: Bundle?, + ) { super.onViewCreated(view, savedInstanceState) initUI() initExistingNoteUI() @@ -104,7 +110,7 @@ class ArkRecorderFragment: BaseEditNoteFragment() { stateToUI = { showState(it) }, - handleSideEffect = { handleSideEffect(it) } + handleSideEffect = { handleSideEffect(it) }, ) } } @@ -142,16 +148,27 @@ class ArkRecorderFragment: BaseEditNoteFragment() { private fun initUI() { var title = "" val etTitle = binding.edtTitle - val etTitleWatcher = object: TextWatcher { - override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {} + val etTitleWatcher = + object : TextWatcher { + override fun beforeTextChanged( + s: CharSequence?, + start: Int, + count: Int, + after: Int, + ) {} + + override fun onTextChanged( + s: CharSequence?, + start: Int, + before: Int, + count: Int, + ) { + title = s?.toString() ?: "" + } - override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) { - title = s?.toString() ?: "" + override fun afterTextChanged(s: Editable?) {} } - override fun afterTextChanged(s: Editable?) {} - } - binding.toolbar.ivRightActionIcon.setImageResource(R.drawable.ic_delete_note) binding.layoutAudioRecord.root.visible() @@ -204,21 +221,25 @@ class ArkRecorderFragment: BaseEditNoteFragment() { binding.layoutAudioRecord.animSoundWave.waveColor = ColorCode.brown binding.toolbar.ivRightActionIcon.setOnClickListener { - val note = VoiceNote( - title = title.ifEmpty { defaultNoteTitle }, - path = arkRecorderViewModel.getRecordingPath() - ) - CommonActionDialog(title = R.string.delete_note, + val note = + VoiceNote( + title = title.ifEmpty { defaultNoteTitle }, + path = arkRecorderViewModel.getRecordingPath(), + ) + CommonActionDialog( + title = R.string.delete_note, message = R.string.ark_memo_delete_warn, positiveText = R.string.action_delete, negativeText = R.string.ark_memo_cancel, isAlert = true, onPositiveClick = { - notesViewModel.onDeleteConfirmed(note){} - toast(requireContext(), getString(R.string.note_deleted)) - activity.onBackPressedDispatcher.onBackPressed() - }, onNegativeClicked = { - }).show(parentFragmentManager, CommonActionDialog.TAG) + notesViewModel.onDeleteConfirmed(note) {} + toast(requireContext(), getString(R.string.note_deleted)) + activity.onBackPressedDispatcher.onBackPressed() + }, + onNegativeClicked = { + }, + ).show(parentFragmentManager, CommonActionDialog.TAG) } note ?: binding.toolbar.ivRightActionIcon.gone() @@ -227,16 +248,18 @@ class ArkRecorderFragment: BaseEditNoteFragment() { private fun handleSideEffect(effect: RecorderSideEffect) { when (effect) { RecorderSideEffect.StartRecording -> { - val stopIcon = ResourcesCompat.getDrawable( - resources, - R.drawable.ic_record_ongoing, - null - ) + val stopIcon = + ResourcesCompat.getDrawable( + resources, + R.drawable.ic_record_ongoing, + null, + ) binding.layoutAudioView.animAudioPlaying.resetWave() ivRecord.setImageDrawable(stopIcon) - ivRecord.backgroundTintList = ColorStateList.valueOf( - ContextCompat.getColor(activity, R.color.warning_50) - ) + ivRecord.backgroundTintList = + ColorStateList.valueOf( + ContextCompat.getColor(activity, R.color.warning_50), + ) ivPauseResume.isEnabled = true enableSaveText(false) binding.layoutAudioRecord.animRecording.playAnimation() @@ -249,15 +272,17 @@ class ArkRecorderFragment: BaseEditNoteFragment() { binding.layoutAudioRecord.tvRecordGuide.gone() } is RecorderSideEffect.StopRecording -> { - val recordIcon = ResourcesCompat.getDrawable( - resources, - R.drawable.ic_record_big, - null - ) + val recordIcon = + ResourcesCompat.getDrawable( + resources, + R.drawable.ic_record_big, + null, + ) ivRecord.setImageDrawable(recordIcon) - ivRecord.backgroundTintList = ColorStateList.valueOf( - ContextCompat.getColor(activity, R.color.warning) - ) + ivRecord.backgroundTintList = + ColorStateList.valueOf( + ContextCompat.getColor(activity, R.color.warning), + ) ivPauseResume.isEnabled = false enableSaveText(true) showPauseIcon() @@ -273,11 +298,12 @@ class ArkRecorderFragment: BaseEditNoteFragment() { binding.layoutAudioRecord.tvDuration.setText(R.string.ark_memo_duration_default) } RecorderSideEffect.PauseRecording -> { - val resumeIcon = ResourcesCompat.getDrawable( - resources, - R.drawable.ic_play, - null - ) + val resumeIcon = + ResourcesCompat.getDrawable( + resources, + R.drawable.ic_play, + null, + ) ivPauseResume.setImageDrawable(resumeIcon) pauseOrResumeRecordingAnimation(false) } @@ -328,18 +354,19 @@ class ArkRecorderFragment: BaseEditNoteFragment() { } private fun showPauseIcon() { - val pauseIcon = ResourcesCompat.getDrawable( - resources, - R.drawable.ic_pause, - null - ) + val pauseIcon = + ResourcesCompat.getDrawable( + resources, + R.drawable.ic_pause, + null, + ) ivPauseResume.setImageDrawable(pauseIcon) } override fun createNewNote(): Note { return VoiceNote( title = binding.edtTitle.text.toString().ifEmpty { defaultNoteTitle }, - path = Path(getCurrentRecordingPath()) + path = Path(getCurrentRecordingPath()), ) } @@ -353,17 +380,23 @@ class ArkRecorderFragment: BaseEditNoteFragment() { val originalRecordingSize = note?.path?.toFile()?.length() ?: 0 val currentRecordingSize = arkRecorderViewModel.getRecordingPath().toFile().length() - (!originalTitle.equals(binding.edtTitle.text.toString()) - || (originalRecordingSize != currentRecordingSize && currentRecordingSize > 0)) + ( + !originalTitle.equals(binding.edtTitle.text.toString()) || + (originalRecordingSize != currentRecordingSize && currentRecordingSize > 0) + ) } else { - (binding.edtTitle.text.toString().isNotEmpty() - || arkRecorderViewModel.isRecordExisting()) + ( + binding.edtTitle.text.toString().isNotEmpty() || + arkRecorderViewModel.isRecordExisting() + ) } } override fun isContentEmpty(): Boolean { - return (!arkRecorderViewModel.isRecordExisting() - && ((note?.path?.toFile()?.length() ?: 0L) == 0L)) + return ( + !arkRecorderViewModel.isRecordExisting() && + ((note?.path?.toFile()?.length() ?: 0L) == 0L) + ) } private fun saveNote() { @@ -430,7 +463,6 @@ class ArkRecorderFragment: BaseEditNoteFragment() { mediaPlayViewModel.getDurationString { duration -> binding.layoutAudioView.tvDuration.text = duration } - } else { binding.layoutAudioView.root.gone() } @@ -441,7 +473,6 @@ class ArkRecorderFragment: BaseEditNoteFragment() { } private fun getCurrentRecordingPath(): String { - val tempRecordingPath = arkRecorderViewModel.getRecordingPath() return if (tempRecordingPath.toFile().length() > 0) { tempRecordingPath.toString() @@ -483,19 +514,22 @@ class ArkRecorderFragment: BaseEditNoteFragment() { } private fun onRecordButtonClick() { - if (!arkRecorderViewModel.isRecording() - && (arkRecorderViewModel.isRecordExisting() || - File(getCurrentRecordingPath()).length() > 0L)) { - - CommonActionDialog(title = R.string.dialog_replace_recording_title, + if (!arkRecorderViewModel.isRecording() && + ( + arkRecorderViewModel.isRecordExisting() || + File(getCurrentRecordingPath()).length() > 0L + ) + ) { + CommonActionDialog( + title = R.string.dialog_replace_recording_title, message = R.string.dialog_replace_recording_message, positiveText = R.string.dialog_replace_recording_positive_text, negativeText = R.string.discard, onPositiveClick = { binding.layoutAudioView.root.gone() - arkRecorderViewModel.onStartStopClick() }, - - onNegativeClicked = { } + arkRecorderViewModel.onStartStopClick() + }, + onNegativeClicked = { }, ).show(parentFragmentManager, CommonActionDialog.TAG) } else { arkRecorderViewModel.onStartStopClick() @@ -503,8 +537,12 @@ class ArkRecorderFragment: BaseEditNoteFragment() { } private fun showPermissionSettingToast() { - val snackBar = Snackbar.make(hostActivity.window.decorView.rootView, - getString(R.string.allow_permission_record_audio_msg), Snackbar.LENGTH_SHORT) + val snackBar = + Snackbar.make( + hostActivity.window.decorView.rootView, + getString(R.string.allow_permission_record_audio_msg), + Snackbar.LENGTH_SHORT, + ) snackBar.setAction(getString(R.string.settings)) { hostActivity.openAppSettings(settingActivityLauncher) @@ -517,11 +555,11 @@ class ArkRecorderFragment: BaseEditNoteFragment() { } companion object { - const val TAG = "voice-notes-fragment" - fun newInstance(note: VoiceNote? = null) = ArkRecorderFragment().apply { - note?.let { this.setNote(note) } - } + fun newInstance(note: VoiceNote? = null) = + ArkRecorderFragment().apply { + note?.let { this.setNote(note) } + } } -} \ No newline at end of file +} diff --git a/app/src/main/java/dev/arkbuilders/arkmemo/ui/fragments/BaseEditNoteFragment.kt b/app/src/main/java/dev/arkbuilders/arkmemo/ui/fragments/BaseEditNoteFragment.kt index 50df0e47..20f5f38f 100644 --- a/app/src/main/java/dev/arkbuilders/arkmemo/ui/fragments/BaseEditNoteFragment.kt +++ b/app/src/main/java/dev/arkbuilders/arkmemo/ui/fragments/BaseEditNoteFragment.kt @@ -18,8 +18,7 @@ import dev.arkbuilders.arkmemo.utils.visible import java.util.Calendar import java.util.Locale -abstract class BaseEditNoteFragment: BaseFragment() { - +abstract class BaseEditNoteFragment : BaseFragment() { lateinit var binding: FragmentEditNotesBinding val notesViewModel: NotesViewModel by activityViewModels() val hostActivity by lazy { activity as MainActivity } @@ -27,23 +26,33 @@ abstract class BaseEditNoteFragment: BaseFragment() { override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, - savedInstanceState: Bundle? + savedInstanceState: Bundle?, ): View? { binding = FragmentEditNotesBinding.inflate(layoutInflater) return binding.root } - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + + override fun onViewCreated( + view: View, + savedInstanceState: Bundle?, + ) { super.onViewCreated(view, savedInstanceState) binding.tvDescription.setOnClickListener { if (binding.editTextDescription.visibility == View.GONE) { binding.tvDescription.setCompoundDrawablesWithIntrinsicBounds( - 0, 0, R.drawable.ic_chevron_down, 0 + 0, + 0, + R.drawable.ic_chevron_down, + 0, ) binding.editTextDescription.visibility = View.VISIBLE } else { binding.tvDescription.setCompoundDrawablesWithIntrinsicBounds( - 0, 0, R.drawable.ic_chevron_right, 0 + 0, + 0, + R.drawable.ic_chevron_right, + 0, ) binding.editTextDescription.visibility = View.GONE } @@ -70,7 +79,6 @@ abstract class BaseEditNoteFragment: BaseFragment() { if (this is EditTextNotesFragment) { binding.groupTextControls.visible() } - } else { binding.layoutGraphicsControl.root.visible() binding.groupTextControls.gone() @@ -92,48 +100,57 @@ abstract class BaseEditNoteFragment: BaseFragment() { val calendar = Calendar.getInstance(Locale.ENGLISH) calendar.timeInMillis = getCurrentNote().resource?.modified?.toMillis() ?: System.currentTimeMillis() - val lastModifiedTime = DateFormat.format( - "dd MMM yyyy', 'hh:mm aa", calendar).toString() + val lastModifiedTime = + DateFormat.format( + "dd MMM yyyy', 'hh:mm aa", + calendar, + ).toString() binding.tvLastModified.text = getString(R.string.note_last_modified_time, lastModifiedTime) } } - private fun showSaveNoteDialog(needStopRecording: Boolean = false, - onDiscard: (needClearResource: Boolean) -> Unit) { - val saveNoteDialog = CommonActionDialog( - title = R.string.dialog_save_note_title, - message = R.string.dialog_save_note_message, - positiveText = R.string.save, - negativeText = R.string.discard, - isAlert = false, - onPositiveClick = { - if (needStopRecording) { - (this as? ArkRecorderFragment)?.stopIfRecording() - } - notesViewModel.onSaveClick(createNewNote()) { show -> - hostActivity.showProgressBar(show) - } - }, - onNegativeClicked = { - onDiscard.invoke(getCurrentNote().resource?.id == null) - hostActivity.onBackPressedDispatcher.onBackPressed() - }) + private fun showSaveNoteDialog( + needStopRecording: Boolean = false, + onDiscard: (needClearResource: Boolean) -> Unit, + ) { + val saveNoteDialog = + CommonActionDialog( + title = R.string.dialog_save_note_title, + message = R.string.dialog_save_note_message, + positiveText = R.string.save, + negativeText = R.string.discard, + isAlert = false, + onPositiveClick = { + if (needStopRecording) { + (this as? ArkRecorderFragment)?.stopIfRecording() + } + notesViewModel.onSaveClick(createNewNote()) { show -> + hostActivity.showProgressBar(show) + } + }, + onNegativeClicked = { + onDiscard.invoke(getCurrentNote().resource?.id == null) + hostActivity.onBackPressedDispatcher.onBackPressed() + }, + ) saveNoteDialog.show(parentFragmentManager, CommonActionDialog.TAG) } fun showDeleteNoteDialog(note: Note) { CommonActionDialog( title = R.string.delete_note, - message = R.string.ark_memo_delete_warn , + message = R.string.ark_memo_delete_warn, positiveText = R.string.action_delete, negativeText = R.string.ark_memo_cancel, isAlert = true, onPositiveClick = { - notesViewModel.onDeleteConfirmed(note){} + notesViewModel.onDeleteConfirmed(note) {} hostActivity.onBackPressedDispatcher.onBackPressed() toast(requireContext(), getString(R.string.note_deleted)) - }, onNegativeClicked = { - }).show(parentFragmentManager, CommonActionDialog.TAG) + }, + onNegativeClicked = { + }, + ).show(parentFragmentManager, CommonActionDialog.TAG) } private fun handleBackPressed() { @@ -141,7 +158,7 @@ abstract class BaseEditNoteFragment: BaseFragment() { val isRecording = recordFragment?.isRecordingVoiceNote() ?: false if (isContentChanged() && !isContentEmpty() || isRecording) { - showSaveNoteDialog (needStopRecording = isRecording) { needClearResource -> + showSaveNoteDialog(needStopRecording = isRecording) { needClearResource -> if (needClearResource) { recordFragment?.stopIfRecording() recordFragment?.deleteTempFile() @@ -157,7 +174,10 @@ abstract class BaseEditNoteFragment: BaseFragment() { } abstract fun createNewNote(): Note + abstract fun getCurrentNote(): Note + abstract fun isContentChanged(): Boolean + abstract fun isContentEmpty(): Boolean -} \ No newline at end of file +} diff --git a/app/src/main/java/dev/arkbuilders/arkmemo/ui/fragments/BaseFragment.kt b/app/src/main/java/dev/arkbuilders/arkmemo/ui/fragments/BaseFragment.kt index 0dbe84ac..078457d9 100644 --- a/app/src/main/java/dev/arkbuilders/arkmemo/ui/fragments/BaseFragment.kt +++ b/app/src/main/java/dev/arkbuilders/arkmemo/ui/fragments/BaseFragment.kt @@ -2,6 +2,6 @@ package dev.arkbuilders.arkmemo.ui.fragments import androidx.fragment.app.Fragment -abstract class BaseFragment: Fragment() { +abstract class BaseFragment : Fragment() { abstract fun onBackPressed() -} \ No newline at end of file +} diff --git a/app/src/main/java/dev/arkbuilders/arkmemo/ui/fragments/EditGraphicNotesFragment.kt b/app/src/main/java/dev/arkbuilders/arkmemo/ui/fragments/EditGraphicNotesFragment.kt index 58fe3eee..bb733d73 100644 --- a/app/src/main/java/dev/arkbuilders/arkmemo/ui/fragments/EditGraphicNotesFragment.kt +++ b/app/src/main/java/dev/arkbuilders/arkmemo/ui/fragments/EditGraphicNotesFragment.kt @@ -38,21 +38,30 @@ import dev.arkbuilders.arkmemo.utils.setDrawableColor import dev.arkbuilders.arkmemo.utils.visible @AndroidEntryPoint -class EditGraphicNotesFragment: BaseEditNoteFragment() { - +class EditGraphicNotesFragment : BaseEditNoteFragment() { private val graphicNotesViewModel: GraphicNotesViewModel by viewModels() private var note = GraphicNote() private val colorBrushes by lazy { listOf( - BrushColorBlack, BrushColorGrey, BrushColorRed, - BrushColorOrange, BrushColorGreen, BrushColorBlue, BrushColorPurple) + BrushColorBlack, + BrushColorGrey, + BrushColorRed, + BrushColorOrange, + BrushColorGreen, + BrushColorBlue, + BrushColorPurple, + ) } private val sizeBrushes by lazy { listOf( - BrushSizeTiny, BrushSizeSmall, BrushSizeMedium, - BrushSizeLarge, BrushSizeHuge) + BrushSizeTiny, + BrushSizeSmall, + BrushSizeMedium, + BrushSizeLarge, + BrushSizeHuge, + ) } override fun onCreate(savedInstanceState: Bundle?) { @@ -65,26 +74,39 @@ class EditGraphicNotesFragment: BaseEditNoteFragment() { } } - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + override fun onViewCreated( + view: View, + savedInstanceState: Bundle?, + ) { super.onViewCreated(view, savedInstanceState) var title = note.title val notesCanvas = binding.notesCanvas val btnSave = binding.toolbar.tvRightActionText val noteTitle = binding.edtTitle - val noteTitleChangeListener = object: TextWatcher { - override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {} - - override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) { - title = s?.toString() ?: "" - if (title.isEmpty()) { - binding.edtTitle.hint = getString(R.string.hint_new_graphical_note) + val noteTitleChangeListener = + object : TextWatcher { + override fun beforeTextChanged( + s: CharSequence?, + start: Int, + count: Int, + after: Int, + ) {} + + override fun onTextChanged( + s: CharSequence?, + start: Int, + before: Int, + count: Int, + ) { + title = s?.toString() ?: "" + if (title.isEmpty()) { + binding.edtTitle.hint = getString(R.string.hint_new_graphical_note) + } + enableSaveText(isContentChanged() && !isContentEmpty()) } - enableSaveText(isContentChanged() && !isContentEmpty()) - } - - override fun afterTextChanged(s: Editable?) {} - } + override fun afterTextChanged(s: Editable?) {} + } hostActivity.title = getString(R.string.edit_note) hostActivity.supportActionBar?.setDisplayHomeAsUpEnabled(true) @@ -93,17 +115,19 @@ class EditGraphicNotesFragment: BaseEditNoteFragment() { noteTitle.setText(title) noteTitle.addTextChangedListener(noteTitleChangeListener) notesCanvas.isVisible = true - notesCanvas.setViewModel(graphicNotesViewModel.apply { - colorBrushes.firstOrNull { it.isSelected }?.let { color -> - val colorCode = color.getColorCode() - setPaintColor(colorCode) - binding.layoutGraphicsControl.tvBrushColor.setDrawableColor(colorCode) - } + notesCanvas.setViewModel( + graphicNotesViewModel.apply { + colorBrushes.firstOrNull { it.isSelected }?.let { color -> + val colorCode = color.getColorCode() + setPaintColor(colorCode) + binding.layoutGraphicsControl.tvBrushColor.setDrawableColor(colorCode) + } - sizeBrushes.firstOrNull { it.isSelected }?.let { - setBrushSize(it.getBrushSize()) - } - }) + sizeBrushes.firstOrNull { it.isSelected }?.let { + setBrushSize(it.getBrushSize()) + } + }, + ) btnSave.setOnClickListener { val note = createNewNote() notesViewModel.onSaveClick(note, parentNote = this.note) { show -> @@ -129,7 +153,7 @@ class EditGraphicNotesFragment: BaseEditNoteFragment() { title = binding.edtTitle.text.toString(), svg = graphicNotesViewModel.svg(), description = binding.editTextDescription.text.toString(), - resource = note.resource + resource = note.resource, ) } @@ -141,8 +165,8 @@ class EditGraphicNotesFragment: BaseEditNoteFragment() { val originalPaths = note.svg?.getPaths() ?: emptyList() val newPaths = graphicNotesViewModel.svg().getPaths() - return note.title != binding.edtTitle.text.toString() - || ((newPaths.size != originalPaths.size) || (!newPaths.containsAll(originalPaths))) + return note.title != binding.edtTitle.text.toString() || + ((newPaths.size != originalPaths.size) || (!newPaths.containsAll(originalPaths))) } override fun isContentEmpty(): Boolean { @@ -203,28 +227,34 @@ class EditGraphicNotesFragment: BaseEditNoteFragment() { } private fun showBrushSizeList(isEraseMode: Boolean = false) { - - val brushSizeAdapter = BrushAdapter( - attributes = sizeBrushes.apply { - val selectedIndex = this.indexOfFirst { it.isSelected } - if (selectedIndex == -1) { - sizeBrushes[0].isSelected = true - } - }, - onItemClick = { attribute, pos -> - Log.v(TAG, "onSizeSelected: " + attribute) - graphicNotesViewModel.setBrushSize((attribute as BrushSize).getBrushSize()) - graphicNotesViewModel.setEraseMode(isEraseMode) - } - ) + val brushSizeAdapter = + BrushAdapter( + attributes = + sizeBrushes.apply { + val selectedIndex = this.indexOfFirst { it.isSelected } + if (selectedIndex == -1) { + sizeBrushes[0].isSelected = true + } + }, + onItemClick = { attribute, pos -> + Log.v(TAG, "onSizeSelected: " + attribute) + graphicNotesViewModel.setBrushSize((attribute as BrushSize).getBrushSize()) + graphicNotesViewModel.setEraseMode(isEraseMode) + }, + ) val layoutMgr = LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false) binding.layoutGraphicsControl.layoutSizeChooser.rvBrushSizes.apply { while (this.itemDecorationCount > 0) { this.removeItemDecorationAt(0) } - addItemDecoration(EqualSpacingItemDecoration(context.resources.getDimensionPixelSize( - R.dimen.brush_size_item_margin), EqualSpacingItemDecoration.HORIZONTAL) + addItemDecoration( + EqualSpacingItemDecoration( + context.resources.getDimensionPixelSize( + R.dimen.brush_size_item_margin, + ), + EqualSpacingItemDecoration.HORIZONTAL, + ), ) this.isNestedScrollingEnabled = false layoutManager = layoutMgr @@ -233,30 +263,36 @@ class EditGraphicNotesFragment: BaseEditNoteFragment() { } private fun showBrushColorList() { - - val brushColorAdapter = BrushAdapter( - attributes = colorBrushes.apply { - val selectedIndex = this.indexOfFirst { it.isSelected } - if (selectedIndex == -1) { - colorBrushes[0].isSelected = true - } - }, - onItemClick = { attribute, pos -> - Log.v(TAG, "onColorSelected: " + attribute) - (attribute as BrushColor).getColorCode().let { colorCode -> - graphicNotesViewModel.setPaintColor(colorCode) - binding.layoutGraphicsControl.tvBrushColor.setDrawableColor(colorCode) - } - } - ) + val brushColorAdapter = + BrushAdapter( + attributes = + colorBrushes.apply { + val selectedIndex = this.indexOfFirst { it.isSelected } + if (selectedIndex == -1) { + colorBrushes[0].isSelected = true + } + }, + onItemClick = { attribute, pos -> + Log.v(TAG, "onColorSelected: " + attribute) + (attribute as BrushColor).getColorCode().let { colorCode -> + graphicNotesViewModel.setPaintColor(colorCode) + binding.layoutGraphicsControl.tvBrushColor.setDrawableColor(colorCode) + } + }, + ) val layoutMgr = LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false) binding.layoutGraphicsControl.layoutColorChooser.rvBrushColors.apply { while (this.itemDecorationCount > 0) { this.removeItemDecorationAt(0) } - addItemDecoration(EqualSpacingItemDecoration(context.resources.getDimensionPixelSize( - R.dimen.brush_color_item_margin), EqualSpacingItemDecoration.HORIZONTAL) + addItemDecoration( + EqualSpacingItemDecoration( + context.resources.getDimensionPixelSize( + R.dimen.brush_color_item_margin, + ), + EqualSpacingItemDecoration.HORIZONTAL, + ), ) this.isNestedScrollingEnabled = false layoutManager = layoutMgr @@ -279,10 +315,12 @@ class EditGraphicNotesFragment: BaseEditNoteFragment() { fun newInstance() = EditGraphicNotesFragment() - fun newInstance(note: GraphicNote) = EditGraphicNotesFragment().apply { - arguments = Bundle().apply { - putParcelable(GRAPHICAL_NOTE_KEY, note) + fun newInstance(note: GraphicNote) = + EditGraphicNotesFragment().apply { + arguments = + Bundle().apply { + putParcelable(GRAPHICAL_NOTE_KEY, note) + } } - } } -} \ No newline at end of file +} diff --git a/app/src/main/java/dev/arkbuilders/arkmemo/ui/fragments/EditTextNotesFragment.kt b/app/src/main/java/dev/arkbuilders/arkmemo/ui/fragments/EditTextNotesFragment.kt index de82b396..3c9b2641 100644 --- a/app/src/main/java/dev/arkbuilders/arkmemo/ui/fragments/EditTextNotesFragment.kt +++ b/app/src/main/java/dev/arkbuilders/arkmemo/ui/fragments/EditTextNotesFragment.kt @@ -23,49 +23,55 @@ import dev.arkbuilders.arkmemo.utils.observeSaveResult import java.lang.StringBuilder @AndroidEntryPoint -class EditTextNotesFragment: BaseEditNoteFragment() { - +class EditTextNotesFragment : BaseEditNoteFragment() { private var note = TextNote() private var noteStr: String? = null - private val pasteNoteClickListener = View.OnClickListener { - requireContext().getTextFromClipBoard(view) { clipBoardText -> - if (clipBoardText != null) { - val newTextBuilder = StringBuilder() - val cursorPos = binding.editNote.selectionStart - val noteContent = binding.editNote.text.toString() - - val newCursorPos = if (cursorPos < 0) { - (clipBoardText.length + noteContent.length - 1).coerceAtLeast(0) + private val pasteNoteClickListener = + View.OnClickListener { + requireContext().getTextFromClipBoard(view) { clipBoardText -> + if (clipBoardText != null) { + val newTextBuilder = StringBuilder() + val cursorPos = binding.editNote.selectionStart + val noteContent = binding.editNote.text.toString() + + val newCursorPos = + if (cursorPos < 0) { + (clipBoardText.length + noteContent.length - 1).coerceAtLeast(0) + } else { + clipBoardText.length + cursorPos + } + try { + newTextBuilder.append(noteContent.insertStringAtPosition(clipBoardText, cursorPos)) + } catch (e: IndexOutOfBoundsException) { + Log.e(TAG, "pasteNoteClickListener exception: ${e.message}") + newTextBuilder.append(noteContent).append(clipBoardText) + } + + binding.editNote.setText(newTextBuilder.toString()) + binding.editNote.setSelection(newCursorPos) } else { - clipBoardText.length + cursorPos + Toast.makeText( + requireContext(), + getString(R.string.nothing_to_paste), + Toast.LENGTH_SHORT, + ).show() } - try { - newTextBuilder.append(noteContent.insertStringAtPosition(clipBoardText, cursorPos)) - } catch (e: IndexOutOfBoundsException) { - Log.e(TAG, "pasteNoteClickListener exception: ${e.message}") - newTextBuilder.append(noteContent).append(clipBoardText) - } - - binding.editNote.setText(newTextBuilder.toString()) - binding.editNote.setSelection(newCursorPos) } - else Toast.makeText(requireContext(), - getString(R.string.nothing_to_paste), Toast.LENGTH_SHORT).show() } - } - private val windowFocusedListener = OnWindowFocusChangeListener { - if (it) { - observeClipboardContent() + private val windowFocusedListener = + OnWindowFocusChangeListener { + if (it) { + observeClipboardContent() + } } - } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) notesViewModel.init {} observeSaveResult(notesViewModel.getSaveNoteResultLiveData()) - if(arguments != null) { + if (arguments != null) { requireArguments().getParcelableCompat(NOTE_KEY, TextNote::class.java)?.let { note = it } @@ -73,24 +79,38 @@ class EditTextNotesFragment: BaseEditNoteFragment() { } } - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + override fun onViewCreated( + view: View, + savedInstanceState: Bundle?, + ) { super.onViewCreated(view, savedInstanceState) var title: String val noteTitle = binding.edtTitle val editNote = binding.editNote - val noteTitleChangeListener = object: TextWatcher { - override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {} - - override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) { - title = s?.toString() ?: "" - if (title.isEmpty()) { - binding.edtTitle.hint = getString(R.string.hint_new_text_note) + val noteTitleChangeListener = + object : TextWatcher { + override fun beforeTextChanged( + s: CharSequence?, + start: Int, + count: Int, + after: Int, + ) {} + + override fun onTextChanged( + s: CharSequence?, + start: Int, + before: Int, + count: Int, + ) { + title = s?.toString() ?: "" + if (title.isEmpty()) { + binding.edtTitle.hint = getString(R.string.hint_new_text_note) + } + enableSaveText(isContentChanged() && !isContentEmpty()) } - enableSaveText(isContentChanged() && !isContentEmpty()) - } - override fun afterTextChanged(s: Editable?) {} - } + override fun afterTextChanged(s: Editable?) {} + } hostActivity.title = getString(R.string.edit_note) hostActivity.supportActionBar?.setDisplayHomeAsUpEnabled(true) @@ -113,8 +133,9 @@ class EditTextNotesFragment: BaseEditNoteFragment() { editNote.requestFocus() editNote.setText(this.note.text) - if(noteStr != null) + if (noteStr != null) { editNote.setText(noteStr) + } binding.tvSave.setOnClickListener { notesViewModel.onSaveClick(createNewNote(), parentNote = note) { show -> @@ -137,8 +158,8 @@ class EditTextNotesFragment: BaseEditNoteFragment() { } override fun isContentChanged(): Boolean { - return note.title != binding.edtTitle.text.toString() - || note.text != binding.editNote.text.toString() + return note.title != binding.edtTitle.text.toString() || + note.text != binding.editNote.text.toString() } override fun isContentEmpty(): Boolean { @@ -155,7 +176,7 @@ class EditTextNotesFragment: BaseEditNoteFragment() { title = binding.edtTitle.text.toString(), description = binding.editTextDescription.text.toString(), text = binding.editNote.text.toString(), - resource = note.resource + resource = note.resource, ) } @@ -186,21 +207,25 @@ class EditTextNotesFragment: BaseEditNoteFragment() { } } - companion object{ + companion object { const val TAG = "EditTextNotesFragment" private const val NOTE_STRING_KEY = "note string" private const val NOTE_KEY = "note key" - fun newInstance(note: String) = EditTextNotesFragment().apply{ - arguments = Bundle().apply { - putString(NOTE_STRING_KEY, note) + fun newInstance(note: String) = + EditTextNotesFragment().apply { + arguments = + Bundle().apply { + putString(NOTE_STRING_KEY, note) + } } - } - fun newInstance(note: TextNote) = EditTextNotesFragment().apply{ - arguments = Bundle().apply{ - putParcelable(NOTE_KEY, note) + fun newInstance(note: TextNote) = + EditTextNotesFragment().apply { + arguments = + Bundle().apply { + putParcelable(NOTE_KEY, note) + } } - } } } diff --git a/app/src/main/java/dev/arkbuilders/arkmemo/ui/fragments/NotesFragment.kt b/app/src/main/java/dev/arkbuilders/arkmemo/ui/fragments/NotesFragment.kt index 33c3e67b..ff076d5f 100644 --- a/app/src/main/java/dev/arkbuilders/arkmemo/ui/fragments/NotesFragment.kt +++ b/app/src/main/java/dev/arkbuilders/arkmemo/ui/fragments/NotesFragment.kt @@ -37,10 +37,8 @@ import dev.arkbuilders.arkmemo.utils.visible import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.launch - @AndroidEntryPoint -class NotesFragment: BaseFragment() { - +class NotesFragment : BaseFragment() { private lateinit var binding: FragmentHomeBinding private val activity: MainActivity by lazy { @@ -56,68 +54,81 @@ class NotesFragment: BaseFragment() { private var playingAudioPosition = -1 private var lastNoteItemPosition = 0 - private val newTextNoteClickListener = View.OnClickListener { - onFloatingActionButtonClicked() - } + private val newTextNoteClickListener = + View.OnClickListener { + onFloatingActionButtonClicked() + } - private val pasteNoteClickListener = View.OnClickListener { - requireContext().getTextFromClipBoard(view) { clipBoardText -> - if (clipBoardText != null) { - activity.fragment = EditTextNotesFragment.newInstance(clipBoardText) - activity.replaceFragment(activity.fragment, EditTextNotesFragment.TAG) + private val pasteNoteClickListener = + View.OnClickListener { + requireContext().getTextFromClipBoard(view) { clipBoardText -> + if (clipBoardText != null) { + activity.fragment = EditTextNotesFragment.newInstance(clipBoardText) + activity.replaceFragment(activity.fragment, EditTextNotesFragment.TAG) + } else { + Toast.makeText( + requireContext(), + getString(R.string.nothing_to_paste), + Toast.LENGTH_SHORT, + ).show() + } } - else Toast.makeText(requireContext(), getString(R.string.nothing_to_paste), - Toast.LENGTH_SHORT).show() } - } private var mItemTouchHelper: ItemTouchHelper? = null - private val mItemTouchCallback = object : ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.LEFT) { - override fun onMove( - recyclerView: RecyclerView, - viewHolder: RecyclerView.ViewHolder, - target: RecyclerView.ViewHolder, - ): Boolean { - return false - } - - override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) { - val deletePosition = viewHolder.bindingAdapterPosition - val noteToDelete = notesAdapter?.getNotes()?.getOrNull(deletePosition)?.apply { - pendingForDelete = true - } ?: return - - val noteViewHolder = viewHolder as? NotesListAdapter.NoteViewHolder - noteViewHolder?.isSwiping = true - - binding.rvPinnedNotes.adapter?.notifyItemChanged(deletePosition) - - CommonActionDialog(title = R.string.delete_note, - message = R.string.ark_memo_delete_warn, - positiveText = R.string.action_delete, - negativeText = R.string.ark_memo_cancel, - isAlert = true, - onPositiveClick = { - noteViewHolder?.isSwiping = false - notesViewModel.onDeleteConfirmed(noteToDelete) { - notesAdapter?.removeNote(noteToDelete) - toast(requireContext(), getString(R.string.note_deleted)) - binding.rvPinnedNotes.adapter?.notifyItemRemoved(deletePosition) - } + private val mItemTouchCallback = + object : ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.LEFT) { + override fun onMove( + recyclerView: RecyclerView, + viewHolder: RecyclerView.ViewHolder, + target: RecyclerView.ViewHolder, + ): Boolean { + return false + } - }, onNegativeClicked = { - noteViewHolder?.isSwiping = false - noteToDelete.pendingForDelete = false - binding.rvPinnedNotes.adapter?.notifyItemChanged(deletePosition) - }, onCloseClicked = { - noteViewHolder?.isSwiping = false - noteToDelete.pendingForDelete = false - binding.rvPinnedNotes.adapter?.notifyItemChanged(deletePosition) - }).show(childFragmentManager, CommonActionDialog.TAG) + override fun onSwiped( + viewHolder: RecyclerView.ViewHolder, + direction: Int, + ) { + val deletePosition = viewHolder.bindingAdapterPosition + val noteToDelete = + notesAdapter?.getNotes()?.getOrNull(deletePosition)?.apply { + pendingForDelete = true + } ?: return + + val noteViewHolder = viewHolder as? NotesListAdapter.NoteViewHolder + noteViewHolder?.isSwiping = true + + binding.rvPinnedNotes.adapter?.notifyItemChanged(deletePosition) + + CommonActionDialog( + title = R.string.delete_note, + message = R.string.ark_memo_delete_warn, + positiveText = R.string.action_delete, + negativeText = R.string.ark_memo_cancel, + isAlert = true, + onPositiveClick = { + noteViewHolder?.isSwiping = false + notesViewModel.onDeleteConfirmed(noteToDelete) { + notesAdapter?.removeNote(noteToDelete) + toast(requireContext(), getString(R.string.note_deleted)) + binding.rvPinnedNotes.adapter?.notifyItemRemoved(deletePosition) + } + }, + onNegativeClicked = { + noteViewHolder?.isSwiping = false + noteToDelete.pendingForDelete = false + binding.rvPinnedNotes.adapter?.notifyItemChanged(deletePosition) + }, + onCloseClicked = { + noteViewHolder?.isSwiping = false + noteToDelete.pendingForDelete = false + binding.rvPinnedNotes.adapter?.notifyItemChanged(deletePosition) + }, + ).show(childFragmentManager, CommonActionDialog.TAG) + } } - } - override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding = FragmentHomeBinding.inflate(layoutInflater) @@ -126,13 +137,16 @@ class NotesFragment: BaseFragment() { override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, - savedInstanceState: Bundle? + savedInstanceState: Bundle?, ): View { binding = FragmentHomeBinding.inflate(layoutInflater) return binding.root } - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + override fun onViewCreated( + view: View, + savedInstanceState: Bundle?, + ) { super.onViewCreated(view, savedInstanceState) activity.title = getString(R.string.app_name) activity.supportActionBar?.setDisplayHomeAsUpEnabled(false) @@ -146,9 +160,11 @@ class NotesFragment: BaseFragment() { binding.pbLoading.visible() notesViewModel.apply { - init { readAllNotes { - onNotesLoaded(it) - } } + init { + readAllNotes { + onNotesLoaded(it) + } + } } initSearch() } @@ -170,12 +186,13 @@ class NotesFragment: BaseFragment() { } notesAdapter?.updateData(notes, fromSearch = true, keyword = text.toString()) - //When search text is cleared, restore previous note item position in the list + // When search text is cleared, restore previous note item position in the list if (text.toString().isEmpty()) { binding.rvPinnedNotes.layoutManager?.scrollToPosition(lastNoteItemPosition) } } - }) + }, + ) binding.edtSearch.setOnFocusChangeListener { _, hasFocus -> if (hasFocus) { @@ -187,37 +204,36 @@ class NotesFragment: BaseFragment() { private fun onNotesLoaded(notes: List) { binding.pbLoading.gone() if (notesAdapter == null) { - notesAdapter = NotesListAdapter( - notes.toMutableList(), - onPlayPauseClick = { path, pos, onStop -> - playingAudioPath = path - if (playingAudioPosition >= 0) { - refreshVoiceNoteItem(playingAudioPosition) - } - - if (playingAudioPosition >= 0 && playingAudioPosition != pos) { - //Another Voice note is being played compared to the previously played one - markResetVoiceNotePlayback(playingAudioPosition) - } - - playingAudioPosition = pos ?: -1 - - if (arkMediaPlayerViewModel.isPlaying()) { - mItemTouchHelper?.attachToRecyclerView(binding.rvPinnedNotes) - markWaitToBeResumed(playingAudioPosition) - } else { - mItemTouchHelper?.attachToRecyclerView(null) - } - - arkMediaPlayerViewModel.onPlayOrPauseClick(path, pos, onStop) - }, - onThumbPrepare = { graphicNote, noteCanvas -> - val tempNoteViewModel: GraphicNotesViewModel by viewModels() - noteCanvas.setViewModel(viewModel = tempNoteViewModel) - - } - ) - + notesAdapter = + NotesListAdapter( + notes.toMutableList(), + onPlayPauseClick = { path, pos, onStop -> + playingAudioPath = path + if (playingAudioPosition >= 0) { + refreshVoiceNoteItem(playingAudioPosition) + } + + if (playingAudioPosition >= 0 && playingAudioPosition != pos) { + // Another Voice note is being played compared to the previously played one + markResetVoiceNotePlayback(playingAudioPosition) + } + + playingAudioPosition = pos ?: -1 + + if (arkMediaPlayerViewModel.isPlaying()) { + mItemTouchHelper?.attachToRecyclerView(binding.rvPinnedNotes) + markWaitToBeResumed(playingAudioPosition) + } else { + mItemTouchHelper?.attachToRecyclerView(null) + } + + arkMediaPlayerViewModel.onPlayOrPauseClick(path, pos, onStop) + }, + onThumbPrepare = { graphicNote, noteCanvas -> + val tempNoteViewModel: GraphicNotesViewModel by viewModels() + noteCanvas.setViewModel(viewModel = tempNoteViewModel) + }, + ) } else { notesAdapter?.setNotes(notes) } @@ -229,12 +245,13 @@ class NotesFragment: BaseFragment() { binding.rvPinnedNotes.apply { this.layoutManager = layoutManager this.adapter = notesAdapter - this.itemAnimator = object : DefaultItemAnimator() { - override fun canReuseUpdatedViewHolder(viewHolder: RecyclerView.ViewHolder): Boolean { - val isSwiping = (viewHolder as? NotesListAdapter.NoteViewHolder)?.isSwiping ?: false - return !isSwiping + this.itemAnimator = + object : DefaultItemAnimator() { + override fun canReuseUpdatedViewHolder(viewHolder: RecyclerView.ViewHolder): Boolean { + val isSwiping = (viewHolder as? NotesListAdapter.NoteViewHolder)?.isSwiping ?: false + return !isSwiping + } } - } } mItemTouchHelper = ItemTouchHelper(mItemTouchCallback) @@ -352,7 +369,6 @@ class NotesFragment: BaseFragment() { binding.tvPaste.setOnClickListener(pasteNoteClickListener) binding.tvInstructions.setOnClickListener { } - } private fun observeClipboardContent() { @@ -386,17 +402,19 @@ class NotesFragment: BaseFragment() { if (showingFloatingButtons) { binding.fabNewAction.shrink() binding.fabNewAction.icon = ContextCompat.getDrawable(activity, R.drawable.ic_add) - binding.fabNewAction.backgroundTintList = ColorStateList.valueOf( - ContextCompat.getColor(activity, R.color.warning) - ) + binding.fabNewAction.backgroundTintList = + ColorStateList.valueOf( + ContextCompat.getColor(activity, R.color.warning), + ) binding.groupFabActions.gone() showingFloatingButtons = false } else { binding.fabNewAction.extend() binding.fabNewAction.icon = ContextCompat.getDrawable(activity, R.drawable.ic_close) - binding.fabNewAction.backgroundTintList = ColorStateList.valueOf( - ContextCompat.getColor(activity, R.color.warning_100) - ) + binding.fabNewAction.backgroundTintList = + ColorStateList.valueOf( + ContextCompat.getColor(activity, R.color.warning_100), + ) binding.groupFabActions.visible() showingFloatingButtons = true } diff --git a/app/src/main/java/dev/arkbuilders/arkmemo/ui/fragments/SettingsFragment.kt b/app/src/main/java/dev/arkbuilders/arkmemo/ui/fragments/SettingsFragment.kt index 1d3d627c..ca889ad0 100644 --- a/app/src/main/java/dev/arkbuilders/arkmemo/ui/fragments/SettingsFragment.kt +++ b/app/src/main/java/dev/arkbuilders/arkmemo/ui/fragments/SettingsFragment.kt @@ -13,10 +13,13 @@ import dev.arkbuilders.arkmemo.utils.gone import dev.arkbuilders.arkmemo.utils.openLink import dev.arkbuilders.arkmemo.utils.visible -open class SettingsFragment: Fragment(R.layout.fragment_settings) { - +open class SettingsFragment : Fragment(R.layout.fragment_settings) { val binding by viewBinding(FragmentSettingsBinding::bind) - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + + override fun onViewCreated( + view: View, + savedInstanceState: Bundle?, + ) { super.onViewCreated(view, savedInstanceState) binding.toolbarCustom.ivBack.setOnClickListener { @@ -31,7 +34,6 @@ open class SettingsFragment: Fragment(R.layout.fragment_settings) { binding.tvAppVersion.text = getString(R.string.setting_app_version, BuildConfig.VERSION_NAME) initSettingActions() - } private fun initSettingActions() { @@ -60,7 +62,8 @@ open class SettingsFragment: Fragment(R.layout.fragment_settings) { walletAddress = "bc1qx8n9r4uwpgrhgnamt2uew53lmrxd8tuevp7lv5", title = getString(R.string.setting_donate_btc), onPositiveClick = { - }).show(childFragmentManager, CommonActionDialog.TAG) + }, + ).show(childFragmentManager, CommonActionDialog.TAG) } binding.tvDonateEth.setOnClickListener { @@ -68,7 +71,8 @@ open class SettingsFragment: Fragment(R.layout.fragment_settings) { walletAddress = "0x9765C5aC38175BFbd2dC7a840b63e50762B80a1b", title = getString(R.string.setting_donate_eth), onPositiveClick = { - }).show(childFragmentManager, CommonActionDialog.TAG) + }, + ).show(childFragmentManager, CommonActionDialog.TAG) } binding.tvDiscoverIssues.setOnClickListener { @@ -78,4 +82,4 @@ open class SettingsFragment: Fragment(R.layout.fragment_settings) { binding.tvBounties.setOnClickListener { } } -} \ No newline at end of file +} diff --git a/app/src/main/java/dev/arkbuilders/arkmemo/ui/viewmodels/ArkMediaPlayerViewModel.kt b/app/src/main/java/dev/arkbuilders/arkmemo/ui/viewmodels/ArkMediaPlayerViewModel.kt index 9e2e2635..53321c48 100644 --- a/app/src/main/java/dev/arkbuilders/arkmemo/ui/viewmodels/ArkMediaPlayerViewModel.kt +++ b/app/src/main/java/dev/arkbuilders/arkmemo/ui/viewmodels/ArkMediaPlayerViewModel.kt @@ -6,8 +6,8 @@ import androidx.lifecycle.viewModelScope import dagger.hilt.android.lifecycle.HiltViewModel import dev.arkbuilders.arkmemo.media.ArkMediaPlayer import dev.arkbuilders.arkmemo.ui.views.WaveView -import dev.arkbuilders.arkmemo.utils.launchPeriodicAsync import dev.arkbuilders.arkmemo.utils.extractDuration +import dev.arkbuilders.arkmemo.utils.launchPeriodicAsync import dev.arkbuilders.arkmemo.utils.millisToString import kotlinx.coroutines.Deferred import kotlinx.coroutines.Dispatchers @@ -20,195 +20,211 @@ import javax.inject.Inject import kotlin.math.log10 sealed class ArkMediaPlayerSideEffect { - data object StartPlaying: ArkMediaPlayerSideEffect() + data object StartPlaying : ArkMediaPlayerSideEffect() - data object PausePlaying: ArkMediaPlayerSideEffect() + data object PausePlaying : ArkMediaPlayerSideEffect() - data object ResumePlaying: ArkMediaPlayerSideEffect() + data object ResumePlaying : ArkMediaPlayerSideEffect() - data object StopPlaying: ArkMediaPlayerSideEffect() + data object StopPlaying : ArkMediaPlayerSideEffect() } data class ArkMediaPlayerState( val currentPos: Int, val duration: String, - val maxAmplitude: Int + val maxAmplitude: Int, ) @HiltViewModel -class ArkMediaPlayerViewModel @Inject constructor( - private val arkMediaPlayer: ArkMediaPlayer -): ViewModel() { - - private var currentPlayingVoiceNotePath: String = "" - private val arkMediaPlayerSideEffect = MutableStateFlow(null) - private val arkMediaPlayerState = MutableStateFlow(null) - val playerState = arkMediaPlayerState as StateFlow - val playerSideEffect = arkMediaPlayerSideEffect as StateFlow - - private var progressJob: Deferred<*>? = null - private var visualizer: Visualizer? = null - - fun initPlayer(path: String) { - currentPlayingVoiceNotePath = path - arkMediaPlayer.init( - path, - onCompletion = { - arkMediaPlayerSideEffect.value = ArkMediaPlayerSideEffect.StopPlaying - finishPlaybackProgressUpdate() - }, - onPrepared = { - arkMediaPlayerState.value = ArkMediaPlayerState( - currentPos = 0, - duration = millisToString(arkMediaPlayer.duration().toLong()), - maxAmplitude = arkMediaPlayer.getMaxAmplitude() - ) - startProgressMonitor() - } - ) - } - - private fun setupVisualizer() { - // Attach a Visualizer to the MediaPlayer - //Inspired from this thread: https://stackoverflow.com/a/30384717 - visualizer = Visualizer(arkMediaPlayer.getAudioSessionId()).apply { - captureSize = Visualizer.getCaptureSizeRange()[1] // Use the max capture size - setDataCaptureListener(object : Visualizer.OnDataCaptureListener { - override fun onWaveFormDataCapture( - visualizer: Visualizer?, - waveform: ByteArray?, - samplingRate: Int - ) { - } - - override fun onFftDataCapture( - visualizer: Visualizer?, - fft: ByteArray?, - samplingRate: Int - ) { - // Optionally, process FFT data here - viewModelScope.launch(Dispatchers.IO) { - val intensity = computeFftMagnitude(fft) - withContext(Dispatchers.Main) { - arkMediaPlayer.setMaxAmplitude((intensity * WaveView.MAX_AMPLITUDE).toInt()) - } - } - } - }, Visualizer.getMaxCaptureRate(), false, true) - - scalingMode = Visualizer.MEASUREMENT_MODE_PEAK_RMS - enabled = true - } - } - - /** - * Calculate the FFT-based sound magnitude from the provided FFT ByteArray - * Inspiration is from: https://developer.android.com/reference/android/media/audiofx/Visualizer#getFft(byte[]) - */ - private fun computeFftMagnitude(fft: ByteArray?): Float { - if (fft == null) return 0f - - // Compute magnitude from FFT data - var sum = 0.0 - for (i in 2 until fft.size step 2) { // Skip the first 2 bytes (DC component) - val real = fft[i].toInt() - val imaginary = fft[i + 1].toInt() - val magnitude = real * real + imaginary * imaginary - sum += magnitude - } - - val averageMagnitude = sum / (fft.size / 2) - - // Convert to a logarithmic scale to make visualizer more responsive - return log10(averageMagnitude + 1).toFloat() // Add 1 to avoid log(0) - } - - fun setPath(path: String) { - currentPlayingVoiceNotePath = path - } - - fun onPlayOrPauseClick(path: String, pos: Int? = null, onStop: ((pos: Int) -> Unit)? = null) { - if (currentPlayingVoiceNotePath != path) { +class ArkMediaPlayerViewModel + @Inject + constructor( + private val arkMediaPlayer: ArkMediaPlayer, + ) : ViewModel() { + private var currentPlayingVoiceNotePath: String = "" + private val arkMediaPlayerSideEffect = MutableStateFlow(null) + private val arkMediaPlayerState = MutableStateFlow(null) + val playerState = arkMediaPlayerState as StateFlow + val playerSideEffect = arkMediaPlayerSideEffect as StateFlow + + private var progressJob: Deferred<*>? = null + private var visualizer: Visualizer? = null + + fun initPlayer(path: String) { currentPlayingVoiceNotePath = path arkMediaPlayer.init( path, onCompletion = { arkMediaPlayerSideEffect.value = ArkMediaPlayerSideEffect.StopPlaying - onStop?.invoke(pos ?: 0) finishPlaybackProgressUpdate() }, onPrepared = { - arkMediaPlayerState.value = ArkMediaPlayerState( - currentPos = 0, - duration = millisToString(arkMediaPlayer.duration().toLong()), - maxAmplitude = arkMediaPlayer.getMaxAmplitude() + arkMediaPlayerState.value = + ArkMediaPlayerState( + currentPos = 0, + duration = millisToString(arkMediaPlayer.duration().toLong()), + maxAmplitude = arkMediaPlayer.getMaxAmplitude(), + ) + startProgressMonitor() + }, + ) + } + + private fun setupVisualizer() { + // Attach a Visualizer to the MediaPlayer + // Inspired from this thread: https://stackoverflow.com/a/30384717 + visualizer = + Visualizer(arkMediaPlayer.getAudioSessionId()).apply { + captureSize = Visualizer.getCaptureSizeRange()[1] // Use the max capture size + setDataCaptureListener( + object : Visualizer.OnDataCaptureListener { + override fun onWaveFormDataCapture( + visualizer: Visualizer?, + waveform: ByteArray?, + samplingRate: Int, + ) { + } + + override fun onFftDataCapture( + visualizer: Visualizer?, + fft: ByteArray?, + samplingRate: Int, + ) { + // Optionally, process FFT data here + viewModelScope.launch(Dispatchers.IO) { + val intensity = computeFftMagnitude(fft) + withContext(Dispatchers.Main) { + arkMediaPlayer.setMaxAmplitude((intensity * WaveView.MAX_AMPLITUDE).toInt()) + } + } + } + }, + Visualizer.getMaxCaptureRate(), false, true, ) + + scalingMode = Visualizer.MEASUREMENT_MODE_PEAK_RMS + enabled = true } - ) } - if (arkMediaPlayer.isPlaying()) { - onPauseClick() - return + + /** + * Calculate the FFT-based sound magnitude from the provided FFT ByteArray + * Inspiration is from: https://developer.android.com/reference/android/media/audiofx/Visualizer#getFft(byte[]) + */ + private fun computeFftMagnitude(fft: ByteArray?): Float { + if (fft == null) return 0f + + // Compute magnitude from FFT data + var sum = 0.0 + for (i in 2 until fft.size step 2) { // Skip the first 2 bytes (DC component) + val real = fft[i].toInt() + val imaginary = fft[i + 1].toInt() + val magnitude = real * real + imaginary * imaginary + sum += magnitude + } + + val averageMagnitude = sum / (fft.size / 2) + + // Convert to a logarithmic scale to make visualizer more responsive + return log10(averageMagnitude + 1).toFloat() // Add 1 to avoid log(0) } - onPlayClick() - } - private fun startProgressMonitor() { - if (progressJob?.isActive == true) return - val duration = millisToString(arkMediaPlayer.duration().toLong()) + fun setPath(path: String) { + currentPlayingVoiceNotePath = path + } - progressJob = viewModelScope.launchPeriodicAsync(repeatMillis = 100L, repeatCondition = isPlaying()) { - val curPosInMillis = arkMediaPlayer.currentPosition() - val curPos = curPosInMillis / 1000 + fun onPlayOrPauseClick( + path: String, + pos: Int? = null, + onStop: ((pos: Int) -> Unit)? = null, + ) { + if (currentPlayingVoiceNotePath != path) { + currentPlayingVoiceNotePath = path + arkMediaPlayer.init( + path, + onCompletion = { + arkMediaPlayerSideEffect.value = ArkMediaPlayerSideEffect.StopPlaying + onStop?.invoke(pos ?: 0) + finishPlaybackProgressUpdate() + }, + onPrepared = { + arkMediaPlayerState.value = + ArkMediaPlayerState( + currentPos = 0, + duration = millisToString(arkMediaPlayer.duration().toLong()), + maxAmplitude = arkMediaPlayer.getMaxAmplitude(), + ) + }, + ) + } + if (arkMediaPlayer.isPlaying()) { + onPauseClick() + return + } + onPlayClick() + } - arkMediaPlayerState.value = ArkMediaPlayerState( - currentPos = curPos, - duration = duration, - maxAmplitude = arkMediaPlayer.getMaxAmplitude() - ) + private fun startProgressMonitor() { + if (progressJob?.isActive == true) return + val duration = millisToString(arkMediaPlayer.duration().toLong()) + + progressJob = + viewModelScope.launchPeriodicAsync(repeatMillis = 100L, repeatCondition = isPlaying()) { + val curPosInMillis = arkMediaPlayer.currentPosition() + val curPos = curPosInMillis / 1000 + + arkMediaPlayerState.value = + ArkMediaPlayerState( + currentPos = curPos, + duration = duration, + maxAmplitude = arkMediaPlayer.getMaxAmplitude(), + ) + } } - } - private fun onPlayClick() { - arkMediaPlayer.play() - setupVisualizer() - startProgressMonitor() - arkMediaPlayerSideEffect.value = ArkMediaPlayerSideEffect.StartPlaying - } + private fun onPlayClick() { + arkMediaPlayer.play() + setupVisualizer() + startProgressMonitor() + arkMediaPlayerSideEffect.value = ArkMediaPlayerSideEffect.StartPlaying + } - private fun onPauseClick() { - arkMediaPlayer.pause() - arkMediaPlayerSideEffect.value = ArkMediaPlayerSideEffect.PausePlaying - releaseVisualizer() - progressJob?.cancel() - } + private fun onPauseClick() { + arkMediaPlayer.pause() + arkMediaPlayerSideEffect.value = ArkMediaPlayerSideEffect.PausePlaying + releaseVisualizer() + progressJob?.cancel() + } - fun getDurationString(onSuccess: (duration: String) -> Unit) { - if (currentPlayingVoiceNotePath.isEmpty() - || File(currentPlayingVoiceNotePath).length() == 0L) return + fun getDurationString(onSuccess: (duration: String) -> Unit) { + if (currentPlayingVoiceNotePath.isEmpty() || + File(currentPlayingVoiceNotePath).length() == 0L + ) { + return + } - viewModelScope.launch(Dispatchers.IO) { - val durationString = extractDuration(currentPlayingVoiceNotePath) - withContext(Dispatchers.Main) { - onSuccess.invoke(durationString) + viewModelScope.launch(Dispatchers.IO) { + val durationString = extractDuration(currentPlayingVoiceNotePath) + withContext(Dispatchers.Main) { + onSuccess.invoke(durationString) + } } } - } - fun isPlayerInitialized(): Boolean{ - return arkMediaPlayer.isInitialized() - } + fun isPlayerInitialized(): Boolean { + return arkMediaPlayer.isInitialized() + } - fun isPlaying(): Boolean { - return arkMediaPlayer.isPlaying() - } + fun isPlaying(): Boolean { + return arkMediaPlayer.isPlaying() + } - private fun finishPlaybackProgressUpdate() { - releaseVisualizer() - progressJob?.cancel() - } + private fun finishPlaybackProgressUpdate() { + releaseVisualizer() + progressJob?.cancel() + } - private fun releaseVisualizer() { - visualizer?.release() + private fun releaseVisualizer() { + visualizer?.release() + } } -} \ No newline at end of file diff --git a/app/src/main/java/dev/arkbuilders/arkmemo/ui/viewmodels/ArkRecorderViewModel.kt b/app/src/main/java/dev/arkbuilders/arkmemo/ui/viewmodels/ArkRecorderViewModel.kt index 9cf4259d..b1e59ece 100644 --- a/app/src/main/java/dev/arkbuilders/arkmemo/ui/viewmodels/ArkRecorderViewModel.kt +++ b/app/src/main/java/dev/arkbuilders/arkmemo/ui/viewmodels/ArkRecorderViewModel.kt @@ -18,175 +18,179 @@ import javax.inject.Inject import kotlin.concurrent.timer sealed class RecorderSideEffect { - data object StartRecording: RecorderSideEffect() + data object StartRecording : RecorderSideEffect() data class StopRecording(val duration: String) : RecorderSideEffect() - data object PauseRecording: RecorderSideEffect() + data object PauseRecording : RecorderSideEffect() - data object ResumeRecording: RecorderSideEffect() + data object ResumeRecording : RecorderSideEffect() } data class RecorderState( val maxAmplitude: Int, - val progress: String + val progress: String, ) @HiltViewModel -class ArkRecorderViewModel @Inject constructor( - private val arkAudioRecorder: ArkAudioRecorder -): ViewModel() { - - private val recorderSideEffect = MutableStateFlow(null) - private val recorderState = MutableStateFlow(null) - private val isRecording = MutableStateFlow(false) - private val isPaused = MutableStateFlow(false) - - // Duration is in milliseconds - private var duration = 0L - private val RECORD_SAMPLING_INTERVAL = 100L //millisecond - - private var timer: Timer? = null - private var recordTimerTask: TimerTask? = null - - fun onStartStopClick() { - if (isRecording.value) { - onStopRecordingClick() - } else { - onStartRecordingClick() +class ArkRecorderViewModel + @Inject + constructor( + private val arkAudioRecorder: ArkAudioRecorder, + ) : ViewModel() { + private val recorderSideEffect = MutableStateFlow(null) + private val recorderState = MutableStateFlow(null) + private val isRecording = MutableStateFlow(false) + private val isPaused = MutableStateFlow(false) + + // Duration is in milliseconds + private var duration = 0L + private val recordSamplingInterval = 100L // millisecond + + private var timer: Timer? = null + private var recordTimerTask: TimerTask? = null + + fun onStartStopClick() { + if (isRecording.value) { + onStopRecordingClick() + } else { + onStartRecordingClick() + } } - } - fun onPauseResumeClick() { - if (isPaused.value) { - onResumeRecordingClick() - } else { - onPauseRecordingClick() + fun onPauseResumeClick() { + if (isPaused.value) { + onResumeRecordingClick() + } else { + onPauseRecordingClick() + } } - } - fun onStartOverClick() { - onStartOverRecordingClick() - } + fun onStartOverClick() { + onStartOverRecordingClick() + } - fun collect( - stateToUI: (RecorderState) -> Unit, - handleSideEffect:(RecorderSideEffect) -> Unit - ) { - viewModelScope.launch { - recorderState.collect { - it?.let { - stateToUI(it) + fun collect( + stateToUI: (RecorderState) -> Unit, + handleSideEffect: (RecorderSideEffect) -> Unit, + ) { + viewModelScope.launch { + recorderState.collect { + it?.let { + stateToUI(it) + } } } - } - viewModelScope.launch { - recorderSideEffect.collectLatest { - it?.let { - handleSideEffect(it) + viewModelScope.launch { + recorderSideEffect.collectLatest { + it?.let { + handleSideEffect(it) + } } } } - } - fun getRecordingPath(): Path { - return arkAudioRecorder.getRecording() - } - - fun isRecordExisting(): Boolean { - val recordFile = File(getRecordingPath().toUri()) - return !isRecording.value && recordFile.exists() && recordFile.length() > 0 - } - - private fun onStartRecordingClick() { - viewModelScope.launch { - arkAudioRecorder.init() - arkAudioRecorder.start() - isRecording.value = true - startTimer() - recorderSideEffect.value = RecorderSideEffect.StartRecording + fun getRecordingPath(): Path { + return arkAudioRecorder.getRecording() } - } - private fun onStopRecordingClick() { - viewModelScope.launch { - arkAudioRecorder.stop() - isRecording.value = false - if (isPaused.value) isPaused.value = false - val lastDuration = duration - duration = 0 - stopTimer() - recorderSideEffect.value = RecorderSideEffect.StopRecording( - duration = millisToString(lastDuration * RECORD_SAMPLING_INTERVAL) - ) + fun isRecordExisting(): Boolean { + val recordFile = File(getRecordingPath().toUri()) + return !isRecording.value && recordFile.exists() && recordFile.length() > 0 } - } - private fun onStartOverRecordingClick() { - viewModelScope.launch { - arkAudioRecorder.stop() - duration = 0 - stopTimer() + private fun onStartRecordingClick() { + viewModelScope.launch { + arkAudioRecorder.init() + arkAudioRecorder.start() + isRecording.value = true + startTimer() + recorderSideEffect.value = RecorderSideEffect.StartRecording + } + } - arkAudioRecorder.init() - arkAudioRecorder.start() - startTimer() + private fun onStopRecordingClick() { + viewModelScope.launch { + arkAudioRecorder.stop() + isRecording.value = false + if (isPaused.value) isPaused.value = false + val lastDuration = duration + duration = 0 + stopTimer() + recorderSideEffect.value = + RecorderSideEffect.StopRecording( + duration = millisToString(lastDuration * recordSamplingInterval), + ) + } } - } - private fun onPauseRecordingClick() { - viewModelScope.launch { - if (isRecording.value) { - isPaused.value = true - arkAudioRecorder.pause() + private fun onStartOverRecordingClick() { + viewModelScope.launch { + arkAudioRecorder.stop() + duration = 0 stopTimer() - recorderSideEffect.value = RecorderSideEffect.PauseRecording + + arkAudioRecorder.init() + arkAudioRecorder.start() + startTimer() } } - } - private fun onResumeRecordingClick() { - viewModelScope.launch { - if (isRecording.value) { - arkAudioRecorder.resume() - isPaused.value = false - startTimer(isResumed = true) - recorderSideEffect.value = RecorderSideEffect.ResumeRecording + private fun onPauseRecordingClick() { + viewModelScope.launch { + if (isRecording.value) { + isPaused.value = true + arkAudioRecorder.pause() + stopTimer() + recorderSideEffect.value = RecorderSideEffect.PauseRecording + } } } - } - private fun startTimer(isResumed: Boolean = false) { - viewModelScope.launch { - if (isResumed) { - recordTimerTask?.cancel() + private fun onResumeRecordingClick() { + viewModelScope.launch { + if (isRecording.value) { + arkAudioRecorder.resume() + isPaused.value = false + startTimer(isResumed = true) + recorderSideEffect.value = RecorderSideEffect.ResumeRecording + } } - timer = timer(initialDelay = 0L, period = RECORD_SAMPLING_INTERVAL) { - recordTimerTask = this - if (isRecording.value && !isPaused.value) { - duration += 1 - recorderState.value = RecorderState( - arkAudioRecorder.maxAmplitude(), - tenthSecondsToString(duration) - ) + } + + private fun startTimer(isResumed: Boolean = false) { + viewModelScope.launch { + if (isResumed) { + recordTimerTask?.cancel() } + timer = + timer(initialDelay = 0L, period = recordSamplingInterval) { + recordTimerTask = this + if (isRecording.value && !isPaused.value) { + duration += 1 + recorderState.value = + RecorderState( + arkAudioRecorder.maxAmplitude(), + tenthSecondsToString(duration), + ) + } + } } } - } - private fun stopTimer() { - timer?.cancel() - recordTimerTask?.cancel() - timer = null - } + private fun stopTimer() { + timer?.cancel() + recordTimerTask?.cancel() + timer = null + } - fun isRecording(): Boolean { - return isRecording.value - } + fun isRecording(): Boolean { + return isRecording.value + } - fun deleteTempFile() { - viewModelScope.launch(Dispatchers.IO) { - arkAudioRecorder.deleteTempFile() + fun deleteTempFile() { + viewModelScope.launch(Dispatchers.IO) { + arkAudioRecorder.deleteTempFile() + } } } -} diff --git a/app/src/main/java/dev/arkbuilders/arkmemo/ui/viewmodels/GraphicNotesViewModel.kt b/app/src/main/java/dev/arkbuilders/arkmemo/ui/viewmodels/GraphicNotesViewModel.kt index f1fe3c9d..ca05b1b2 100644 --- a/app/src/main/java/dev/arkbuilders/arkmemo/ui/viewmodels/GraphicNotesViewModel.kt +++ b/app/src/main/java/dev/arkbuilders/arkmemo/ui/viewmodels/GraphicNotesViewModel.kt @@ -15,64 +15,67 @@ import kotlinx.coroutines.launch import javax.inject.Inject @HiltViewModel -class GraphicNotesViewModel @Inject constructor(): ViewModel() { +class GraphicNotesViewModel + @Inject + constructor() : ViewModel() { + private var paintColor = Color.BLACK.code + private var lastPaintColor = paintColor + private var strokeWidth = Size.TINY.value - private var paintColor = Color.BLACK.code - private var lastPaintColor = paintColor - private var strokeWidth = Size.TINY.value + val paint get() = + Paint().also { + it.color = paintColor + it.style = Paint.Style.STROKE + it.strokeWidth = strokeWidth + it.strokeCap = Paint.Cap.ROUND + it.strokeJoin = Paint.Join.ROUND + it.isAntiAlias = true + } - val paint get() = Paint().also { - it.color = paintColor - it.style = Paint.Style.STROKE - it.strokeWidth = strokeWidth - it.strokeCap = Paint.Cap.ROUND - it.strokeJoin = Paint.Join.ROUND - it.isAntiAlias = true - } - - private val editPaths = ArrayDeque() + private val editPaths = ArrayDeque() - private var svg = SVG() - private val svgLiveData = MutableLiveData() - val observableSvgLiveData = svgLiveData as LiveData + private var svg = SVG() + private val svgLiveData = MutableLiveData() + val observableSvgLiveData = svgLiveData as LiveData - fun onNoteOpened(note: GraphicNote) { - viewModelScope.launch { - if (editPaths.isNotEmpty()) editPaths.clear() - editPaths.addAll(note.svg?.getPaths()!!) - svg = note.svg.copy() + fun onNoteOpened(note: GraphicNote) { + viewModelScope.launch { + if (editPaths.isNotEmpty()) editPaths.clear() + editPaths.addAll(note.svg?.getPaths()!!) + svg = note.svg.copy() + } } - } - fun onDrawPath(path: DrawPath) { - editPaths.addLast(path) - svg.addPath(path) - svgLiveData.postValue(svg) - } + fun onDrawPath(path: DrawPath) { + editPaths.addLast(path) + svg.addPath(path) + svgLiveData.postValue(svg) + } - fun paths(): Collection = editPaths + fun paths(): Collection = editPaths - fun svg(): SVG = svg + fun svg(): SVG = svg - fun setPaintColor(color: Int) { - paintColor = color - lastPaintColor = paintColor - } + fun setPaintColor(color: Int) { + paintColor = color + lastPaintColor = paintColor + } - fun setBrushSize(size: Float) { - strokeWidth = size - } + fun setBrushSize(size: Float) { + strokeWidth = size + } - fun setEraseMode(eraseMode: Boolean) { - paintColor = if (eraseMode) { - Color.WHITE.code - } else { - lastPaintColor + fun setEraseMode(eraseMode: Boolean) { + paintColor = + if (eraseMode) { + Color.WHITE.code + } else { + lastPaintColor + } } } -} data class DrawPath( val path: Path, - val paint: Paint -) \ No newline at end of file + val paint: Paint, +) diff --git a/app/src/main/java/dev/arkbuilders/arkmemo/ui/viewmodels/NotesViewModel.kt b/app/src/main/java/dev/arkbuilders/arkmemo/ui/viewmodels/NotesViewModel.kt index ca6b0f4b..6b59f99e 100644 --- a/app/src/main/java/dev/arkbuilders/arkmemo/ui/viewmodels/NotesViewModel.kt +++ b/app/src/main/java/dev/arkbuilders/arkmemo/ui/viewmodels/NotesViewModel.kt @@ -6,159 +6,175 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import dagger.hilt.android.lifecycle.HiltViewModel import dev.arkbuilders.arklib.ResourceId -import kotlinx.coroutines.CoroutineDispatcher -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext -import dev.arkbuilders.arkmemo.models.SaveNoteResult -import dev.arkbuilders.arkmemo.repo.NotesRepo import dev.arkbuilders.arkmemo.di.IO_DISPATCHER import dev.arkbuilders.arkmemo.models.GraphicNote import dev.arkbuilders.arkmemo.models.Note +import dev.arkbuilders.arkmemo.models.SaveNoteResult import dev.arkbuilders.arkmemo.models.TextNote import dev.arkbuilders.arkmemo.models.VoiceNote +import dev.arkbuilders.arkmemo.repo.NotesRepo import dev.arkbuilders.arkmemo.utils.extractDuration +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext import javax.inject.Inject import javax.inject.Named import kotlin.io.path.pathString @HiltViewModel -class NotesViewModel @Inject constructor( - @Named(IO_DISPATCHER) private val iODispatcher: CoroutineDispatcher, - private val textNotesRepo: NotesRepo, - private val graphicNotesRepo: NotesRepo, - private val voiceNotesRepo: NotesRepo -) : ViewModel() { - - private val notes = MutableStateFlow(listOf()) - private val mSaveNoteResultLiveData = MutableLiveData() - private var searchJob: Job? = null - - fun init(extraBlock: () -> Unit) { - val initJob = viewModelScope.launch(iODispatcher) { - textNotesRepo.init() - graphicNotesRepo.init() - voiceNotesRepo.init() - } - viewModelScope.launch { - initJob.join() - extraBlock() - } - } - - fun readAllNotes(onSuccess: (notes: List) -> Unit) { - viewModelScope.launch(iODispatcher) { - notes.value = textNotesRepo.read() + graphicNotesRepo.read() + voiceNotesRepo.read() - notes.value.let { - withContext(Dispatchers.Main) { - onSuccess(it.sortedByDescending { note -> note.resource?.modified }) +class NotesViewModel + @Inject + constructor( + @Named(IO_DISPATCHER) private val iODispatcher: CoroutineDispatcher, + private val textNotesRepo: NotesRepo, + private val graphicNotesRepo: NotesRepo, + private val voiceNotesRepo: NotesRepo, + ) : ViewModel() { + private val notes = MutableStateFlow(listOf()) + private val mSaveNoteResultLiveData = MutableLiveData() + private var searchJob: Job? = null + + fun init(extraBlock: () -> Unit) { + val initJob = + viewModelScope.launch(iODispatcher) { + textNotesRepo.init() + graphicNotesRepo.init() + voiceNotesRepo.init() } + viewModelScope.launch { + initJob.join() + extraBlock() } } - } - - fun searchNote(keyword: String, onSuccess: (notes: List) -> Unit) { - searchJob?.cancel() - searchJob = viewModelScope.launch(iODispatcher) { - //Add a delay to restart the search job if there are 2 consecutive search events - //triggered within 0.5 second window. - delay(500) - notes.collectLatest { - val filteredNotes = it - .filter { note -> - note.title.contains(keyword, true) + fun readAllNotes(onSuccess: (notes: List) -> Unit) { + viewModelScope.launch(iODispatcher) { + notes.value = textNotesRepo.read() + graphicNotesRepo.read() + voiceNotesRepo.read() + notes.value.let { + withContext(Dispatchers.Main) { + onSuccess(it.sortedByDescending { note -> note.resource?.modified }) } - - //Keep the search result ordered chronologically - .sortedByDescending { note -> note.resource?.modified } - withContext(Dispatchers.Main) { - onSuccess(filteredNotes) } } } - } - fun onSaveClick(note: Note, parentNote: Note? = null, showProgress: (Boolean) -> Unit) { - val noteResId = note.resource?.id - viewModelScope.launch(iODispatcher) { - withContext(Dispatchers.Main) { - showProgress(true) - } - fun handleResult(result: SaveNoteResult) { - if (result == SaveNoteResult.SUCCESS_NEW - || result == SaveNoteResult.SUCCESS_UPDATED) { + fun searchNote( + keyword: String, + onSuccess: (notes: List) -> Unit, + ) { + searchJob?.cancel() + searchJob = + viewModelScope.launch(iODispatcher) { + // Add a delay to restart the search job if there are 2 consecutive search events + // triggered within 0.5 second window. + delay(500) + notes.collectLatest { + val filteredNotes = + it + .filter { note -> + note.title.contains(keyword, true) + } + // Keep the search result ordered chronologically + .sortedByDescending { note -> note.resource?.modified } + withContext(Dispatchers.Main) { + onSuccess(filteredNotes) + } + } + } + } - if (result == SaveNoteResult.SUCCESS_NEW) { - parentNote?.let { onDeleteConfirmed(parentNote){} } + fun onSaveClick( + note: Note, + parentNote: Note? = null, + showProgress: (Boolean) -> Unit, + ) { + val noteResId = note.resource?.id + viewModelScope.launch(iODispatcher) { + withContext(Dispatchers.Main) { + showProgress(true) + } + fun handleResult(result: SaveNoteResult) { + if (result == SaveNoteResult.SUCCESS_NEW || + result == SaveNoteResult.SUCCESS_UPDATED + ) { + if (result == SaveNoteResult.SUCCESS_NEW) { + parentNote?.let { onDeleteConfirmed(parentNote) {} } + } + add(note, noteResId) } - add(note, noteResId) + mSaveNoteResultLiveData.postValue(result) } - mSaveNoteResultLiveData.postValue(result) - } - when (note) { - is TextNote -> { - textNotesRepo.save(note) { result -> - handleResult(result) + when (note) { + is TextNote -> { + textNotesRepo.save(note) { result -> + handleResult(result) + } } - } - is GraphicNote -> { - graphicNotesRepo.save(note) { result -> - handleResult(result) + is GraphicNote -> { + graphicNotesRepo.save(note) { result -> + handleResult(result) + } } - } - is VoiceNote -> { - voiceNotesRepo.save(note) { result -> - handleResult(result) + is VoiceNote -> { + voiceNotesRepo.save(note) { result -> + handleResult(result) + } } } - } - withContext(Dispatchers.Main) { - showProgress(false) + withContext(Dispatchers.Main) { + showProgress(false) + } } } - } - fun onDeleteConfirmed(note: Note, onSuccess: () -> Unit) { - viewModelScope.launch(iODispatcher) { - when (note) { - is TextNote -> textNotesRepo.delete(note) - is GraphicNote -> graphicNotesRepo.delete(note) - is VoiceNote -> voiceNotesRepo.delete(note) - } + fun onDeleteConfirmed( + note: Note, + onSuccess: () -> Unit, + ) { + viewModelScope.launch(iODispatcher) { + when (note) { + is TextNote -> textNotesRepo.delete(note) + is GraphicNote -> graphicNotesRepo.delete(note) + is VoiceNote -> voiceNotesRepo.delete(note) + } - this@NotesViewModel.notes.value = this@NotesViewModel.notes.value.toMutableList() - .apply { remove(note) } - withContext(Dispatchers.Main) { - onSuccess.invoke() + this@NotesViewModel.notes.value = + this@NotesViewModel.notes.value.toMutableList() + .apply { remove(note) } + withContext(Dispatchers.Main) { + onSuccess.invoke() + } } } - } - private fun add(note: Note, parentResId: ResourceId? = null) { - val notes = this.notes.value.toMutableList() - note.resource?.let { - notes.removeIf { it.resource?.id == parentResId ?: note.resource?.id } - } - if (note is VoiceNote) { - note.duration = extractDuration(note.path.pathString) + private fun add( + note: Note, + parentResId: ResourceId? = null, + ) { + val notes = this.notes.value.toMutableList() + note.resource?.let { + notes.removeIf { it.resource?.id == parentResId ?: note.resource?.id } + } + if (note is VoiceNote) { + note.duration = extractDuration(note.path.pathString) + } + notes.add(note) + this.notes.value = notes } - notes.add(note) - this.notes.value = notes - } - private fun remove(note: Note) { - val notes = this.notes.value.toMutableList() - notes.remove(note) - this.notes.value = notes - } + private fun remove(note: Note) { + val notes = this.notes.value.toMutableList() + notes.remove(note) + this.notes.value = notes + } - fun getSaveNoteResultLiveData(): LiveData { - return mSaveNoteResultLiveData + fun getSaveNoteResultLiveData(): LiveData { + return mSaveNoteResultLiveData + } } -} \ No newline at end of file diff --git a/app/src/main/java/dev/arkbuilders/arkmemo/ui/viewmodels/QRViewModel.kt b/app/src/main/java/dev/arkbuilders/arkmemo/ui/viewmodels/QRViewModel.kt index e161e1a4..4694416f 100644 --- a/app/src/main/java/dev/arkbuilders/arkmemo/ui/viewmodels/QRViewModel.kt +++ b/app/src/main/java/dev/arkbuilders/arkmemo/ui/viewmodels/QRViewModel.kt @@ -20,44 +20,53 @@ import java.io.File import javax.inject.Inject import javax.inject.Named - @HiltViewModel -class QRViewModel @Inject constructor( - @Named(IO_DISPATCHER) private val iODispatcher: CoroutineDispatcher, - @ApplicationContext private val appContext: Context, -) : ViewModel() { - - fun generateQRCode(text: String, onSuccess: (bitmap: Bitmap) -> Unit) { - viewModelScope.launch(iODispatcher) { - // Initializing the QR Encoder with your value to be encoded, type you required and Dimension - val qrgEncoder = QRGEncoder(text, null, QRGContents.Type.TEXT, 300.dpToPx()) - qrgEncoder.colorBlack = Color.BLACK - qrgEncoder.colorWhite = Color.WHITE - withContext(Dispatchers.Main) { - onSuccess.invoke(qrgEncoder.getBitmap(0)) +class QRViewModel + @Inject + constructor( + @Named(IO_DISPATCHER) private val iODispatcher: CoroutineDispatcher, + @ApplicationContext private val appContext: Context, + ) : ViewModel() { + fun generateQRCode( + text: String, + onSuccess: (bitmap: Bitmap) -> Unit, + ) { + viewModelScope.launch(iODispatcher) { + // Initializing the QR Encoder with your value to be encoded, type you required and Dimension + val qrgEncoder = QRGEncoder(text, null, QRGContents.Type.TEXT, 300.dpToPx()) + qrgEncoder.colorBlack = Color.BLACK + qrgEncoder.colorWhite = Color.WHITE + withContext(Dispatchers.Main) { + onSuccess.invoke(qrgEncoder.getBitmap(0)) + } } } - } - fun saveQRCodeImage(text: String, bitmap: Bitmap, onSuccess: (path: String) -> Unit) { - viewModelScope.launch { - // Save with location, value, bitmap returned and type of Image(JPG/PNG). - val qrgSaver = QRGSaver() + fun saveQRCodeImage( + text: String, + bitmap: Bitmap, + onSuccess: (path: String) -> Unit, + ) { + viewModelScope.launch { + // Save with location, value, bitmap returned and type of Image(JPG/PNG). + val qrgSaver = QRGSaver() - val savePath = (appContext.getExternalFilesDir(null)?.path + "/images/").apply { - File(this).mkdirs() - } + val savePath = + (appContext.getExternalFilesDir(null)?.path + "/images/").apply { + File(this).mkdirs() + } - val isSuccess = qrgSaver.save( - savePath, - text, - bitmap, - QRGContents.ImageType.IMAGE_JPEG - ) + val isSuccess = + qrgSaver.save( + savePath, + text, + bitmap, + QRGContents.ImageType.IMAGE_JPEG, + ) - if (isSuccess) { - onSuccess.invoke(savePath) + if (isSuccess) { + onSuccess.invoke(savePath) + } } } } -} \ No newline at end of file diff --git a/app/src/main/java/dev/arkbuilders/arkmemo/ui/views/GraphicControlTextView.kt b/app/src/main/java/dev/arkbuilders/arkmemo/ui/views/GraphicControlTextView.kt index 0d38fdc7..bc3bb38d 100644 --- a/app/src/main/java/dev/arkbuilders/arkmemo/ui/views/GraphicControlTextView.kt +++ b/app/src/main/java/dev/arkbuilders/arkmemo/ui/views/GraphicControlTextView.kt @@ -9,57 +9,63 @@ import androidx.core.content.ContextCompat import androidx.core.widget.TextViewCompat import dev.arkbuilders.arkmemo.R -class GraphicControlTextView @JvmOverloads constructor( - context: Context, - attrs: AttributeSet? = null, -) : androidx.appcompat.widget.AppCompatTextView(context, attrs) { +class GraphicControlTextView + @JvmOverloads + constructor( + context: Context, + attrs: AttributeSet? = null, + ) : androidx.appcompat.widget.AppCompatTextView(context, attrs) { + private var iconTintColor: Int - private var iconTintColor: Int + init { + val typedArray: TypedArray = + context.obtainStyledAttributes(attrs, R.styleable.GraphicControlTextView) + val drawableResId = + typedArray.getResourceId(R.styleable.GraphicControlTextView_gct_drawable, 0) + val isSelected = + typedArray.getBoolean(R.styleable.GraphicControlTextView_gct_selected, false) + iconTintColor = typedArray.getColor(R.styleable.GraphicControlTextView_gct_icon_tint, -1) - init { - val typedArray: TypedArray = - context.obtainStyledAttributes(attrs, R.styleable.GraphicControlTextView) - val drawableResId = - typedArray.getResourceId(R.styleable.GraphicControlTextView_gct_drawable, 0) - val isSelected = - typedArray.getBoolean(R.styleable.GraphicControlTextView_gct_selected, false) - iconTintColor = typedArray.getColor(R.styleable.GraphicControlTextView_gct_icon_tint, -1) + drawableResId.let { + this.setCompoundDrawablesWithIntrinsicBounds(drawableResId, 0, 0, 0) + } + setSelected(isSelected) - drawableResId.let { - this.setCompoundDrawablesWithIntrinsicBounds(drawableResId, 0, 0, 0) + typedArray.recycle() } - setSelected(isSelected) - typedArray.recycle() - } - - override fun setSelected(selected: Boolean) { - super.setSelected(selected) - if (selected) { - this.background = ContextCompat.getDrawable( - context, R.drawable.bg_graphic_control_text_selected - ) - val selectedColor = ContextCompat.getColor(context, R.color.warning_700) + override fun setSelected(selected: Boolean) { + super.setSelected(selected) + if (selected) { + this.background = + ContextCompat.getDrawable( + context, R.drawable.bg_graphic_control_text_selected, + ) + val selectedColor = ContextCompat.getColor(context, R.color.warning_700) - this.setTextColor(selectedColor) - setDrawableTint(selectedColor) - } else { - this.background = ContextCompat.getDrawable( - context, R.drawable.bg_border_r8 - ) - val selectedColor = ContextCompat.getColor(context, R.color.text_tertiary) - val drawableColor = if (iconTintColor != -1) iconTintColor else selectedColor + this.setTextColor(selectedColor) + setDrawableTint(selectedColor) + } else { + this.background = + ContextCompat.getDrawable( + context, R.drawable.bg_border_r8, + ) + val selectedColor = ContextCompat.getColor(context, R.color.text_tertiary) + val drawableColor = if (iconTintColor != -1) iconTintColor else selectedColor - this.setTextColor(selectedColor) - setDrawableTint(drawableColor) + this.setTextColor(selectedColor) + setDrawableTint(drawableColor) + } } - } - private fun setDrawableTint(@ColorInt color: Int) { - if (iconTintColor == -1) { - TextViewCompat.setCompoundDrawableTintList(this, - ColorStateList.valueOf(color) - ) + private fun setDrawableTint( + @ColorInt color: Int, + ) { + if (iconTintColor == -1) { + TextViewCompat.setCompoundDrawableTintList( + this, + ColorStateList.valueOf(color), + ) + } } } -} \ No newline at end of file diff --git a/app/src/main/java/dev/arkbuilders/arkmemo/ui/views/NotesCanvas.kt b/app/src/main/java/dev/arkbuilders/arkmemo/ui/views/NotesCanvas.kt index 99f44ae7..39ac8c47 100644 --- a/app/src/main/java/dev/arkbuilders/arkmemo/ui/views/NotesCanvas.kt +++ b/app/src/main/java/dev/arkbuilders/arkmemo/ui/views/NotesCanvas.kt @@ -12,8 +12,7 @@ import dev.arkbuilders.arkmemo.ui.viewmodels.GraphicNotesViewModel import dev.arkbuilders.arkmemo.utils.getBrushSizeId import dev.arkbuilders.arkmemo.utils.getStrokeColor -class NotesCanvas(context: Context, attrs: AttributeSet): View(context, attrs) { - +class NotesCanvas(context: Context, attrs: AttributeSet) : View(context, attrs) { private var currentX = 0f private var currentY = 0f private lateinit var viewModel: GraphicNotesViewModel @@ -33,14 +32,16 @@ class NotesCanvas(context: Context, attrs: AttributeSet): View(context, attrs) { val y = event.y var finishDrawing = false - when(event.action) { + when (event.action) { MotionEvent.ACTION_DOWN -> { path.moveTo(x, y) viewModel.svg().apply { - addCommand(SVGCommand.MoveTo(x, y).apply { - paintColor = viewModel.paint.color.getStrokeColor() - brushSizeId = viewModel.paint.strokeWidth.getBrushSizeId() - }) + addCommand( + SVGCommand.MoveTo(x, y).apply { + paintColor = viewModel.paint.color.getStrokeColor() + brushSizeId = viewModel.paint.strokeWidth.getBrushSizeId() + }, + ) } currentX = x currentY = y @@ -50,10 +51,12 @@ class NotesCanvas(context: Context, attrs: AttributeSet): View(context, attrs) { val y2 = (currentY + y) / 2 path.quadTo(currentX, currentY, x2, y2) viewModel.svg().apply { - addCommand(SVGCommand.AbsQuadTo(currentX, currentY, x2, y2).apply { - paintColor = viewModel.paint.color.getStrokeColor() - brushSizeId = viewModel.paint.strokeWidth.getBrushSizeId() - }) + addCommand( + SVGCommand.AbsQuadTo(currentX, currentY, x2, y2).apply { + paintColor = viewModel.paint.color.getStrokeColor() + brushSizeId = viewModel.paint.strokeWidth.getBrushSizeId() + }, + ) } currentX = x currentY = y @@ -76,4 +79,4 @@ class NotesCanvas(context: Context, attrs: AttributeSet): View(context, attrs) { fun setViewModel(viewModel: GraphicNotesViewModel) { this.viewModel = viewModel } -} \ No newline at end of file +} diff --git a/app/src/main/java/dev/arkbuilders/arkmemo/ui/views/SettingTextView.kt b/app/src/main/java/dev/arkbuilders/arkmemo/ui/views/SettingTextView.kt index 4bb550a7..4d15e3af 100644 --- a/app/src/main/java/dev/arkbuilders/arkmemo/ui/views/SettingTextView.kt +++ b/app/src/main/java/dev/arkbuilders/arkmemo/ui/views/SettingTextView.kt @@ -10,26 +10,28 @@ import dev.arkbuilders.arkmemo.databinding.LayoutSettingTextBinding import dev.arkbuilders.arkmemo.utils.gone import dev.arkbuilders.arkmemo.utils.visible -class SettingTextView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) : +class SettingTextView + @JvmOverloads + constructor(context: Context, attrs: AttributeSet? = null) : ConstraintLayout(context, attrs) { - init { - val binding = LayoutSettingTextBinding.inflate(LayoutInflater.from(context), this, true) - val typedArray: TypedArray = - context.obtainStyledAttributes(attrs, R.styleable.SettingTextView) - val textResId = typedArray.getText(R.styleable.SettingTextView_stv_text) - val iconResId = typedArray.getResourceId(R.styleable.SettingTextView_stv_icon, 0) - val enableSwitch = - typedArray.getBoolean(R.styleable.SettingTextView_stv_switch_on, false) - textResId?.let { - binding.tvText.text = textResId - } - binding.ivIcon.setImageResource(iconResId) - if (enableSwitch) { - binding.switchRight.visible() - } else { - binding.switchRight.gone() - } + init { + val binding = LayoutSettingTextBinding.inflate(LayoutInflater.from(context), this, true) + val typedArray: TypedArray = + context.obtainStyledAttributes(attrs, R.styleable.SettingTextView) + val textResId = typedArray.getText(R.styleable.SettingTextView_stv_text) + val iconResId = typedArray.getResourceId(R.styleable.SettingTextView_stv_icon, 0) + val enableSwitch = + typedArray.getBoolean(R.styleable.SettingTextView_stv_switch_on, false) + textResId?.let { + binding.tvText.text = textResId + } + binding.ivIcon.setImageResource(iconResId) + if (enableSwitch) { + binding.switchRight.visible() + } else { + binding.switchRight.gone() + } - typedArray.recycle() + typedArray.recycle() + } } -} \ No newline at end of file diff --git a/app/src/main/java/dev/arkbuilders/arkmemo/ui/views/SupportTextView.kt b/app/src/main/java/dev/arkbuilders/arkmemo/ui/views/SupportTextView.kt index cd82cd4b..e02aac66 100644 --- a/app/src/main/java/dev/arkbuilders/arkmemo/ui/views/SupportTextView.kt +++ b/app/src/main/java/dev/arkbuilders/arkmemo/ui/views/SupportTextView.kt @@ -12,37 +12,39 @@ import dev.arkbuilders.arkmemo.utils.gone import dev.arkbuilders.arkmemo.utils.setOnDebounceTouchListener import dev.arkbuilders.arkmemo.utils.showAvailabilityToolTip -class SupportTextView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) : +class SupportTextView + @JvmOverloads + constructor(context: Context, attrs: AttributeSet? = null) : ConstraintLayout(context, attrs) { - init { - val binding = LayoutSupportTextBinding.inflate(LayoutInflater.from(context), this, true) - val typedArray: TypedArray = - context.obtainStyledAttributes(attrs, R.styleable.SupportTextView) - val textResId = typedArray.getText(R.styleable.SupportTextView_support_text) - val iconResId = typedArray.getResourceId(R.styleable.SupportTextView_support_icon, 0) - val enabled = typedArray.getBoolean(R.styleable.SupportTextView_support_enabled, true) - textResId?.let { - binding.tvText.text = textResId - } ?: let { - binding.tvText.gone() - binding.ivIcon.gone() - } + init { + val binding = LayoutSupportTextBinding.inflate(LayoutInflater.from(context), this, true) + val typedArray: TypedArray = + context.obtainStyledAttributes(attrs, R.styleable.SupportTextView) + val textResId = typedArray.getText(R.styleable.SupportTextView_support_text) + val iconResId = typedArray.getResourceId(R.styleable.SupportTextView_support_icon, 0) + val enabled = typedArray.getBoolean(R.styleable.SupportTextView_support_enabled, true) + textResId?.let { + binding.tvText.text = textResId + } ?: let { + binding.tvText.gone() + binding.ivIcon.gone() + } - if (iconResId != 0) { - binding.ivIcon.setImageResource(iconResId) - } else { - binding.ivIcon.gone() - } + if (iconResId != 0) { + binding.ivIcon.setImageResource(iconResId) + } else { + binding.ivIcon.gone() + } - if (!enabled) { - binding.tvText.setTextColor(ContextCompat.getColor(context, R.color.gray_400)) - binding.tvText.isEnabled = false - setOnDebounceTouchListener { v, event -> - showAvailabilityToolTip() - binding.tvText.isEnabled = true + if (!enabled) { + binding.tvText.setTextColor(ContextCompat.getColor(context, R.color.gray_400)) + binding.tvText.isEnabled = false + setOnDebounceTouchListener { v, event -> + showAvailabilityToolTip() + binding.tvText.isEnabled = true + } } - } - typedArray.recycle() + typedArray.recycle() + } } -} \ No newline at end of file diff --git a/app/src/main/java/dev/arkbuilders/arkmemo/ui/views/SwitchButton.kt b/app/src/main/java/dev/arkbuilders/arkmemo/ui/views/SwitchButton.kt index 355d1604..afaba3ef 100644 --- a/app/src/main/java/dev/arkbuilders/arkmemo/ui/views/SwitchButton.kt +++ b/app/src/main/java/dev/arkbuilders/arkmemo/ui/views/SwitchButton.kt @@ -20,13 +20,6 @@ import dev.arkbuilders.arkmemo.utils.dpToPx * SwitchButton. */ class SwitchButton : View, Checkable { - private val ANIMATE_STATE_NONE = 0 - private val ANIMATE_STATE_PENDING_DRAG = 1 - private val ANIMATE_STATE_DRAGING = 2 - private val ANIMATE_STATE_PENDING_RESET = 3 - private val ANIMATE_STATE_PENDING_SETTLE = 4 - private val ANIMATE_STATE_SWITCH = 5 - constructor(context: Context) : super(context) { init(context, null) } @@ -38,7 +31,7 @@ class SwitchButton : View, Checkable { constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super( context, attrs, - defStyleAttr + defStyleAttr, ) { init(context, attrs) } @@ -47,124 +40,152 @@ class SwitchButton : View, Checkable { context: Context, attrs: AttributeSet?, defStyleAttr: Int, - defStyleRes: Int + defStyleRes: Int, ) : super(context, attrs, defStyleAttr, defStyleRes) { init(context, attrs) } - override fun setPadding(left: Int, top: Int, right: Int, bottom: Int) { + override fun setPadding( + left: Int, + top: Int, + right: Int, + bottom: Int, + ) { super.setPadding(0, 0, 0, 0) } - private fun init(context: Context, attrs: AttributeSet?) { + private fun init( + context: Context, + attrs: AttributeSet?, + ) { var typedArray: TypedArray? = null if (attrs != null) { typedArray = context.obtainStyledAttributes(attrs, R.styleable.SwitchButton) } - shadowEffect = optBoolean( - typedArray, - R.styleable.SwitchButton_sb_shadow_effect, - true - ) - uncheckCircleColor = optColor( - typedArray, - R.styleable.SwitchButton_sb_uncheckcircle_color, - R.color.sb_unchecked_circle_color - ) - uncheckCircleWidth = optPixelSize( - typedArray, - R.styleable.SwitchButton_sb_uncheckcircle_width, - dpToPxInt(1.5f) - ) + shadowEffect = + optBoolean( + typedArray, + R.styleable.SwitchButton_sb_shadow_effect, + true, + ) + uncheckCircleColor = + optColor( + typedArray, + R.styleable.SwitchButton_sb_uncheckcircle_color, + R.color.sb_unchecked_circle_color, + ) + uncheckCircleWidth = + optPixelSize( + typedArray, + R.styleable.SwitchButton_sb_uncheckcircle_width, + dpToPxInt(1.5f), + ) uncheckCircleOffsetX = 10f.dpToPx() - uncheckCircleRadius = optPixelSize( - typedArray, - R.styleable.SwitchButton_sb_uncheckcircle_radius, - 4f.dpToPx() - ) + uncheckCircleRadius = + optPixelSize( + typedArray, + R.styleable.SwitchButton_sb_uncheckcircle_radius, + 4f.dpToPx(), + ) checkedLineOffsetX = 4f.dpToPx() checkedLineOffsetY = 4f.dpToPx() - shadowRadius = optPixelSize( - typedArray, - R.styleable.SwitchButton_sb_shadow_radius, - dpToPxInt(2.5f) - ) - shadowOffset = optPixelSize( - typedArray, - R.styleable.SwitchButton_sb_shadow_offset, - dpToPxInt(1.5f) - ) - shadowColor = optColor( - typedArray, - R.styleable.SwitchButton_sb_shadow_color, - R.color.sb_shadow_color - ) - uncheckColor = optColor( - typedArray, - R.styleable.SwitchButton_sb_uncheck_color, - R.color.sb_unchecked_color - ) - checkedColor = optColor( - typedArray, - R.styleable.SwitchButton_sb_checked_color, - R.color.sb_checked_color - ) - borderWidth = optPixelSize( - typedArray, - R.styleable.SwitchButton_sb_border_width, - dpToPxInt(1f) - ) - checkLineColor = optColor( - typedArray, - R.styleable.SwitchButton_sb_checkline_color, - Color.WHITE - ) - checkLineWidth = optPixelSize( - typedArray, - R.styleable.SwitchButton_sb_checkline_width, - dpToPxInt(1f) - ) + shadowRadius = + optPixelSize( + typedArray, + R.styleable.SwitchButton_sb_shadow_radius, + dpToPxInt(2.5f), + ) + shadowOffset = + optPixelSize( + typedArray, + R.styleable.SwitchButton_sb_shadow_offset, + dpToPxInt(1.5f), + ) + shadowColor = + optColor( + typedArray, + R.styleable.SwitchButton_sb_shadow_color, + R.color.sb_shadow_color, + ) + uncheckColor = + optColor( + typedArray, + R.styleable.SwitchButton_sb_uncheck_color, + R.color.sb_unchecked_color, + ) + checkedColor = + optColor( + typedArray, + R.styleable.SwitchButton_sb_checked_color, + R.color.sb_checked_color, + ) + borderWidth = + optPixelSize( + typedArray, + R.styleable.SwitchButton_sb_border_width, + dpToPxInt(1f), + ) + checkLineColor = + optColor( + typedArray, + R.styleable.SwitchButton_sb_checkline_color, + Color.WHITE, + ) + checkLineWidth = + optPixelSize( + typedArray, + R.styleable.SwitchButton_sb_checkline_width, + dpToPxInt(1f), + ) checkLineLength = 6f.dpToPx() - val buttonColor = optColor( - typedArray, - R.styleable.SwitchButton_sb_button_color, - Color.WHITE - ) - uncheckButtonColor = optColor( - typedArray, - R.styleable.SwitchButton_sb_uncheckbutton_color, - buttonColor - ) - checkedButtonColor = optColor( - typedArray, - R.styleable.SwitchButton_sb_checkedbutton_color, - buttonColor - ) - val effectDuration = optInt( - typedArray, - R.styleable.SwitchButton_sb_effect_duration, - 300 - ) - isChecked = optBoolean( - typedArray, - R.styleable.SwitchButton_sb_checked, - false - ) - showIndicator = optBoolean( - typedArray, - R.styleable.SwitchButton_sb_show_indicator, - true - ) - background = optColor( - typedArray, - R.styleable.SwitchButton_sb_background, - Color.WHITE - ) - enableEffect = optBoolean( - typedArray, - R.styleable.SwitchButton_sb_enable_effect, - true - ) + val buttonColor = + optColor( + typedArray, + R.styleable.SwitchButton_sb_button_color, + Color.WHITE, + ) + uncheckButtonColor = + optColor( + typedArray, + R.styleable.SwitchButton_sb_uncheckbutton_color, + buttonColor, + ) + checkedButtonColor = + optColor( + typedArray, + R.styleable.SwitchButton_sb_checkedbutton_color, + buttonColor, + ) + val effectDuration = + optInt( + typedArray, + R.styleable.SwitchButton_sb_effect_duration, + 300, + ) + isChecked = + optBoolean( + typedArray, + R.styleable.SwitchButton_sb_checked, + false, + ) + showIndicator = + optBoolean( + typedArray, + R.styleable.SwitchButton_sb_show_indicator, + true, + ) + background = + optColor( + typedArray, + R.styleable.SwitchButton_sb_background, + Color.WHITE, + ) + enableEffect = + optBoolean( + typedArray, + R.styleable.SwitchButton_sb_enable_effect, + true, + ) typedArray?.recycle() paint = Paint(Paint.ANTI_ALIAS_FLAG) buttonPaint = Paint(Paint.ANTI_ALIAS_FLAG) @@ -172,8 +193,9 @@ class SwitchButton : View, Checkable { if (shadowEffect) { buttonPaint.setShadowLayer( shadowRadius.toFloat(), - 0f, shadowOffset.toFloat(), - shadowColor + 0f, + shadowOffset.toFloat(), + shadowColor, ) } viewState = ViewState() @@ -189,25 +211,33 @@ class SwitchButton : View, Checkable { setLayerType(LAYER_TYPE_SOFTWARE, null) } - override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { + override fun onMeasure( + widthMeasureSpec: Int, + heightMeasureSpec: Int, + ) { var newWidthSpec = widthMeasureSpec var newHeightSpec = heightMeasureSpec val widthMode = MeasureSpec.getMode(newWidthSpec) val heightMode = MeasureSpec.getMode(newHeightSpec) - if (widthMode == MeasureSpec.UNSPECIFIED - || widthMode == MeasureSpec.AT_MOST + if (widthMode == MeasureSpec.UNSPECIFIED || + widthMode == MeasureSpec.AT_MOST ) { newWidthSpec = MeasureSpec.makeMeasureSpec(DEFAULT_WIDTH, MeasureSpec.EXACTLY) } - if (heightMode == MeasureSpec.UNSPECIFIED - || heightMode == MeasureSpec.AT_MOST + if (heightMode == MeasureSpec.UNSPECIFIED || + heightMode == MeasureSpec.AT_MOST ) { newHeightSpec = MeasureSpec.makeMeasureSpec(DEFAULT_HEIGHT, MeasureSpec.EXACTLY) } super.onMeasure(newWidthSpec, newHeightSpec) } - override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) { + override fun onSizeChanged( + w: Int, + h: Int, + oldw: Int, + oldh: Int, + ) { super.onSizeChanged(w, h, oldw, oldh) val viewPadding = (shadowRadius + shadowOffset).coerceAtLeast(borderWidth).toFloat() height = h - viewPadding - viewPadding @@ -260,40 +290,58 @@ class SwitchButton : View, Checkable { paint.color = background drawRoundRect( canvas, - left, top, right, bottom, - viewRadius, paint + left, + top, + right, + bottom, + viewRadius, + paint, ) paint.style = Paint.Style.STROKE paint.color = uncheckColor drawRoundRect( canvas, - left, top, right, bottom, - viewRadius, paint + left, + top, + right, + bottom, + viewRadius, + paint, ) if (showIndicator) { drawUncheckIndicator(canvas) } - val des = viewState.radius * .5f //[0-backgroundRadius*0.5f] + val des = viewState.radius * .5f // [0-backgroundRadius*0.5f] paint.style = Paint.Style.STROKE paint.color = viewState.checkStateColor paint.strokeWidth = borderWidth + des * 2f drawRoundRect( canvas, - left + des, top + des, right - des, bottom - des, - viewRadius, paint + left + des, + top + des, + right - des, + bottom - des, + viewRadius, + paint, ) paint.style = Paint.Style.FILL paint.strokeWidth = 1f drawArc( canvas, - left, top, - left + 2 * viewRadius, top + 2 * viewRadius, - 90f, 180f, paint + left, + top, + left + 2 * viewRadius, + top + 2 * viewRadius, + 90f, + 180f, + paint, ) canvas.drawRect( - left + viewRadius, top, - viewState.buttonX, top + 2 * viewRadius, - paint + left + viewRadius, + top, + viewState.buttonX, + top + 2 * viewRadius, + paint, ) if (showIndicator) { drawCheckedIndicator(canvas) @@ -313,14 +361,17 @@ class SwitchButton : View, Checkable { ex: Float = left + viewRadius - checkedLineOffsetY, ey: Float = centerY + checkLineLength, - paint: Paint = this.paint + paint: Paint = this.paint, ) { paint.style = Paint.Style.STROKE paint.color = color paint.strokeWidth = lineWidth canvas.drawLine( - sx, sy, ex, ey, - paint + sx, + sy, + ex, + ey, + paint, ) } @@ -329,9 +380,10 @@ class SwitchButton : View, Checkable { canvas, uncheckCircleColor, uncheckCircleWidth.toFloat(), - right - uncheckCircleOffsetX, centerY, + right - uncheckCircleOffsetX, + centerY, uncheckCircleRadius, - paint + paint, ) } @@ -339,9 +391,10 @@ class SwitchButton : View, Checkable { canvas: Canvas, color: Int, lineWidth: Float, - centerX: Float, centerY: Float, + centerX: Float, + centerY: Float, radius: Float, - paint: Paint + paint: Paint, ) { paint.style = Paint.Style.STROKE paint.color = color @@ -351,31 +404,51 @@ class SwitchButton : View, Checkable { private fun drawArc( canvas: Canvas, - left: Float, top: Float, - right: Float, bottom: Float, - startAngle: Float, sweepAngle: Float, - paint: Paint + left: Float, + top: Float, + right: Float, + bottom: Float, + startAngle: Float, + sweepAngle: Float, + paint: Paint, ) { canvas.drawArc( - left, top, right, bottom, - startAngle, sweepAngle, true, paint + left, + top, + right, + bottom, + startAngle, + sweepAngle, + true, + paint, ) } private fun drawRoundRect( canvas: Canvas, - left: Float, top: Float, - right: Float, bottom: Float, + left: Float, + top: Float, + right: Float, + bottom: Float, backgroundRadius: Float, - paint: Paint + paint: Paint, ) { canvas.drawRoundRect( - left, top, right, bottom, - backgroundRadius, backgroundRadius, paint + left, + top, + right, + bottom, + backgroundRadius, + backgroundRadius, + paint, ) } - private fun drawButton(canvas: Canvas, x: Float, y: Float) { + private fun drawButton( + canvas: Canvas, + x: Float, + y: Float, + ) { canvas.drawCircle(x, y, buttonRadius, buttonPaint) paint.style = Paint.Style.STROKE paint.strokeWidth = 1f @@ -403,7 +476,10 @@ class SwitchButton : View, Checkable { toggle(animate, true) } - private fun toggle(animate: Boolean, broadcast: Boolean) { + private fun toggle( + animate: Boolean, + broadcast: Boolean, + ) { if (!isEnabled) { return } @@ -471,20 +547,25 @@ class SwitchButton : View, Checkable { if (isPendingDragState) { var fraction = eventX / getWidth() fraction = 0f.coerceAtLeast(1f.coerceAtMost(fraction)) - viewState.buttonX = (buttonMinX - + (buttonMaxX - buttonMinX) - * fraction) + viewState.buttonX = ( + buttonMinX + + (buttonMaxX - buttonMinX) * + fraction + ) } else if (isDragState) { var fraction = eventX / getWidth() fraction = 0f.coerceAtLeast(1f.coerceAtMost(fraction)) - viewState.buttonX = (buttonMinX - + (buttonMaxX - buttonMinX) - * fraction) - viewState.checkStateColor = argbEvaluator.evaluate( - fraction, - uncheckColor, - checkedColor - ) as Int + viewState.buttonX = ( + buttonMinX + + (buttonMaxX - buttonMinX) * + fraction + ) + viewState.checkStateColor = + argbEvaluator.evaluate( + fraction, + uncheckColor, + checkedColor, + ) as Int postInvalidate() } } @@ -513,8 +594,8 @@ class SwitchButton : View, Checkable { MotionEvent.ACTION_CANCEL -> { isTouchingDown = false removeCallbacks(postPendingDrag) - if (isPendingDragState - || isDragState + if (isPendingDragState || + isDragState ) { pendingCancelDragState() } @@ -526,10 +607,12 @@ class SwitchButton : View, Checkable { private val isInAnimating: Boolean get() = animateState != ANIMATE_STATE_NONE private val isPendingDragState: Boolean - get() = (animateState == ANIMATE_STATE_PENDING_DRAG - || animateState == ANIMATE_STATE_PENDING_RESET) + get() = ( + animateState == ANIMATE_STATE_PENDING_DRAG || + animateState == ANIMATE_STATE_PENDING_RESET + ) private val isDragState: Boolean - get() = animateState == ANIMATE_STATE_DRAGING + get() = animateState == ANIMATE_STATE_DRAGGING fun setShadowEffect(shadowEffect: Boolean) { if (this.shadowEffect == shadowEffect) { @@ -539,14 +622,16 @@ class SwitchButton : View, Checkable { if (this.shadowEffect) { buttonPaint.setShadowLayer( shadowRadius.toFloat(), - 0f, shadowOffset.toFloat(), - shadowColor + 0f, + shadowOffset.toFloat(), + shadowColor, ) } else { buttonPaint.setShadowLayer( 0f, - 0f, 0f, - 0 + 0f, + 0f, + 0, ) } } @@ -611,13 +696,18 @@ class SwitchButton : View, Checkable { } override fun setOnClickListener(l: OnClickListener?) {} + override fun setOnLongClickListener(l: OnLongClickListener?) {} + fun setOnCheckedChangeListener(l: OnCheckedChangeListener?) { onCheckedChangeListener = l } interface OnCheckedChangeListener { - fun onCheckedChanged(view: SwitchButton?, isChecked: Boolean) + fun onCheckedChanged( + view: SwitchButton?, + isChecked: Boolean, + ) } private var shadowRadius = 0 @@ -675,147 +765,175 @@ class SwitchButton : View, Checkable { private var isEventBroadcast = false private var onCheckedChangeListener: OnCheckedChangeListener? = null private var touchDownTime: Long = 0 - private val postPendingDrag: Runnable = Runnable { - if (!isInAnimating) { - pendingDragState() - } - } - private val animatorUpdateListener: AnimatorUpdateListener = object : AnimatorUpdateListener { - override fun onAnimationUpdate(animation: ValueAnimator) { - val value = animation.animatedValue as Float - when (animateState) { - ANIMATE_STATE_PENDING_SETTLE -> { - run { - viewState.checkedLineColor = argbEvaluator.evaluate( - value, - beforeState.checkedLineColor, - afterState.checkedLineColor - ) as Int - viewState.radius = (beforeState.radius - + (afterState.radius - beforeState.radius) * value) - if (animateState != ANIMATE_STATE_PENDING_DRAG) { - viewState.buttonX = (beforeState.buttonX - + (afterState.buttonX - beforeState.buttonX) * value) + private val postPendingDrag: Runnable = + Runnable { + if (!isInAnimating) { + pendingDragState() + } + } + private val animatorUpdateListener: AnimatorUpdateListener = + object : AnimatorUpdateListener { + override fun onAnimationUpdate(animation: ValueAnimator) { + val value = animation.animatedValue as Float + when (animateState) { + ANIMATE_STATE_PENDING_SETTLE -> { + run { + viewState.checkedLineColor = + argbEvaluator.evaluate( + value, + beforeState.checkedLineColor, + afterState.checkedLineColor, + ) as Int + viewState.radius = ( + beforeState.radius + + (afterState.radius - beforeState.radius) * value + ) + if (animateState != ANIMATE_STATE_PENDING_DRAG) { + viewState.buttonX = ( + beforeState.buttonX + + (afterState.buttonX - beforeState.buttonX) * value + ) + } + viewState.checkStateColor = + argbEvaluator.evaluate( + value, + beforeState.checkStateColor, + afterState.checkStateColor, + ) as Int } - viewState.checkStateColor = argbEvaluator.evaluate( - value, - beforeState.checkStateColor, - afterState.checkStateColor - ) as Int } - } - ANIMATE_STATE_PENDING_RESET -> { - run { - viewState.checkedLineColor = argbEvaluator.evaluate( - value, - beforeState.checkedLineColor, - afterState.checkedLineColor - ) as Int - viewState.radius = (beforeState.radius - + (afterState.radius - beforeState.radius) * value) - if (animateState != ANIMATE_STATE_PENDING_DRAG) { - viewState.buttonX = (beforeState.buttonX - + (afterState.buttonX - beforeState.buttonX) * value) + ANIMATE_STATE_PENDING_RESET -> { + run { + viewState.checkedLineColor = + argbEvaluator.evaluate( + value, + beforeState.checkedLineColor, + afterState.checkedLineColor, + ) as Int + viewState.radius = ( + beforeState.radius + + (afterState.radius - beforeState.radius) * value + ) + if (animateState != ANIMATE_STATE_PENDING_DRAG) { + viewState.buttonX = ( + beforeState.buttonX + + (afterState.buttonX - beforeState.buttonX) * value + ) + } + viewState.checkStateColor = + argbEvaluator.evaluate( + value, + beforeState.checkStateColor, + afterState.checkStateColor, + ) as Int } - viewState.checkStateColor = argbEvaluator.evaluate( - value, - beforeState.checkStateColor, - afterState.checkStateColor - ) as Int } - } - ANIMATE_STATE_PENDING_DRAG -> { - viewState.checkedLineColor = argbEvaluator.evaluate( - value, - beforeState.checkedLineColor, - afterState.checkedLineColor - ) as Int - viewState.radius = (beforeState.radius - + (afterState.radius - beforeState.radius) * value) - if (animateState != ANIMATE_STATE_PENDING_DRAG) { - viewState.buttonX = (beforeState.buttonX - + (afterState.buttonX - beforeState.buttonX) * value) + ANIMATE_STATE_PENDING_DRAG -> { + viewState.checkedLineColor = + argbEvaluator.evaluate( + value, + beforeState.checkedLineColor, + afterState.checkedLineColor, + ) as Int + viewState.radius = ( + beforeState.radius + + (afterState.radius - beforeState.radius) * value + ) + if (animateState != ANIMATE_STATE_PENDING_DRAG) { + viewState.buttonX = ( + beforeState.buttonX + + (afterState.buttonX - beforeState.buttonX) * value + ) + } + viewState.checkStateColor = + argbEvaluator.evaluate( + value, + beforeState.checkStateColor, + afterState.checkStateColor, + ) as Int } - viewState.checkStateColor = argbEvaluator.evaluate( - value, - beforeState.checkStateColor, - afterState.checkStateColor - ) as Int - } - ANIMATE_STATE_SWITCH -> { - viewState.buttonX = (beforeState.buttonX - + (afterState.buttonX - beforeState.buttonX) * value) - val fraction = (viewState.buttonX - buttonMinX) / (buttonMaxX - buttonMinX) - viewState.checkStateColor = argbEvaluator.evaluate( - fraction, - uncheckColor, - checkedColor - ) as Int - viewState.radius = fraction * viewRadius - viewState.checkedLineColor = argbEvaluator.evaluate( - fraction, - Color.TRANSPARENT, - checkLineColor - ) as Int - } + ANIMATE_STATE_SWITCH -> { + viewState.buttonX = ( + beforeState.buttonX + + (afterState.buttonX - beforeState.buttonX) * value + ) + val fraction = (viewState.buttonX - buttonMinX) / (buttonMaxX - buttonMinX) + viewState.checkStateColor = + argbEvaluator.evaluate( + fraction, + uncheckColor, + checkedColor, + ) as Int + viewState.radius = fraction * viewRadius + viewState.checkedLineColor = + argbEvaluator.evaluate( + fraction, + Color.TRANSPARENT, + checkLineColor, + ) as Int + } - ANIMATE_STATE_DRAGING -> { - } + ANIMATE_STATE_DRAGGING -> { + } - ANIMATE_STATE_NONE -> {} - else -> { + ANIMATE_STATE_NONE -> {} + else -> { + } } + postInvalidate() } - postInvalidate() } - } - private val animatorListener: Animator.AnimatorListener = object : Animator.AnimatorListener { - override fun onAnimationStart(animation: Animator) {} - override fun onAnimationEnd(animation: Animator) { - when (animateState) { - ANIMATE_STATE_DRAGING -> {} - ANIMATE_STATE_PENDING_DRAG -> { - animateState = ANIMATE_STATE_DRAGING - viewState.checkedLineColor = Color.TRANSPARENT - viewState.radius = viewRadius - postInvalidate() - } + private val animatorListener: Animator.AnimatorListener = + object : Animator.AnimatorListener { + override fun onAnimationStart(animation: Animator) {} + + override fun onAnimationEnd(animation: Animator) { + when (animateState) { + ANIMATE_STATE_DRAGGING -> {} + ANIMATE_STATE_PENDING_DRAG -> { + animateState = ANIMATE_STATE_DRAGGING + viewState.checkedLineColor = Color.TRANSPARENT + viewState.radius = viewRadius + postInvalidate() + } - ANIMATE_STATE_PENDING_RESET -> { - animateState = ANIMATE_STATE_NONE - postInvalidate() - } + ANIMATE_STATE_PENDING_RESET -> { + animateState = ANIMATE_STATE_NONE + postInvalidate() + } - ANIMATE_STATE_PENDING_SETTLE -> { - animateState = ANIMATE_STATE_NONE - postInvalidate() - broadcastEvent() - } + ANIMATE_STATE_PENDING_SETTLE -> { + animateState = ANIMATE_STATE_NONE + postInvalidate() + broadcastEvent() + } - ANIMATE_STATE_SWITCH -> { - isChecked = !isChecked - animateState = ANIMATE_STATE_NONE - postInvalidate() - broadcastEvent() - } + ANIMATE_STATE_SWITCH -> { + isChecked = !isChecked + animateState = ANIMATE_STATE_NONE + postInvalidate() + broadcastEvent() + } - ANIMATE_STATE_NONE -> {} - else -> {} + ANIMATE_STATE_NONE -> {} + else -> {} + } } - } - override fun onAnimationCancel(animation: Animator) {} - override fun onAnimationRepeat(animation: Animator) {} - } + override fun onAnimationCancel(animation: Animator) {} + + override fun onAnimationRepeat(animation: Animator) {} + } private class ViewState { var buttonX = 0f var checkStateColor = 0 var checkedLineColor = 0 var radius = 0f + fun copy(source: ViewState) { buttonX = source.buttonX checkStateColor = source.checkStateColor @@ -825,6 +943,12 @@ class SwitchButton : View, Checkable { } companion object { + private const val ANIMATE_STATE_NONE = 0 + private const val ANIMATE_STATE_PENDING_DRAG = 1 + private const val ANIMATE_STATE_DRAGGING = 2 + private const val ANIMATE_STATE_PENDING_RESET = 3 + private const val ANIMATE_STATE_PENDING_SETTLE = 4 + private const val ANIMATE_STATE_SWITCH = 5 private val DEFAULT_WIDTH = dpToPxInt(50f) private val DEFAULT_HEIGHT = dpToPxInt(31f) @@ -835,7 +959,7 @@ class SwitchButton : View, Checkable { private fun optInt( typedArray: TypedArray?, index: Int, - def: Int + def: Int, ): Int { return typedArray?.getInt(index, def) ?: def } @@ -843,7 +967,7 @@ class SwitchButton : View, Checkable { private fun optPixelSize( typedArray: TypedArray?, index: Int, - def: Float + def: Float, ): Float { return typedArray?.getDimension(index, def) ?: def } @@ -851,7 +975,7 @@ class SwitchButton : View, Checkable { private fun optPixelSize( typedArray: TypedArray?, index: Int, - def: Int + def: Int, ): Int { return typedArray?.getDimensionPixelOffset(index, def) ?: def } @@ -859,7 +983,7 @@ class SwitchButton : View, Checkable { private fun optColor( typedArray: TypedArray?, index: Int, - def: Int + def: Int, ): Int { return typedArray?.getColor(index, def) ?: def } @@ -867,9 +991,9 @@ class SwitchButton : View, Checkable { private fun optBoolean( typedArray: TypedArray?, index: Int, - def: Boolean + def: Boolean, ): Boolean { return typedArray?.getBoolean(index, def) ?: def } } -} \ No newline at end of file +} diff --git a/app/src/main/java/dev/arkbuilders/arkmemo/ui/views/WaveView.kt b/app/src/main/java/dev/arkbuilders/arkmemo/ui/views/WaveView.kt index 4a2aed3d..20ba09aa 100644 --- a/app/src/main/java/dev/arkbuilders/arkmemo/ui/views/WaveView.kt +++ b/app/src/main/java/dev/arkbuilders/arkmemo/ui/views/WaveView.kt @@ -8,8 +8,7 @@ import android.graphics.Rect import android.util.AttributeSet import android.view.View -class WaveView(context: Context, attrs: AttributeSet): View(context, attrs) { - +class WaveView(context: Context, attrs: AttributeSet) : View(context, attrs) { var waveColor = Color.WHITE private val paint by lazy { Paint().also { @@ -25,8 +24,15 @@ class WaveView(context: Context, attrs: AttributeSet): View(context, attrs) { super.onDraw(canvas) if (bars.isNotEmpty()) { bars.forEach { - canvas.drawRoundRect(it.left.toFloat(), it.top.toFloat(), it.right.toFloat(), - it.bottom.toFloat(), radius, radius, paint) + canvas.drawRoundRect( + it.left.toFloat(), + it.top.toFloat(), + it.right.toFloat(), + it.bottom.toFloat(), + radius, + radius, + paint, + ) } } } diff --git a/app/src/main/java/dev/arkbuilders/arkmemo/ui/views/WebLinkTextView.kt b/app/src/main/java/dev/arkbuilders/arkmemo/ui/views/WebLinkTextView.kt index 0b8882b2..ba7c90e5 100644 --- a/app/src/main/java/dev/arkbuilders/arkmemo/ui/views/WebLinkTextView.kt +++ b/app/src/main/java/dev/arkbuilders/arkmemo/ui/views/WebLinkTextView.kt @@ -9,25 +9,26 @@ import dev.arkbuilders.arkmemo.R import dev.arkbuilders.arkmemo.databinding.LayoutWebLinkTextBinding import dev.arkbuilders.arkmemo.utils.gone -class WebLinkTextView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) : +class WebLinkTextView + @JvmOverloads + constructor(context: Context, attrs: AttributeSet? = null) : ConstraintLayout(context, attrs) { + init { + val binding = LayoutWebLinkTextBinding.inflate(LayoutInflater.from(context), this, true) + val typedArray: TypedArray = + context.obtainStyledAttributes(attrs, R.styleable.WebLinkTextView) + val textResId = typedArray.getText(R.styleable.WebLinkTextView_web_link_text) + val iconResId = typedArray.getResourceId(R.styleable.WebLinkTextView_web_link_icon, 0) + textResId?.let { + binding.tvText.text = textResId + } - init { - val binding = LayoutWebLinkTextBinding.inflate(LayoutInflater.from(context), this, true) - val typedArray: TypedArray = - context.obtainStyledAttributes(attrs, R.styleable.WebLinkTextView) - val textResId = typedArray.getText(R.styleable.WebLinkTextView_web_link_text) - val iconResId = typedArray.getResourceId(R.styleable.WebLinkTextView_web_link_icon, 0) - textResId?.let { - binding.tvText.text = textResId - } + if (iconResId != 0) { + binding.ivIcon.setImageResource(iconResId) + } else { + binding.ivIcon.gone() + } - if (iconResId != 0) { - binding.ivIcon.setImageResource(iconResId) - } else { - binding.ivIcon.gone() + typedArray.recycle() } - - typedArray.recycle() } -} \ No newline at end of file diff --git a/app/src/main/java/dev/arkbuilders/arkmemo/utils/BundleExt.kt b/app/src/main/java/dev/arkbuilders/arkmemo/utils/BundleExt.kt index 2176cfa7..eb9664ed 100644 --- a/app/src/main/java/dev/arkbuilders/arkmemo/utils/BundleExt.kt +++ b/app/src/main/java/dev/arkbuilders/arkmemo/utils/BundleExt.kt @@ -3,11 +3,14 @@ package dev.arkbuilders.arkmemo.utils import android.os.Build import android.os.Bundle -fun Bundle?.getParcelableCompat(key: String, clazz: Class): T? { +fun Bundle?.getParcelableCompat( + key: String, + clazz: Class, +): T? { return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { this?.getParcelable(key, clazz) } else { @Suppress("DEPRECATION") this?.getParcelable(key) } -} \ No newline at end of file +} diff --git a/app/src/main/java/dev/arkbuilders/arkmemo/utils/ContextExt.kt b/app/src/main/java/dev/arkbuilders/arkmemo/utils/ContextExt.kt index 84c0f377..b688d880 100644 --- a/app/src/main/java/dev/arkbuilders/arkmemo/utils/ContextExt.kt +++ b/app/src/main/java/dev/arkbuilders/arkmemo/utils/ContextExt.kt @@ -10,7 +10,8 @@ import androidx.activity.result.ActivityResultLauncher fun Context.openLink(url: String) { try { startActivity( - Intent(Intent.ACTION_VIEW).setData(Uri.parse(url))) + Intent(Intent.ACTION_VIEW).setData(Uri.parse(url)), + ) } catch (e: Exception) { Log.e("openLink", " exception: " + e.message) } @@ -18,10 +19,11 @@ fun Context.openLink(url: String) { fun Context.openAppSettings(activityLauncher: ActivityResultLauncher? = null) { try { - val settingIntent = Intent().apply { - action = Settings.ACTION_APPLICATION_DETAILS_SETTINGS - data = Uri.fromParts("package", this@openAppSettings.packageName, null) - } + val settingIntent = + Intent().apply { + action = Settings.ACTION_APPLICATION_DETAILS_SETTINGS + data = Uri.fromParts("package", this@openAppSettings.packageName, null) + } activityLauncher?.let { activityLauncher.launch(settingIntent) } ?: { @@ -30,5 +32,4 @@ fun Context.openAppSettings(activityLauncher: ActivityResultLauncher? = } catch (e: Exception) { Log.e("openAppSettings", " exception: " + e.message) } - -} \ No newline at end of file +} diff --git a/app/src/main/java/dev/arkbuilders/arkmemo/utils/CoroutineExt.kt b/app/src/main/java/dev/arkbuilders/arkmemo/utils/CoroutineExt.kt index f67bfa8a..894f52e0 100644 --- a/app/src/main/java/dev/arkbuilders/arkmemo/utils/CoroutineExt.kt +++ b/app/src/main/java/dev/arkbuilders/arkmemo/utils/CoroutineExt.kt @@ -9,17 +9,18 @@ import kotlinx.coroutines.isActive fun CoroutineScope.launchPeriodicAsync( repeatMillis: Long, repeatCondition: Boolean, - action: () -> Unit + action: () -> Unit, ): Deferred { - val deferred = this.async { - if (repeatMillis > 0) { - while (this.isActive && repeatCondition) { + val deferred = + this.async { + if (repeatMillis > 0) { + while (this.isActive && repeatCondition) { + action() + delay(repeatMillis) + } + } else { action() - delay(repeatMillis) } - } else { - action() } - } return deferred -} \ No newline at end of file +} diff --git a/app/src/main/java/dev/arkbuilders/arkmemo/utils/DimensionUtils.kt b/app/src/main/java/dev/arkbuilders/arkmemo/utils/DimensionUtils.kt index c3207226..b0ae33eb 100644 --- a/app/src/main/java/dev/arkbuilders/arkmemo/utils/DimensionUtils.kt +++ b/app/src/main/java/dev/arkbuilders/arkmemo/utils/DimensionUtils.kt @@ -7,7 +7,7 @@ fun Int.dpToPx(): Int { return TypedValue.applyDimension( TypedValue.COMPLEX_UNIT_DIP, this.toFloat(), - Resources.getSystem().displayMetrics + Resources.getSystem().displayMetrics, ).toInt() } @@ -15,6 +15,6 @@ fun Float.dpToPx(): Float { return TypedValue.applyDimension( TypedValue.COMPLEX_UNIT_DIP, this, - Resources.getSystem().displayMetrics + Resources.getSystem().displayMetrics, ) } diff --git a/app/src/main/java/dev/arkbuilders/arkmemo/utils/GraphicBrushExt.kt b/app/src/main/java/dev/arkbuilders/arkmemo/utils/GraphicBrushExt.kt index 535b0ded..f42cb1f1 100644 --- a/app/src/main/java/dev/arkbuilders/arkmemo/utils/GraphicBrushExt.kt +++ b/app/src/main/java/dev/arkbuilders/arkmemo/utils/GraphicBrushExt.kt @@ -18,52 +18,54 @@ import dev.arkbuilders.arkmemo.ui.adapters.BrushSizeSmall import dev.arkbuilders.arkmemo.ui.adapters.BrushSizeTiny fun Int.getStrokeSize(): Float { - return when(this) { - Size.TINY.id -> Size.TINY.value - Size.SMALL.id -> Size.SMALL.value + return when (this) { + Size.TINY.id -> Size.TINY.value + Size.SMALL.id -> Size.SMALL.value Size.MEDIUM.id -> Size.MEDIUM.value - Size.LARGE.id -> Size.LARGE.value - Size.HUGE.id -> Size.HUGE.value - else -> Size.TINY.value + Size.LARGE.id -> Size.LARGE.value + Size.HUGE.id -> Size.HUGE.value + else -> Size.TINY.value } } fun Float.getBrushSizeId(): Int { - return when(this) { + return when (this) { Size.TINY.value -> Size.TINY.id Size.SMALL.value -> Size.SMALL.id Size.MEDIUM.value -> Size.MEDIUM.id Size.LARGE.value -> Size.LARGE.id Size.HUGE.value -> Size.HUGE.id - else -> { Size.TINY.id } + else -> { + Size.TINY.id + } } } fun Int.getStrokeColor(): String { return when (this) { - Color.BLACK.code -> Color.BLACK.value - Color.GRAY.code -> Color.GRAY.value - Color.RED.code -> Color.RED.value - Color.GREEN.code -> Color.GREEN.value - Color.BLUE.code -> Color.BLUE.value + Color.BLACK.code -> Color.BLACK.value + Color.GRAY.code -> Color.GRAY.value + Color.RED.code -> Color.RED.value + Color.GREEN.code -> Color.GREEN.value + Color.BLUE.code -> Color.BLUE.value Color.PURPLE.code -> Color.PURPLE.value Color.ORANGE.code -> Color.ORANGE.value - Color.WHITE.code -> Color.WHITE.value - else -> Color.BLACK.value + Color.WHITE.code -> Color.WHITE.value + else -> Color.BLACK.value } } fun String.getColorCode(): Int { return when (this) { - Color.BLACK.value -> Color.BLACK.code - Color.GRAY.value -> Color.GRAY.code - Color.RED.value -> Color.RED.code - Color.GREEN.value -> Color.GREEN.code - Color.BLUE.value -> Color.BLUE.code + Color.BLACK.value -> Color.BLACK.code + Color.GRAY.value -> Color.GRAY.code + Color.RED.value -> Color.RED.code + Color.GREEN.value -> Color.GREEN.code + Color.BLUE.value -> Color.BLUE.code Color.PURPLE.value -> Color.PURPLE.code Color.ORANGE.value -> Color.ORANGE.code - Color.WHITE.value -> Color.WHITE.code - else -> Color.BLACK.code + Color.WHITE.value -> Color.WHITE.code + else -> Color.BLACK.code } } @@ -80,11 +82,11 @@ fun BrushColor.getColorCode(): Int { } fun BrushSize.getBrushSize(): Float { - return when(this) { + return when (this) { is BrushSizeTiny -> Size.TINY.value is BrushSizeSmall -> Size.SMALL.value is BrushSizeMedium -> Size.MEDIUM.value is BrushSizeLarge -> Size.LARGE.value is BrushSizeHuge -> Size.HUGE.value } -} \ No newline at end of file +} diff --git a/app/src/main/java/dev/arkbuilders/arkmemo/utils/NoteExt.kt b/app/src/main/java/dev/arkbuilders/arkmemo/utils/NoteExt.kt index 07662da0..4f9fb030 100644 --- a/app/src/main/java/dev/arkbuilders/arkmemo/utils/NoteExt.kt +++ b/app/src/main/java/dev/arkbuilders/arkmemo/utils/NoteExt.kt @@ -19,7 +19,8 @@ fun Note.getAutoTitle(context: Context? = null): String { is GraphicNote -> { title.ifEmpty { String.format( - context.getString(R.string.ark_memo_graphic_note), resource?.id + context.getString(R.string.ark_memo_graphic_note), + resource?.id, ) } } @@ -27,12 +28,17 @@ fun Note.getAutoTitle(context: Context? = null): String { is VoiceNote -> { title.ifEmpty { String.format( - context.getString(R.string.ark_memo_voice_note), resource?.id + context.getString(R.string.ark_memo_voice_note), + resource?.id, ) } } - else -> { "" } + else -> { + "" + } } - } else { "" } -} \ No newline at end of file + } else { + "" + } +} diff --git a/app/src/main/java/dev/arkbuilders/arkmemo/utils/Permission.kt b/app/src/main/java/dev/arkbuilders/arkmemo/utils/Permission.kt index 3bd36191..fa06017c 100644 --- a/app/src/main/java/dev/arkbuilders/arkmemo/utils/Permission.kt +++ b/app/src/main/java/dev/arkbuilders/arkmemo/utils/Permission.kt @@ -4,7 +4,10 @@ import android.content.Context import android.content.pm.PackageManager object Permission { - fun hasPermission(context: Context, permission: String): Boolean { + fun hasPermission( + context: Context, + permission: String, + ): Boolean { return context.checkSelfPermission(permission) == PackageManager.PERMISSION_GRANTED } -} \ No newline at end of file +} diff --git a/app/src/main/java/dev/arkbuilders/arkmemo/utils/StringExt.kt b/app/src/main/java/dev/arkbuilders/arkmemo/utils/StringExt.kt index 3e1cb381..85316f29 100644 --- a/app/src/main/java/dev/arkbuilders/arkmemo/utils/StringExt.kt +++ b/app/src/main/java/dev/arkbuilders/arkmemo/utils/StringExt.kt @@ -1,6 +1,9 @@ package dev.arkbuilders.arkmemo.utils -fun String.insertStringAtPosition(stringToInsert: String, position: Int): String { +fun String.insertStringAtPosition( + stringToInsert: String, + position: Int, +): String { if (position < 0 || position > this.length) { throw IndexOutOfBoundsException("Position is out of bounds of the string") } diff --git a/app/src/main/java/dev/arkbuilders/arkmemo/utils/TextViewExt.kt b/app/src/main/java/dev/arkbuilders/arkmemo/utils/TextViewExt.kt index b2afcb5c..633a24ed 100644 --- a/app/src/main/java/dev/arkbuilders/arkmemo/utils/TextViewExt.kt +++ b/app/src/main/java/dev/arkbuilders/arkmemo/utils/TextViewExt.kt @@ -11,7 +11,6 @@ import androidx.core.content.ContextCompat.getColor import dev.arkbuilders.arkmemo.R fun TextView.highlightWord(word: String) { - val textString = this.text.toString() val wordToSpan = SpannableString(textString) val startIndex = textString.lowercase().indexOf(word.lowercase()) @@ -19,14 +18,21 @@ fun TextView.highlightWord(word: String) { if (startIndex == -1) return val endIndex = startIndex + word.length - wordToSpan.setSpan(BackgroundColorSpan( - getColor(this.context, R.color.warning_300)), - startIndex, endIndex, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) + wordToSpan.setSpan( + BackgroundColorSpan( + getColor(this.context, R.color.warning_300), + ), + startIndex, + endIndex, + Spannable.SPAN_EXCLUSIVE_EXCLUSIVE, + ) text = wordToSpan } -fun TextView.setDrawableColor(@ColorInt color: Int) { +fun TextView.setDrawableColor( + @ColorInt color: Int, +) { compoundDrawables.filterNotNull().forEach { it.colorFilter = PorterDuffColorFilter(color, PorterDuff.Mode.SRC_IN) } -} \ No newline at end of file +} diff --git a/app/src/main/java/dev/arkbuilders/arkmemo/utils/Utils.kt b/app/src/main/java/dev/arkbuilders/arkmemo/utils/Utils.kt index 892c6428..5604391b 100644 --- a/app/src/main/java/dev/arkbuilders/arkmemo/utils/Utils.kt +++ b/app/src/main/java/dev/arkbuilders/arkmemo/utils/Utils.kt @@ -19,7 +19,6 @@ import java.nio.file.Path import kotlin.io.path.extension import kotlin.io.path.forEachLine - fun Fragment.observeSaveResult(result: LiveData) { result.observe(this) { if (!isResumed) return@observe @@ -35,7 +34,10 @@ fun Fragment.observeSaveResult(result: LiveData) { } } -fun AppCompatActivity.replaceFragment(fragment: Fragment, tag: String) { +fun AppCompatActivity.replaceFragment( + fragment: Fragment, + tag: String, +) { supportFragmentManager.beginTransaction().apply { val backStackName = fragment.javaClass.name val popBackStack = supportFragmentManager.popBackStackImmediate(backStackName, 0) @@ -49,27 +51,36 @@ fun AppCompatActivity.replaceFragment(fragment: Fragment, tag: String) { } } -fun AppCompatActivity.resumeFragment(fragment: Fragment){ - supportFragmentManager.beginTransaction().apply{ +fun AppCompatActivity.resumeFragment(fragment: Fragment) { + supportFragmentManager.beginTransaction().apply { show(fragment) commit() } } -fun Context.getTextFromClipBoard(view: View?, onSuccess: (text: String?) -> Unit) { +fun Context.getTextFromClipBoard( + view: View?, + onSuccess: (text: String?) -> Unit, +) { view?.post { val clipboardManager = getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager onSuccess.invoke(clipboardManager.primaryClip?.getItemAt(0)?.text?.toString()) } ?: return } -fun Context.copyToClipboard(label: String, text: String) { +fun Context.copyToClipboard( + label: String, + text: String, +) { val clipboard = getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager? val clip = ClipData.newPlainText(label, text) clipboard?.setPrimaryClip(clip) } -fun Path.listFiles(extension: String, process: (Path) -> R): List = +fun Path.listFiles( + extension: String, + process: (Path) -> R, +): List = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { Files.list(this).toList().filter { it.extension == extension }.map { process(it) @@ -118,9 +129,10 @@ fun extractDuration(path: String): String { return try { val metadataRetriever = MediaMetadataRetriever() metadataRetriever.setDataSource(path) - val duration = metadataRetriever.extractMetadata( - MediaMetadataRetriever.METADATA_KEY_DURATION - )?.toLong() ?: 0L + val duration = + metadataRetriever.extractMetadata( + MediaMetadataRetriever.METADATA_KEY_DURATION, + )?.toLong() ?: 0L millisToString(duration) } catch (e: Exception) { Log.e("ExtractDuration", "extractDuration exception: " + e.message) diff --git a/app/src/main/java/dev/arkbuilders/arkmemo/utils/ViewExt.kt b/app/src/main/java/dev/arkbuilders/arkmemo/utils/ViewExt.kt index beabd9f4..d2e45f28 100644 --- a/app/src/main/java/dev/arkbuilders/arkmemo/utils/ViewExt.kt +++ b/app/src/main/java/dev/arkbuilders/arkmemo/utils/ViewExt.kt @@ -30,25 +30,25 @@ fun View.setOnDebounceTouchListener(action: (v: View, e: MotionEvent) -> Unit) { postDelayed({ isTouched = false }, 500) } true - } } fun View.showAvailabilityToolTip() { - val balloon = Balloon.Builder(context) - .setWidthRatio(1.0f) - .setHeight(BalloonSizeSpec.WRAP) - .setText(context.getString(R.string.tips_will_be_available_soon)) - .setTextColorResource(R.color.white) - .setTextSize(12f) - .setArrowPositionRules(ArrowPositionRules.ALIGN_ANCHOR) - .setArrowSize(10) - .setArrowPosition(0.5f) - .setPadding(12) - .setCornerRadius(8f) - .setWidthRatio(0.5f) - .setBackgroundColorResource(R.color.warning_300) - .setBalloonAnimation(BalloonAnimation.ELASTIC) - .build() + val balloon = + Balloon.Builder(context) + .setWidthRatio(1.0f) + .setHeight(BalloonSizeSpec.WRAP) + .setText(context.getString(R.string.tips_will_be_available_soon)) + .setTextColorResource(R.color.white) + .setTextSize(12f) + .setArrowPositionRules(ArrowPositionRules.ALIGN_ANCHOR) + .setArrowSize(10) + .setArrowPosition(0.5f) + .setPadding(12) + .setCornerRadius(8f) + .setWidthRatio(0.5f) + .setBackgroundColorResource(R.color.warning_300) + .setBalloonAnimation(BalloonAnimation.ELASTIC) + .build() showAlignTop(balloon) -} \ No newline at end of file +} diff --git a/app/src/test/java/dev/arkbuilders/arkmemo/utils/StringExtTest.kt b/app/src/test/java/dev/arkbuilders/arkmemo/utils/StringExtTest.kt index 38cfaef3..5ce8c171 100644 --- a/app/src/test/java/dev/arkbuilders/arkmemo/utils/StringExtTest.kt +++ b/app/src/test/java/dev/arkbuilders/arkmemo/utils/StringExtTest.kt @@ -1,11 +1,9 @@ package dev.arkbuilders.arkmemo.utils -import org.junit.Assert.* - +import junit.framework.TestCase.assertEquals import org.junit.Test class StringExtTest { - @Test fun `insert string in the middle of another string`() { val result = "Hello World".insertStringAtPosition("Beautiful ", 6) @@ -67,5 +65,4 @@ class StringExtTest { val result = "Hello\nWorld".insertStringAtPosition(" Beautiful", 5) assertEquals("Hello Beautiful\nWorld", result) } - -} \ No newline at end of file +}