diff --git a/app/src/androidTest/java/com/example/cahier/data/OfflineNotesRepositoryTest.kt b/app/src/androidTest/java/com/example/cahier/data/OfflineNotesRepositoryTest.kt index c333789..56b3618 100644 --- a/app/src/androidTest/java/com/example/cahier/data/OfflineNotesRepositoryTest.kt +++ b/app/src/androidTest/java/com/example/cahier/data/OfflineNotesRepositoryTest.kt @@ -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) diff --git a/app/src/main/java/com/example/cahier/data/OfflineNotesRepository.kt b/app/src/main/java/com/example/cahier/data/OfflineNotesRepository.kt index 4ed4414..eeef68e 100644 --- a/app/src/main/java/com/example/cahier/data/OfflineNotesRepository.kt +++ b/app/src/main/java/com/example/cahier/data/OfflineNotesRepository.kt @@ -49,7 +49,8 @@ class OfflineNotesRepository( strokes: List, 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) diff --git a/app/src/main/java/com/example/cahier/ui/CahierTextureBitmapStore.kt b/app/src/main/java/com/example/cahier/ui/CahierTextureBitmapStore.kt new file mode 100644 index 0000000..d7fb437 --- /dev/null +++ b/app/src/main/java/com/example/cahier/ui/CahierTextureBitmapStore.kt @@ -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 = mapOf( + "music-clef-g" to R.drawable.music_clef_g, + "music-note-sixteenth" to R.drawable.music_note_sixteenth + ) + + private val loadedBitmaps = mutableMapOf() + + 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") + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/cahier/ui/Converters.kt b/app/src/main/java/com/example/cahier/ui/Converters.kt index 103ba30..1a86074 100644 --- a/app/src/main/java/com/example/cahier/ui/Converters.kt +++ b/app/src/main/java/com/example/cahier/ui/Converters.kt @@ -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): 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): String { + val serializedBrush = serializeBrush(stroke.brush, customBrushes) val encodedSerializedInputs = ByteArrayOutputStream().use { outputStream -> stroke.inputs.encode(outputStream) outputStream.toByteArray() @@ -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() diff --git a/app/src/main/java/com/example/cahier/ui/CustomBrushes.kt b/app/src/main/java/com/example/cahier/ui/CustomBrushes.kt index f78b2f5..f6b7e1f 100644 --- a/app/src/main/java/com/example/cahier/ui/CustomBrushes.kt +++ b/app/src/main/java/com/example/cahier/ui/CustomBrushes.kt @@ -41,7 +41,6 @@ object CustomBrushes { @OptIn(ExperimentalInkCustomBrushApi::class) private fun loadCustomBrushes(context: Context): List { - 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), @@ -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 diff --git a/app/src/main/java/com/example/cahier/ui/DrawingCanvas.kt b/app/src/main/java/com/example/cahier/ui/DrawingCanvas.kt index 0b39008..e8457a2 100644 --- a/app/src/main/java/com/example/cahier/ui/DrawingCanvas.kt +++ b/app/src/main/java/com/example/cahier/ui/DrawingCanvas.kt @@ -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 @@ -275,7 +276,11 @@ private fun DrawingSurfaceWithTarget( val currentBrush by drawingCanvasViewModel.currentBrush.collectAsStateWithLifecycle() val isEraserMode by drawingCanvasViewModel.isEraserMode.collectAsStateWithLifecycle() val strokes = remember { mutableStateListOf() } - 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 @@ -360,6 +365,7 @@ private fun DrawingSurfaceWithTarget( onGetNextBrush = drawingCanvasViewModel::getCurrentBrush, isEraserMode = isEraserMode, backgroundImageUri = uiState.note.imageUriList?.firstOrNull(), + textureStore = textureStore, modifier = Modifier.fillMaxSize() ) } diff --git a/app/src/main/java/com/example/cahier/ui/DrawingDetailThumbnail.kt b/app/src/main/java/com/example/cahier/ui/DrawingDetailThumbnail.kt index 73bfeda..355e666 100644 --- a/app/src/main/java/com/example/cahier/ui/DrawingDetailThumbnail.kt +++ b/app/src/main/java/com/example/cahier/ui/DrawingDetailThumbnail.kt @@ -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 @@ -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 diff --git a/app/src/main/java/com/example/cahier/ui/DrawingSurface.kt b/app/src/main/java/com/example/cahier/ui/DrawingSurface.kt index 09e0df7..fc35f52 100644 --- a/app/src/main/java/com/example/cahier/ui/DrawingSurface.kt +++ b/app/src/main/java/com/example/cahier/ui/DrawingSurface.kt @@ -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 @@ -53,6 +54,7 @@ import com.example.cahier.utils.pointerInputWithSiblingFallthrough fun DrawingSurface( strokes: List, canvasStrokeRenderer: CanvasStrokeRenderer, + textureStore: TextureBitmapStore? = null, onStrokesFinished: (List) -> Unit, onErase: (offsetX: Float, offsetY: Float) -> Unit, onEraseStart: () -> Unit, @@ -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, diff --git a/app/src/main/java/com/example/cahier/ui/viewmodels/DrawingCanvasViewModel.kt b/app/src/main/java/com/example/cahier/ui/viewmodels/DrawingCanvasViewModel.kt index 08e36c0..c9d9288 100644 --- a/app/src/main/java/com/example/cahier/ui/viewmodels/DrawingCanvasViewModel.kt +++ b/app/src/main/java/com/example/cahier/ui/viewmodels/DrawingCanvasViewModel.kt @@ -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 @@ -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 = @@ -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()) } @@ -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) diff --git a/app/src/main/res/drawable/music_clef_g.png b/app/src/main/res/drawable/music_clef_g.png new file mode 100644 index 0000000..020a716 Binary files /dev/null and b/app/src/main/res/drawable/music_clef_g.png differ diff --git a/app/src/main/res/drawable/music_note_sixteenth.png b/app/src/main/res/drawable/music_note_sixteenth.png new file mode 100644 index 0000000..020a716 Binary files /dev/null and b/app/src/main/res/drawable/music_note_sixteenth.png differ diff --git a/screen-20260114-201242-1768439556638.mp4 b/screen-20260114-201242-1768439556638.mp4 new file mode 100644 index 0000000..07c955f Binary files /dev/null and b/screen-20260114-201242-1768439556638.mp4 differ