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
4 changes: 4 additions & 0 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,10 @@ dependencies {
//Coil
implementation(libs.coil.compose)
implementation(libs.coil.network.okhttp)

//Protobuf
implementation(project(":ink-proto"))
implementation(libs.protobuf.javalite)
}
java {
toolchain {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,5 +25,6 @@ import androidx.ink.brush.BrushFamily
data class CustomBrush(
val name: String,
val icon: Int,
val brushFamily: BrushFamily
val brushFamily: BrushFamily,
val isRemovable: Boolean = false
)
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,12 @@ val MIGRATION_7_8 = object : Migration(7, 8) {
override fun migrate(db: SupportSQLiteDatabase) {
db.execSQL("UPDATE notes SET type = 'Drawing' WHERE type = 'DRAWING'")
}
}

val MIGRATION_8_9 = object : Migration(8, 9) {
override fun migrate(db: SupportSQLiteDatabase) {
db.execSQL(
"CREATE TABLE IF NOT EXISTS `custom_brushes` (`name` TEXT NOT NULL, `brushBytes` BLOB NOT NULL, PRIMARY KEY(`name`))"
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,18 @@ import androidx.room.Database
import androidx.room.RoomDatabase
import androidx.room.TypeConverters
import com.example.cahier.core.ui.Converters
import com.example.cahier.developer.brushdesigner.data.CustomBrushDao
import com.example.cahier.developer.brushdesigner.data.CustomBrushEntity

@Database(
entities = [Note::class],
version = 8,
entities = [Note::class, CustomBrushEntity::class],
version = 9,
exportSchema = false
)
@TypeConverters(Converters::class)
abstract class NoteDatabase : RoomDatabase() {
abstract fun noteDao(): NoteDao
abstract fun customBrushDao(): CustomBrushDao

companion object {
const val DATABASE_NAME = "note_database"
Expand Down
10 changes: 9 additions & 1 deletion app/src/main/java/com/example/cahier/core/di/AppModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,12 @@ import android.content.Context
import androidx.room.Room
import coil3.ImageLoader
import com.example.cahier.core.data.MIGRATION_7_8
import com.example.cahier.core.data.MIGRATION_8_9
import com.example.cahier.core.data.NoteDatabase
import com.example.cahier.core.data.NotesRepository
import com.example.cahier.core.data.OfflineNotesRepository
import com.example.cahier.core.utils.FileHelper
import com.example.cahier.developer.brushdesigner.data.CustomBrushDao
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
Expand All @@ -45,10 +47,16 @@ object AppModule {
NoteDatabase::class.java,
NoteDatabase.DATABASE_NAME
)
.addMigrations(MIGRATION_7_8)
.addMigrations(MIGRATION_7_8, MIGRATION_8_9)
.build()
}

@Provides
@Singleton
fun provideCustomBrushDao(database: NoteDatabase): CustomBrushDao {
return database.customBrushDao()
}

@Provides
@Singleton
fun provideNoteRepository(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
/*
*
* * Copyright 2025 Google LLC. All rights reserved.
* *
* * Licensed under the Apache License, Version 2.0 (the "License");
* * you may not use this file except in compliance with the License.
* * You may obtain a copy of the License at
* *
* * http://www.apache.org/licenses/LICENSE-2.0
* *
* * Unless required by applicable law or agreed to in writing, software
* * distributed under the License is distributed on an "AS IS" BASIS,
* * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* * See the License for the specific language governing permissions and
* * limitations under the License.
*
*/

package com.example.cahier.developer.brushdesigner.data

import androidx.ink.strokes.Stroke
import ink.proto.BrushCoat as ProtoBrushCoat
import ink.proto.BrushFamily as ProtoBrushFamily
import ink.proto.BrushPaint as ProtoBrushPaint
import ink.proto.BrushTip as ProtoBrushTip
import ink.proto.ColorFunction as ProtoColorFunction
import javax.inject.Inject
import javax.inject.Singleton
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow

@Singleton
class BrushDesignerRepository @Inject constructor() {

private val initialProto: ProtoBrushFamily = ProtoBrushFamily.newBuilder()
.addCoats(
ProtoBrushCoat.newBuilder()
.setTip(
ProtoBrushTip.newBuilder().setScaleX(1f).setScaleY(1f).setCornerRounding(1f)
)
.addPaintPreferences(
ProtoBrushPaint.newBuilder()
.setSelfOverlap(ProtoBrushPaint.SelfOverlap.SELF_OVERLAP_ANY)
.addColorFunctions(
ProtoColorFunction.newBuilder()
.setOpacityMultiplier(1f)
)
)
)
.setInputModel(
ProtoBrushFamily.InputModel.newBuilder()
.setSlidingWindowModel(
ProtoBrushFamily.SlidingWindowModel.newBuilder()
.setWindowSizeSeconds(0.02f)
.setExperimentalUpsamplingPeriodSeconds(0.005f)
)
)
.build()

private val _activeBrushProto = MutableStateFlow(initialProto)
val activeBrushProto: StateFlow<ProtoBrushFamily> = _activeBrushProto.asStateFlow()

private val _testStrokes = MutableStateFlow<List<Stroke>>(emptyList())
val testStrokes: StateFlow<List<Stroke>> = _testStrokes.asStateFlow()

fun updateActiveBrushProto(proto: ProtoBrushFamily) {
_activeBrushProto.value = proto
}

fun updateTestStrokes(strokes: List<Stroke>) {
_testStrokes.value = strokes
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
*
* * Copyright 2025 Google LLC. All rights reserved.
* *
* * Licensed under the Apache License, Version 2.0 (the "License");
* * you may not use this file except in compliance with the License.
* * You may obtain a copy of the License at
* *
* * http://www.apache.org/licenses/LICENSE-2.0
* *
* * Unless required by applicable law or agreed to in writing, software
* * distributed under the License is distributed on an "AS IS" BASIS,
* * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* * See the License for the specific language governing permissions and
* * limitations under the License.
*
*/

package com.example.cahier.developer.brushdesigner.data

import androidx.room.Dao
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import kotlinx.coroutines.flow.Flow

@Dao
interface CustomBrushDao {
@Query("SELECT * FROM custom_brushes")
fun getAllCustomBrushes(): Flow<List<CustomBrushEntity>>

@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun saveCustomBrush(brush: CustomBrushEntity)

@Query("DELETE FROM custom_brushes WHERE name = :name")
suspend fun deleteCustomBrush(name: String)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/*
*
* * Copyright 2025 Google LLC. All rights reserved.
* *
* * Licensed under the Apache License, Version 2.0 (the "License");
* * you may not use this file except in compliance with the License.
* * You may obtain a copy of the License at
* *
* * http://www.apache.org/licenses/LICENSE-2.0
* *
* * Unless required by applicable law or agreed to in writing, software
* * distributed under the License is distributed on an "AS IS" BASIS,
* * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* * See the License for the specific language governing permissions and
* * limitations under the License.
*
*/

package com.example.cahier.developer.brushdesigner.data

import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey

@Entity(tableName = "custom_brushes")
data class CustomBrushEntity(
@PrimaryKey val name: String,
@ColumnInfo(typeAffinity = ColumnInfo.BLOB) val brushBytes: ByteArray
) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false

other as CustomBrushEntity

if (name != other.name) return false
if (!brushBytes.contentEquals(other.brushBytes)) return false

return true
}

override fun hashCode(): Int {
var result = name.hashCode()
result = 31 * result + brushBytes.contentHashCode()
return result
}
}
2 changes: 2 additions & 0 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ lifecycleRuntimeCompose = "2.10.0"
adaptiveNavigationAndroid = "1.2.0"
navigationRuntimeKtx = "2.9.7"
navigationCompose = "2.9.7"
protobufJavalite = "4.34.0"
Comment thread
cka-dev marked this conversation as resolved.
roomKtx = "2.8.4"
roomRuntime = "2.8.4"
windowCore = "1.5.1"
Expand Down Expand Up @@ -98,6 +99,7 @@ robolectric = { group = "org.robolectric", name = "robolectric", version.ref = "
roborazzi = { group = "io.github.takahirom.roborazzi", name = "roborazzi", version.ref = "roborazzi" }
roborazzi-compose = { group = "io.github.takahirom.roborazzi", name = "roborazzi-compose", version.ref = "roborazzi" }
roborazzi-rule = { group = "io.github.takahirom.roborazzi", name = "roborazzi-junit-rule", version.ref = "roborazzi" }
protobuf-javalite = { module = "com.google.protobuf:protobuf-javalite", version.ref = "protobufJavalite" }

[plugins]
androidApplication = { id = "com.android.application", version.ref = "agp" }
Expand Down
1 change: 1 addition & 0 deletions ink-proto/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/build
30 changes: 30 additions & 0 deletions ink-proto/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
plugins {
id("java-library")
id("com.google.protobuf") version "0.9.6"
Comment thread
cka-dev marked this conversation as resolved.
}

java {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}


dependencies {
implementation(libs.protobuf.javalite)
}

protobuf {
protoc {
artifact = "com.google.protobuf:protoc:3.19.4"
}

generateProtoTasks {
all().forEach { task ->
task.builtins {
named("java") {
option("lite")
}
}
}
}
}
Loading
Loading