Skip to content

Commit

Permalink
Implement graphics note's thumb in Note list
Browse files Browse the repository at this point in the history
  • Loading branch information
tuancoltech committed Nov 7, 2024
1 parent 6a90eca commit bd4dfda
Show file tree
Hide file tree
Showing 12 changed files with 199 additions and 57 deletions.
21 changes: 21 additions & 0 deletions app/src/main/java/dev/arkbuilders/arkmemo/di/AppModule.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package dev.arkbuilders.arkmemo.di

import android.content.Context
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton

@Module
@InstallIn(SingletonComponent::class)
object AppModule {

@Provides
@Singleton
fun provideApplicationContext(@ApplicationContext context: Context): Context {
return context
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,5 @@ 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") }
val lightYellow by lazy { android.graphics.Color.parseColor("#f8f6ed") }
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package dev.arkbuilders.arkmemo.models

import android.graphics.Bitmap
import android.os.Parcelable
import dev.arkbuilders.arklib.data.index.Resource
import dev.arkbuilders.arkmemo.graphics.SVG
Expand All @@ -15,4 +16,6 @@ data class GraphicNote(
@IgnoredOnParcel
override var resource: Resource? = null,
override var pendingForDelete: Boolean = false,
var thumb: Bitmap? = null

) : Note, Parcelable
Original file line number Diff line number Diff line change
@@ -1,18 +1,33 @@
package dev.arkbuilders.arkmemo.repo.graphics

import android.content.Context
import android.content.res.Resources
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.graphics.Canvas
import android.os.Environment
import android.util.Log
import dagger.hilt.android.qualifiers.ApplicationContext
import dev.arkbuilders.arklib.computeId
import dev.arkbuilders.arklib.data.index.Resource
import dev.arkbuilders.arkmemo.R
import dev.arkbuilders.arkmemo.di.IO_DISPATCHER
import dev.arkbuilders.arkmemo.graphics.ColorCode
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.dpToPx
import dev.arkbuilders.arkmemo.utils.listFiles
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.withContext
import java.io.File
import java.io.FileOutputStream
import java.io.IOException
import java.nio.file.Path
import javax.inject.Inject
import javax.inject.Named
Expand All @@ -23,19 +38,26 @@ import kotlin.io.path.fileSize
import kotlin.io.path.getLastModifiedTime
import kotlin.io.path.name

class GraphicNotesRepo
@Inject
constructor(
private val memoPreferences: MemoPreferences,
@Named(IO_DISPATCHER) private val iODispatcher: CoroutineDispatcher,
private val helper: NotesRepoHelper,
) : NotesRepo<GraphicNote> {
private lateinit var root: Path

override suspend fun init() {
helper.init()
root = memoPreferences.getNotesStorage()
}
class GraphicNotesRepo @Inject constructor(
private val memoPreferences: MemoPreferences,
@Named(IO_DISPATCHER) private val iODispatcher: CoroutineDispatcher,
private val helper: NotesRepoHelper,
@ApplicationContext private val context: Context
): NotesRepo<GraphicNote> {

private lateinit var root: Path

private val displayMetrics by lazy { Resources.getSystem().displayMetrics }
private val screenWidth by lazy { displayMetrics.widthPixels }
private val screenHeight by lazy { displayMetrics.heightPixels - 150.dpToPx() }
private val thumbViewWidth by lazy { context.resources.getDimension(R.dimen.graphic_thumb_width) }

private val thumbDirectory by lazy { context.getExternalFilesDir(Environment.DIRECTORY_PICTURES) }

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

override suspend fun save(
note: GraphicNote,
Expand Down Expand Up @@ -108,17 +130,78 @@ class GraphicNotesRepo
modified = path.getLastModifiedTime(),
)

val userNoteProperties = helper.readProperties(id, "")
val userNoteProperties = helper.readProperties(id, "")
val bitmap = exportBitmapFromSvg(fileName = id.toString(), svg = svg)

GraphicNote(
title = userNoteProperties.title,
description = userNoteProperties.description,
svg = svg,
resource = resource,
thumb = bitmap
)

GraphicNote(
title = userNoteProperties.title,
description = userNoteProperties.description,
svg = svg,
resource = resource,
)
}.filter { graphicNote -> graphicNote.svg != null }
}.filter { graphicNote -> graphicNote.svg != null }
}

private fun exportBitmapFromSvg(fileName: String, svg: SVG?): Bitmap? {

// Check if thumb bitmap already exists
val file = File(thumbDirectory, "$fileName.png")
try {
if (file.exists()) {
return BitmapFactory.decodeFile(file.absolutePath)
}
} catch (e: Exception) {
e.printStackTrace()
}

// If thumb doesn't exist, create a bitmap and a canvas for offscreen drawing
val bitmap = Bitmap.createBitmap(
thumbViewWidth.toInt(), thumbViewWidth.toInt(), Bitmap.Config.ARGB_8888
)
val canvas = Canvas(bitmap)

canvas.drawColor(ColorCode.lightYellow)
svg?.getPaths()?.forEach { path ->

canvas.save()

// Scale factor to fit the SVG path into the view
val scaleX = thumbViewWidth / screenWidth
val scaleY = thumbViewWidth / screenHeight

// Find the smallest scale to maintain the aspect ratio
val scale = minOf(scaleX, scaleY)

// Center the path in the view
val dx = (thumbViewWidth - screenWidth * scale) / 2f
val dy = (thumbViewWidth - screenHeight * scale) / 2f

// Apply scaling and translation to center the path
canvas.translate(dx, dy)
canvas.scale(scale, scale)

canvas.drawPath(path.path, path.paint)
canvas.restore()
} ?: let {
return null
}

// Save the bitmap to a file
try {

// Open an output stream and write the bitmap to the file
FileOutputStream(file).use { outputStream ->
bitmap.compress(Bitmap.CompressFormat.PNG, 80, outputStream) // Save as PNG
}
return bitmap
} catch (e: IOException) {
e.printStackTrace()
return null
}
}
}

