Skip to content

Commit d0ca231

Browse files
authored
Merge pull request #50 from android/add-brush-data-layer
add custom brush data layer for brush designer
2 parents ae56758 + 957ca06 commit d0ca231

14 files changed

Lines changed: 1647 additions & 5 deletions

File tree

app/build.gradle.kts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,10 @@ dependencies {
160160
//Coil
161161
implementation(libs.coil.compose)
162162
implementation(libs.coil.network.okhttp)
163+
164+
//Protobuf
165+
implementation(project(":ink-proto"))
166+
implementation(libs.protobuf.javalite)
163167
}
164168
java {
165169
toolchain {

app/src/main/java/com/example/cahier/core/data/CustomBrush.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,5 +25,6 @@ import androidx.ink.brush.BrushFamily
2525
data class CustomBrush(
2626
val name: String,
2727
val icon: Int,
28-
val brushFamily: BrushFamily
28+
val brushFamily: BrushFamily,
29+
val isRemovable: Boolean = false
2930
)

app/src/main/java/com/example/cahier/core/data/DatabaseMigration.kt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,4 +27,12 @@ val MIGRATION_7_8 = object : Migration(7, 8) {
2727
override fun migrate(db: SupportSQLiteDatabase) {
2828
db.execSQL("UPDATE notes SET type = 'Drawing' WHERE type = 'DRAWING'")
2929
}
30+
}
31+
32+
val MIGRATION_8_9 = object : Migration(8, 9) {
33+
override fun migrate(db: SupportSQLiteDatabase) {
34+
db.execSQL(
35+
"CREATE TABLE IF NOT EXISTS `custom_brushes` (`name` TEXT NOT NULL, `brushBytes` BLOB NOT NULL, PRIMARY KEY(`name`))"
36+
)
37+
}
3038
}

app/src/main/java/com/example/cahier/core/data/NoteDatabase.kt

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,15 +22,18 @@ import androidx.room.Database
2222
import androidx.room.RoomDatabase
2323
import androidx.room.TypeConverters
2424
import com.example.cahier.core.ui.Converters
25+
import com.example.cahier.developer.brushdesigner.data.CustomBrushDao
26+
import com.example.cahier.developer.brushdesigner.data.CustomBrushEntity
2527

2628
@Database(
27-
entities = [Note::class],
28-
version = 8,
29+
entities = [Note::class, CustomBrushEntity::class],
30+
version = 9,
2931
exportSchema = false
3032
)
3133
@TypeConverters(Converters::class)
3234
abstract class NoteDatabase : RoomDatabase() {
3335
abstract fun noteDao(): NoteDao
36+
abstract fun customBrushDao(): CustomBrushDao
3437

3538
companion object {
3639
const val DATABASE_NAME = "note_database"

app/src/main/java/com/example/cahier/core/di/AppModule.kt

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,12 @@ import android.content.Context
2222
import androidx.room.Room
2323
import coil3.ImageLoader
2424
import com.example.cahier.core.data.MIGRATION_7_8
25+
import com.example.cahier.core.data.MIGRATION_8_9
2526
import com.example.cahier.core.data.NoteDatabase
2627
import com.example.cahier.core.data.NotesRepository
2728
import com.example.cahier.core.data.OfflineNotesRepository
2829
import com.example.cahier.core.utils.FileHelper
30+
import com.example.cahier.developer.brushdesigner.data.CustomBrushDao
2931
import dagger.Module
3032
import dagger.Provides
3133
import dagger.hilt.InstallIn
@@ -45,10 +47,16 @@ object AppModule {
4547
NoteDatabase::class.java,
4648
NoteDatabase.DATABASE_NAME
4749
)
48-
.addMigrations(MIGRATION_7_8)
50+
.addMigrations(MIGRATION_7_8, MIGRATION_8_9)
4951
.build()
5052
}
5153

54+
@Provides
55+
@Singleton
56+
fun provideCustomBrushDao(database: NoteDatabase): CustomBrushDao {
57+
return database.customBrushDao()
58+
}
59+
5260
@Provides
5361
@Singleton
5462
fun provideNoteRepository(
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
/*
2+
*
3+
* * Copyright 2025 Google LLC. All rights reserved.
4+
* *
5+
* * Licensed under the Apache License, Version 2.0 (the "License");
6+
* * you may not use this file except in compliance with the License.
7+
* * You may obtain a copy of the License at
8+
* *
9+
* * http://www.apache.org/licenses/LICENSE-2.0
10+
* *
11+
* * Unless required by applicable law or agreed to in writing, software
12+
* * distributed under the License is distributed on an "AS IS" BASIS,
13+
* * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* * See the License for the specific language governing permissions and
15+
* * limitations under the License.
16+
*
17+
*/
18+
19+
package com.example.cahier.developer.brushdesigner.data
20+
21+
import androidx.ink.strokes.Stroke
22+
import ink.proto.BrushCoat as ProtoBrushCoat
23+
import ink.proto.BrushFamily as ProtoBrushFamily
24+
import ink.proto.BrushPaint as ProtoBrushPaint
25+
import ink.proto.BrushTip as ProtoBrushTip
26+
import ink.proto.ColorFunction as ProtoColorFunction
27+
import javax.inject.Inject
28+
import javax.inject.Singleton
29+
import kotlinx.coroutines.flow.MutableStateFlow
30+
import kotlinx.coroutines.flow.StateFlow
31+
import kotlinx.coroutines.flow.asStateFlow
32+
33+
@Singleton
34+
class BrushDesignerRepository @Inject constructor() {
35+
36+
private val initialProto: ProtoBrushFamily = ProtoBrushFamily.newBuilder()
37+
.addCoats(
38+
ProtoBrushCoat.newBuilder()
39+
.setTip(
40+
ProtoBrushTip.newBuilder().setScaleX(1f).setScaleY(1f).setCornerRounding(1f)
41+
)
42+
.addPaintPreferences(
43+
ProtoBrushPaint.newBuilder()
44+
.setSelfOverlap(ProtoBrushPaint.SelfOverlap.SELF_OVERLAP_ANY)
45+
.addColorFunctions(
46+
ProtoColorFunction.newBuilder()
47+
.setOpacityMultiplier(1f)
48+
)
49+
)
50+
)
51+
.setInputModel(
52+
ProtoBrushFamily.InputModel.newBuilder()
53+
.setSlidingWindowModel(
54+
ProtoBrushFamily.SlidingWindowModel.newBuilder()
55+
.setWindowSizeSeconds(0.02f)
56+
.setExperimentalUpsamplingPeriodSeconds(0.005f)
57+
)
58+
)
59+
.build()
60+
61+
private val _activeBrushProto = MutableStateFlow(initialProto)
62+
val activeBrushProto: StateFlow<ProtoBrushFamily> = _activeBrushProto.asStateFlow()
63+
64+
private val _testStrokes = MutableStateFlow<List<Stroke>>(emptyList())
65+
val testStrokes: StateFlow<List<Stroke>> = _testStrokes.asStateFlow()
66+
67+
fun updateActiveBrushProto(proto: ProtoBrushFamily) {
68+
_activeBrushProto.value = proto
69+
}
70+
71+
fun updateTestStrokes(strokes: List<Stroke>) {
72+
_testStrokes.value = strokes
73+
}
74+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/*
2+
*
3+
* * Copyright 2025 Google LLC. All rights reserved.
4+
* *
5+
* * Licensed under the Apache License, Version 2.0 (the "License");
6+
* * you may not use this file except in compliance with the License.
7+
* * You may obtain a copy of the License at
8+
* *
9+
* * http://www.apache.org/licenses/LICENSE-2.0
10+
* *
11+
* * Unless required by applicable law or agreed to in writing, software
12+
* * distributed under the License is distributed on an "AS IS" BASIS,
13+
* * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* * See the License for the specific language governing permissions and
15+
* * limitations under the License.
16+
*
17+
*/
18+
19+
package com.example.cahier.developer.brushdesigner.data
20+
21+
import androidx.room.Dao
22+
import androidx.room.Insert
23+
import androidx.room.OnConflictStrategy
24+
import androidx.room.Query
25+
import kotlinx.coroutines.flow.Flow
26+
27+
@Dao
28+
interface CustomBrushDao {
29+
@Query("SELECT * FROM custom_brushes")
30+
fun getAllCustomBrushes(): Flow<List<CustomBrushEntity>>
31+
32+
@Insert(onConflict = OnConflictStrategy.REPLACE)
33+
suspend fun saveCustomBrush(brush: CustomBrushEntity)
34+
35+
@Query("DELETE FROM custom_brushes WHERE name = :name")
36+
suspend fun deleteCustomBrush(name: String)
37+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
/*
2+
*
3+
* * Copyright 2025 Google LLC. All rights reserved.
4+
* *
5+
* * Licensed under the Apache License, Version 2.0 (the "License");
6+
* * you may not use this file except in compliance with the License.
7+
* * You may obtain a copy of the License at
8+
* *
9+
* * http://www.apache.org/licenses/LICENSE-2.0
10+
* *
11+
* * Unless required by applicable law or agreed to in writing, software
12+
* * distributed under the License is distributed on an "AS IS" BASIS,
13+
* * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* * See the License for the specific language governing permissions and
15+
* * limitations under the License.
16+
*
17+
*/
18+
19+
package com.example.cahier.developer.brushdesigner.data
20+
21+
import androidx.room.ColumnInfo
22+
import androidx.room.Entity
23+
import androidx.room.PrimaryKey
24+
25+
@Entity(tableName = "custom_brushes")
26+
data class CustomBrushEntity(
27+
@PrimaryKey val name: String,
28+
@ColumnInfo(typeAffinity = ColumnInfo.BLOB) val brushBytes: ByteArray
29+
) {
30+
override fun equals(other: Any?): Boolean {
31+
if (this === other) return true
32+
if (javaClass != other?.javaClass) return false
33+
34+
other as CustomBrushEntity
35+
36+
if (name != other.name) return false
37+
if (!brushBytes.contentEquals(other.brushBytes)) return false
38+
39+
return true
40+
}
41+
42+
override fun hashCode(): Int {
43+
var result = name.hashCode()
44+
result = 31 * result + brushBytes.contentHashCode()
45+
return result
46+
}
47+
}

gradle/libs.versions.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ lifecycleRuntimeCompose = "2.10.0"
2020
adaptiveNavigationAndroid = "1.2.0"
2121
navigationRuntimeKtx = "2.9.7"
2222
navigationCompose = "2.9.7"
23+
protobufJavalite = "4.34.0"
2324
roomKtx = "2.8.4"
2425
roomRuntime = "2.8.4"
2526
windowCore = "1.5.1"
@@ -98,6 +99,7 @@ robolectric = { group = "org.robolectric", name = "robolectric", version.ref = "
9899
roborazzi = { group = "io.github.takahirom.roborazzi", name = "roborazzi", version.ref = "roborazzi" }
99100
roborazzi-compose = { group = "io.github.takahirom.roborazzi", name = "roborazzi-compose", version.ref = "roborazzi" }
100101
roborazzi-rule = { group = "io.github.takahirom.roborazzi", name = "roborazzi-junit-rule", version.ref = "roborazzi" }
102+
protobuf-javalite = { module = "com.google.protobuf:protobuf-javalite", version.ref = "protobufJavalite" }
101103

102104
[plugins]
103105
androidApplication = { id = "com.android.application", version.ref = "agp" }

ink-proto/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
/build

0 commit comments

Comments
 (0)