Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement graphics note's thumb #79

Merged
merged 2 commits into from
Nov 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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,5 @@ 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,31 @@
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.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 @@ -29,9 +42,17 @@ class GraphicNotesRepo
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()
Expand Down Expand Up @@ -109,15 +130,79 @@ class GraphicNotesRepo
)

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,
)
}.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"
Expand Down
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,7 +33,6 @@ 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>() {
private lateinit var activity: MainActivity

Expand All @@ -41,6 +42,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 +74,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 +115,27 @@ 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 +228,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 @@ -67,7 +67,6 @@ class EditGraphicNotesFragment : BaseEditNoteFragment() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
notesViewModel.init {}
observeSaveResult(notesViewModel.getSaveNoteResultLiveData())
arguments?.getParcelableCompat(GRAPHICAL_NOTE_KEY, GraphicNote::class.java)?.let {
note = it
graphicNotesViewModel.onNoteOpened(note)
Expand Down Expand Up @@ -140,6 +139,7 @@ class EditGraphicNotesFragment : BaseEditNoteFragment() {
binding.editTextDescription.setText(this.note.description)
initBottomControls()
observeDrawEvent()
observeSaveResult(notesViewModel.getSaveNoteResultLiveData())
}

private fun observeDrawEvent() {
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 @@ -210,7 +208,7 @@ class NotesFragment : BaseFragment() {
onPlayPauseClick = { path, pos, onStop ->
playingAudioPath = path
if (playingAudioPosition >= 0) {
refreshVoiceNoteItem(playingAudioPosition)
refreshNoteItem(playingAudioPosition)
}

if (playingAudioPosition >= 0 && playingAudioPosition != pos) {
Expand All @@ -229,10 +227,6 @@ class NotesFragment : BaseFragment() {

arkMediaPlayerViewModel.onPlayOrPauseClick(path, pos, onStop)
},
onThumbPrepare = { graphicNote, noteCanvas ->
val tempNoteViewModel: GraphicNotesViewModel by viewModels()
noteCanvas.setViewModel(viewModel = tempNoteViewModel)
},
)
} 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>
31 changes: 20 additions & 11 deletions app/src/main/res/layout/adapter_text_note.xml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
android:textColor="@color/text_primary"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintEnd_toStartOf="@+id/canvas_graphic_thumb"
app:layout_constraintEnd_toStartOf="@+id/iv_graphics_thumb"
android:gravity="start"
app:layout_constraintTop_toBottomOf="@+id/layout_audio_view"
android:maxLines="1"
Expand All @@ -46,7 +46,7 @@
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@+id/tv_title"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/canvas_graphic_thumb"
app:layout_constraintEnd_toStartOf="@+id/iv_graphics_thumb"
android:maxLines="2"
android:ellipsize="end"
android:layout_marginTop="4dp"
Expand All @@ -55,17 +55,18 @@
android:id="@+id/tv_content_preview"
tools:text="How do you create compelling presentations that wow your colleagues and impress your managers?"/>

<dev.arkbuilders.arkmemo.ui.views.NotesCanvas
android:id="@+id/canvas_graphic_thumb"
android:layout_width="90dp"
<com.google.android.material.imageview.ShapeableImageView
android:id="@+id/iv_graphics_thumb"
android:layout_width="@dimen/graphic_thumb_width"
android:layout_height="match_parent"
app:layout_constraintEnd_toEndOf="parent"
android:paddingStart="10dp"
android:paddingTop="10dp"
android:paddingEnd="10dp"
android:paddingBottom="80dp"
app:layout_constraintEnd_toStartOf="@+id/barrier_graphic_thumb_end"
android:layout_marginEnd="@dimen/border_width_thin"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:layout_marginVertical="@dimen/border_width_thin"
app:shapeAppearanceOverlay="@style/GraphicThumbBackground"
android:visibility="gone"
tools:visibility="gone"/>
tools:visibility="visible"/>

<androidx.recyclerview.widget.RecyclerView
android:layout_width="match_parent"
Expand Down Expand Up @@ -96,4 +97,12 @@
android:visibility="gone"
android:gravity="center"/>

<androidx.constraintlayout.widget.Barrier
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/barrier_graphic_thumb_end"
app:barrierDirection="start"
app:constraint_referenced_ids="tv_delete"
app:layout_constraintEnd_toEndOf="parent"/>

</androidx.constraintlayout.widget.ConstraintLayout>
Loading