Skip to content
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
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ class OfflineNotesRepositoryTest {
fun getNoteStrokes_deserializes_strokes_correctly() = runTest {
val brush = Brush(StockBrushes.marker(), 1f, 1f)
val stroke = Stroke(brush, ImmutableStrokeInputBatch.EMPTY)
val serializedStroke = Converters().serializeStroke(stroke)
val serializedStroke = Converters().serializeStroke(stroke, emptyList())
val strokesJson = Json.encodeToString(listOf(serializedStroke))
val note = Note(id = 1, strokesData = strokesJson)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,8 @@ class OfflineNotesRepository(
strokes: List<Stroke>,
clientBrushFamilyId: String?
) {
val strokesData = strokes.map { converters.serializeStroke(it) }
val customBrushes = CustomBrushes.getBrushes(context)
val strokesData = strokes.map { converters.serializeStroke(it, customBrushes) }
val strokesJson = Json.encodeToString(strokesData)

val note = notesDao.getNoteById(noteId)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package com.example.cahier.ui

import android.content.Context
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import androidx.annotation.DrawableRes
import androidx.ink.brush.ExperimentalInkCustomBrushApi
import androidx.ink.brush.TextureBitmapStore
import com.example.cahier.R

@OptIn(ExperimentalInkCustomBrushApi::class)
class CahierTextureBitmapStore(context: Context) : TextureBitmapStore {
private val resources = context.resources

private val textureResources: Map<String, Int> = mapOf(
"music-clef-g" to R.drawable.music_clef_g,
"music-note-sixteenth" to R.drawable.music_note_sixteenth
)

private val loadedBitmaps = mutableMapOf<String, Bitmap>()

override operator fun get(clientTextureId: String): Bitmap? {
val id = getShortName(clientTextureId)
return loadedBitmaps.getOrPut(id) {
textureResources[id]?.let { loadBitmap(it) } ?: return null
}
}

private fun getShortName(clientTextureId: String): String =
clientTextureId.removePrefix("ink://ink").removePrefix("/texture:")

private fun loadBitmap(@DrawableRes drawable: Int): Bitmap {
return BitmapFactory.decodeResource(resources, drawable)
?: throw IllegalStateException("Could not load bitmap for resource $drawable")
}
}
11 changes: 6 additions & 5 deletions app/src/main/java/com/example/cahier/ui/Converters.kt
Original file line number Diff line number Diff line change
Expand Up @@ -55,18 +55,19 @@ class Converters {
stockBrushToEnumValues.entries.associate { (key, value) -> value to key }
}

private fun serializeBrush(brush: Brush): SerializedBrush {
private fun serializeBrush(brush: Brush, customBrushes: List<CustomBrush>): SerializedBrush {
val customBrushName = customBrushes.find { it.brushFamily == brush.family }?.name
return SerializedBrush(
size = brush.size,
color = brush.colorLong,
epsilon = brush.epsilon,
stockBrush = stockBrushToEnumValues[brush.family] ?: SerializedStockBrush.MarkerLatest,
clientBrushFamilyId = brush.family.clientBrushFamilyId
clientBrushFamilyId = customBrushName
)
}

fun serializeStroke(stroke: Stroke): String {
val serializedBrush = serializeBrush(stroke.brush)
fun serializeStroke(stroke: Stroke, customBrushes: List<CustomBrush>): String {
val serializedBrush = serializeBrush(stroke.brush, customBrushes)
val encodedSerializedInputs = ByteArrayOutputStream().use { outputStream ->
stroke.inputs.encode(outputStream)
outputStream.toByteArray()
Expand Down Expand Up @@ -96,7 +97,7 @@ class Converters {
): Brush {
val stockBrushFamily = enumToStockBrush[serializedBrush.stockBrush]
val customBrush = customBrushes.find {
it.brushFamily.clientBrushFamilyId == serializedBrush.clientBrushFamilyId
it.name == serializedBrush.clientBrushFamilyId
}

val brushFamily = customBrush?.brushFamily ?: stockBrushFamily ?: StockBrushes.marker()
Expand Down
3 changes: 1 addition & 2 deletions app/src/main/java/com/example/cahier/ui/CustomBrushes.kt
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@ object CustomBrushes {

@OptIn(ExperimentalInkCustomBrushApi::class)
private fun loadCustomBrushes(context: Context): List<CustomBrush> {

val brushFiles = mapOf(
"Calligraphy" to (R.raw.calligraphy to R.drawable.draw_24px),
"Flag Banner" to (R.raw.flag_banner to R.drawable.flag_24px),
Expand All @@ -61,7 +60,7 @@ object CustomBrushes {
val brushFamily = context.resources.openRawResource(resourceId).use { inputStream ->
BrushFamily.decode(inputStream)
}
CustomBrush(name, icon, brushFamily.copy(clientBrushFamilyId = name))
CustomBrush(name, icon, brushFamily)
} catch (e: Exception) {
Log.e(TAG, "Error loading custom brush $name", e)
null
Expand Down
8 changes: 7 additions & 1 deletion app/src/main/java/com/example/cahier/ui/DrawingCanvas.kt
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.focus.onFocusChanged
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.onSizeChanged
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalView
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.input.ImeAction
Expand Down Expand Up @@ -275,7 +276,11 @@ private fun DrawingSurfaceWithTarget(
val currentBrush by drawingCanvasViewModel.currentBrush.collectAsStateWithLifecycle()
val isEraserMode by drawingCanvasViewModel.isEraserMode.collectAsStateWithLifecycle()
val strokes = remember { mutableStateListOf<Stroke>() }
val canvasStrokeRenderer = remember { CanvasStrokeRenderer.create() }
val context = LocalContext.current
val textureStore = remember { CahierTextureBitmapStore(context) }
val canvasStrokeRenderer = remember {
CanvasStrokeRenderer.create(textureStore = textureStore)
}
var canvasSize by remember { mutableStateOf(IntSize.Zero) }
val view = LocalView.current
val activity = LocalActivity.current as ComponentActivity
Expand Down Expand Up @@ -360,6 +365,7 @@ private fun DrawingSurfaceWithTarget(
onGetNextBrush = drawingCanvasViewModel::getCurrentBrush,
isEraserMode = isEraserMode,
backgroundImageUri = uiState.note.imageUriList?.firstOrNull(),
textureStore = textureStore,
modifier = Modifier.fillMaxSize()
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.nativeCanvas
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.semantics.Role
import androidx.compose.ui.tooling.preview.Preview
import androidx.ink.rendering.android.canvas.CanvasStrokeRenderer
Expand All @@ -43,7 +44,10 @@ fun DrawingDetailThumbnail(
modifier: Modifier = Modifier,
backgroundImageUri: String? = null,
) {
val canvasStrokeRenderer = remember { CanvasStrokeRenderer.create() }
val context = LocalContext.current
val canvasStrokeRenderer = remember {
CanvasStrokeRenderer.create(textureStore = CahierTextureBitmapStore(context))
}

Box(
modifier = modifier
Expand Down
11 changes: 10 additions & 1 deletion app/src/main/java/com/example/cahier/ui/DrawingSurface.kt
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ import androidx.core.graphics.withSave
import androidx.ink.authoring.compose.InProgressStrokes
import androidx.ink.brush.Brush
import androidx.ink.brush.StockBrushes
import androidx.ink.brush.TextureBitmapStore
import androidx.ink.brush.compose.createWithComposeColor
import androidx.ink.rendering.android.canvas.CanvasStrokeRenderer
import androidx.ink.strokes.Stroke
Expand All @@ -53,6 +54,7 @@ import com.example.cahier.utils.pointerInputWithSiblingFallthrough
fun DrawingSurface(
strokes: List<Stroke>,
canvasStrokeRenderer: CanvasStrokeRenderer,
textureStore: TextureBitmapStore? = null,
onStrokesFinished: (List<Stroke>) -> Unit,
onErase: (offsetX: Float, offsetY: Float) -> Unit,
onEraseStart: () -> Unit,
Expand Down Expand Up @@ -130,7 +132,14 @@ fun DrawingSurface(
}
)
} else {
InProgressStrokes(
textureStore?.let {
InProgressStrokes(
defaultBrush = currentBrush,
nextBrush = onGetNextBrush,
onStrokesFinished = onStrokesFinished,
textureBitmapStore = it
)
} ?: InProgressStrokes(
defaultBrush = currentBrush,
nextBrush = onGetNextBrush,
onStrokesFinished = onStrokesFinished,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ import coil3.toBitmap
import com.example.cahier.data.CustomBrush
import com.example.cahier.data.NotesRepository
import com.example.cahier.navigation.DrawingCanvasDestination
import com.example.cahier.ui.CahierTextureBitmapStore
import com.example.cahier.ui.CahierUiState
import com.example.cahier.ui.CustomBrushes
import com.example.cahier.utils.FileHelper
Expand Down Expand Up @@ -127,7 +128,7 @@ class DrawingCanvasViewModel @Inject constructor(
note.clientBrushFamilyId?.let { id ->
if (!isBrushSelectedInSession) {
val customBrush = customBrushes.value.find {
it.brushFamily.clientBrushFamilyId == id
it.name == id
}
customBrush?.let {
_selectedBrush.value =
Expand Down Expand Up @@ -269,7 +270,10 @@ class DrawingCanvasViewModel @Inject constructor(
canvas.drawBitmap(bmp, matrix, null)
}

val strokeRenderer = CanvasStrokeRenderer.create(forcePathRendering = true)
val strokeRenderer = CanvasStrokeRenderer.create(
forcePathRendering = true,
textureStore = CahierTextureBitmapStore(context)
)
strokes.forEach { stroke ->
strokeRenderer.draw(canvas, stroke, android.graphics.Matrix())
}
Expand All @@ -279,8 +283,8 @@ class DrawingCanvasViewModel @Inject constructor(
suspend fun saveStrokes() {
if (historyIndex >= 0 && historyIndex < history.size) {
val strokesToSave = history[historyIndex]
val clientBrushFamilyId =
strokesToSave.firstOrNull()?.brush?.family?.clientBrushFamilyId
val currentBrushFamily = strokesToSave.lastOrNull()?.brush?.family
val clientBrushFamilyId = _customBrushes.value.find { it.brushFamily == currentBrushFamily }?.name
noteRepository.updateNoteStrokes(noteId, strokesToSave, clientBrushFamilyId)
} else if (history.isEmpty()) {
noteRepository.updateNoteStrokes(noteId, emptyList(), null)
Expand Down
Binary file added app/src/main/res/drawable/music_clef_g.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added screen-20260114-201242-1768439556638.mp4
Binary file not shown.
Loading