private const val GRAPHICS_REPO = "GraphicNotesRepo"
private const val SVG_EXT = "svg"
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package dev.arkbuilders.arkmemo.ui.adapters

import android.graphics.drawable.BitmapDrawable
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
Expand All @@ -8,6 +9,8 @@ import androidx.core.content.ContextCompat
import androidx.core.content.res.ResourcesCompat
import androidx.recyclerview.widget.RecyclerView
import by.kirich1409.viewbindingdelegate.viewBinding
import com.google.android.material.shape.CornerFamily
import com.google.android.material.shape.ShapeAppearanceModel
import dev.arkbuilders.arkmemo.R
import dev.arkbuilders.arkmemo.databinding.AdapterTextNoteBinding
import dev.arkbuilders.arkmemo.models.GraphicNote
Expand All @@ -20,7 +23,6 @@ import dev.arkbuilders.arkmemo.ui.fragments.EditGraphicNotesFragment
import dev.arkbuilders.arkmemo.ui.fragments.EditTextNotesFragment
import dev.arkbuilders.arkmemo.ui.viewmodels.ArkMediaPlayerSideEffect
import dev.arkbuilders.arkmemo.ui.viewmodels.ArkMediaPlayerState
import dev.arkbuilders.arkmemo.ui.views.NotesCanvas
import dev.arkbuilders.arkmemo.utils.getAutoTitle
import dev.arkbuilders.arkmemo.utils.gone
import dev.arkbuilders.arkmemo.utils.highlightWord
Expand All @@ -31,8 +33,8 @@ import dev.arkbuilders.arkmemo.utils.visible
class NotesListAdapter(
private var notes: MutableList<Note>,
private val onPlayPauseClick: (path: String, pos: Int?, stopCallback: ((pos: Int) -> Unit)?) -> Unit,
private val onThumbPrepare: (note: GraphicNote, holder: NotesCanvas) -> Unit,
) : RecyclerView.Adapter<NotesListAdapter.NoteViewHolder>() {
): RecyclerView.Adapter<NotesListAdapter.NoteViewHolder>() {

private lateinit var activity: MainActivity

lateinit var observeItemSideEffect: () -> ArkMediaPlayerSideEffect
Expand All @@ -41,6 +43,10 @@ class NotesListAdapter(
private var isFromSearch: Boolean = false
private var searchKeyWord: String = ""

private val cornerRadius by lazy {
activity.resources.getDimension(R.dimen.corner_radius_big)
}

fun setActivity(activity: AppCompatActivity) {
this.activity = activity as MainActivity
}
Expand Down Expand Up @@ -69,6 +75,7 @@ class NotesListAdapter(
holder.contentPreview.text = note.text
}
holder.layoutAudioView.root.gone()
holder.ivGraphicThumb.gone()
if (note is VoiceNote) {
val isRecordingExist = note.path.toFile().length() > 0L
if (isRecordingExist) {
Expand Down Expand Up @@ -109,12 +116,24 @@ class NotesListAdapter(
}
}
} else if (note is GraphicNote) {
holder.canvasGraphicThumb.visible()
onThumbPrepare(note, holder.canvasGraphicThumb)
holder.ivGraphicThumb.background = BitmapDrawable(
holder.itemView.context.resources, note.thumb
)
holder.ivGraphicThumb.visible()
holder.ivGraphicThumb.shapeAppearanceModel = ShapeAppearanceModel.builder()
.setBottomLeftCornerSize(0f)
.setTopLeftCornerSize(0f)
.setTopRightCorner(CornerFamily.ROUNDED, cornerRadius)
.setBottomRightCorner(CornerFamily.ROUNDED, cornerRadius)
.build()
}

if (note.pendingForDelete) {
holder.tvDelete.visible()
if (note is GraphicNote) {
holder.ivGraphicThumb.shapeAppearanceModel = ShapeAppearanceModel.builder()
.setAllCorners(CornerFamily.ROUNDED, 0f).build()
}
} else {
holder.tvDelete.gone()
}
Expand Down Expand Up @@ -207,7 +226,7 @@ class NotesListAdapter(
val btnPlayPause = binding.layoutAudioView.ivPlayAudio
val layoutAudioView = binding.layoutAudioView
val tvPlayingPosition = binding.layoutAudioView.tvPlayingPosition
val canvasGraphicThumb = binding.canvasGraphicThumb
val ivGraphicThumb = binding.ivGraphicsThumb
val tvDelete = binding.tvDelete
var isSwiping: Boolean = false

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ class CommonActionDialog(

private fun initViews() {
dialog?.setCanceledOnTouchOutside(false)
dialog?.setCancelable(false)

if (isAlert) {
mBinding.tvPositive.setTextAppearance(R.style.AlertButton)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import android.widget.Toast
import androidx.core.content.ContextCompat
import androidx.core.widget.addTextChangedListener
import androidx.fragment.app.activityViewModels
import androidx.fragment.app.viewModels
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
Expand All @@ -27,7 +26,6 @@ import dev.arkbuilders.arkmemo.ui.adapters.NotesListAdapter
import dev.arkbuilders.arkmemo.ui.dialogs.CommonActionDialog
import dev.arkbuilders.arkmemo.ui.viewmodels.ArkMediaPlayerSideEffect
import dev.arkbuilders.arkmemo.ui.viewmodels.ArkMediaPlayerViewModel
import dev.arkbuilders.arkmemo.ui.viewmodels.GraphicNotesViewModel
import dev.arkbuilders.arkmemo.ui.viewmodels.NotesViewModel
import dev.arkbuilders.arkmemo.ui.views.toast
import dev.arkbuilders.arkmemo.utils.getTextFromClipBoard
Expand Down Expand Up @@ -204,14 +202,13 @@ class NotesFragment : BaseFragment() {
private fun onNotesLoaded(notes: List<Note>) {
binding.pbLoading.gone()
if (notesAdapter == null) {
notesAdapter =
NotesListAdapter(
notes.toMutableList(),
onPlayPauseClick = { path, pos, onStop ->
playingAudioPath = path
if (playingAudioPosition >= 0) {
refreshVoiceNoteItem(playingAudioPosition)
}
notesAdapter = NotesListAdapter(
notes.toMutableList(),
onPlayPauseClick = { path, pos, onStop ->
playingAudioPath = path
if (playingAudioPosition >= 0) {
refreshNoteItem(playingAudioPosition)
}

if (playingAudioPosition >= 0 && playingAudioPosition != pos) {
// Another Voice note is being played compared to the previously played one
Expand All @@ -227,13 +224,10 @@ class NotesFragment : BaseFragment() {
mItemTouchHelper?.attachToRecyclerView(null)
}

arkMediaPlayerViewModel.onPlayOrPauseClick(path, pos, onStop)
},
onThumbPrepare = { graphicNote, noteCanvas ->
val tempNoteViewModel: GraphicNotesViewModel by viewModels()
noteCanvas.setViewModel(viewModel = tempNoteViewModel)
},
)
arkMediaPlayerViewModel.onPlayOrPauseClick(path, pos, onStop)
}
)

} else {
notesAdapter?.setNotes(notes)
}
Expand Down Expand Up @@ -278,7 +272,7 @@ class NotesFragment : BaseFragment() {
(notesAdapter?.getNotes()?.getOrNull(pos) as? VoiceNote)?.waitToBeResumed = true
}

private fun refreshVoiceNoteItem(position: Int) {
private fun refreshNoteItem(position: Int) {
notesAdapter?.notifyItemChanged(position)
}

Expand Down
2 changes: 1 addition & 1 deletion app/src/main/res/drawable/bg_big_radius.xml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">

<stroke android:width="1dp" android:color="@color/border_primary"/>
<stroke android:width="@dimen/border_width_thin" android:color="@color/border_primary"/>
<corners android:radius="@dimen/corner_radius_big"/>

</shape>
Loading

0 comments on commit bd4dfda

Please sign in to comment.