Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
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
2 changes: 2 additions & 0 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,8 @@ dependencies {
implementation(libs.androidx.ui.graphics)
implementation(libs.androidx.ui.tooling.preview)
implementation(libs.androidx.material3)
implementation(libs.androidx.compose.material.icons.core)
implementation(libs.androidx.compose.material.icons.extended)
implementation(libs.androidx.lifecycle.viewmodel.compose)
implementation(libs.androidx.lifecycle.runtime.compose)
implementation(libs.androidx.navigation.runtime.ktx)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,8 @@ class BrushDesignerViewModelTest {

viewModel.saveToPalette(brushName).join()

kotlinx.coroutines.delay(500)

val savedBrushes = customBrushDao.getAllCustomBrushes().first()
assertTrue(savedBrushes.any { it.name == brushName })
}
Expand Down
15 changes: 15 additions & 0 deletions app/src/main/java/com/example/cahier/core/di/AppModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,26 @@ import dagger.hilt.InstallIn
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton
import javax.inject.Qualifier
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob

@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class ApplicationScope

@Module
@InstallIn(SingletonComponent::class)
object AppModule {

@Provides
@Singleton
@ApplicationScope
fun provideApplicationScope(): CoroutineScope {
return CoroutineScope(SupervisorJob() + Dispatchers.Default)
}

@Provides
@Singleton
fun provideNoteDatabase(@ApplicationContext context: Context): NoteDatabase {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,11 @@ import androidx.annotation.DrawableRes
import androidx.ink.brush.ExperimentalInkCustomBrushApi
import androidx.ink.brush.TextureBitmapStore
import com.example.cahier.R
import javax.inject.Inject
import dagger.hilt.android.qualifiers.ApplicationContext

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

private val textureResources: Map<String, Int> = mapOf(
Expand All @@ -43,6 +45,11 @@ class CahierTextureBitmapStore(context: Context) : TextureBitmapStore {
}
}

/** Returns all available texture IDs. */
fun getAllIds(): Set<String> {
return textureResources.keys + loadedBitmaps.keys
}

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

Expand Down
12 changes: 12 additions & 0 deletions app/src/main/java/com/example/cahier/core/ui/theme/Color.kt
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,18 @@ val Purple40 = Color(0xFF6650a4)
val PurpleGrey40 = Color(0xFF625b71)
val Pink40 = Color(0xFF7D5260)

// Warning Colors - Light Mode
val LightWarning = Color(0xFFF57C00)
val LightOnWarning = Color(0xFFFFFFFF)
val LightWarningContainer = Color(0xFFFFF3E0)
val LightOnWarningContainer = Color(0xFFE65100)

// Warning Colors - Dark Mode
val DarkWarning = Color(0xFFFFB74D)
val DarkOnWarning = Color(0xFF4E342E)
val DarkWarningContainer = Color(0xFFE65100)
val DarkOnWarningContainer = Color(0xFFFFCC80)

// Brush Designer: color picker presets
val BrushBlack = Color(0xFF000000)
val BrushRed = Color(0xFFFF0000)
Expand Down
55 changes: 50 additions & 5 deletions app/src/main/java/com/example/cahier/core/ui/theme/Theme.kt
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,51 @@ import androidx.compose.material3.dynamicDarkColorScheme
import androidx.compose.material3.dynamicLightColorScheme
import androidx.compose.material3.lightColorScheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.Immutable
import androidx.compose.runtime.SideEffect
import androidx.compose.runtime.staticCompositionLocalOf
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalView
import androidx.core.view.WindowCompat

@Immutable
data class ExtendedColorScheme(
val warning: Color,
val onWarning: Color,
val warningContainer: Color,
val onWarningContainer: Color,
)

val LocalExtendedColorScheme = staticCompositionLocalOf {
ExtendedColorScheme(
warning = Color.Unspecified,
onWarning = Color.Unspecified,
warningContainer = Color.Unspecified,
onWarningContainer = Color.Unspecified,
)
}

val MaterialTheme.extendedColorScheme: ExtendedColorScheme
@Composable get() = LocalExtendedColorScheme.current

private val LightExtendedColorScheme =
ExtendedColorScheme(
warning = LightWarning,
onWarning = LightOnWarning,
warningContainer = LightWarningContainer,
onWarningContainer = LightOnWarningContainer,
)

private val DarkExtendedColorScheme =
ExtendedColorScheme(
warning = DarkWarning,
onWarning = DarkOnWarning,
warningContainer = DarkWarningContainer,
onWarningContainer = DarkOnWarningContainer,
)

private val DarkColorScheme = darkColorScheme(
primary = Purple80,
secondary = PurpleGrey80,
Expand Down Expand Up @@ -59,6 +99,9 @@ fun CahierAppTheme(
darkTheme -> DarkColorScheme
else -> LightColorScheme
}

val extendedColorScheme = if (darkTheme) DarkExtendedColorScheme else LightExtendedColorScheme

val view = LocalView.current
if (!view.isInEditMode) {
SideEffect {
Expand All @@ -67,9 +110,11 @@ fun CahierAppTheme(
}
}

MaterialTheme(
colorScheme = colorScheme,
typography = Typography,
content = content
)
CompositionLocalProvider(LocalExtendedColorScheme provides extendedColorScheme) {
MaterialTheme(
colorScheme = colorScheme,
typography = Typography,
content = content
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,15 @@ import androidx.room.OnConflictStrategy
import androidx.room.Query
import kotlinx.coroutines.flow.Flow

public const val AUTOSAVE_KEY = "__autosave__"

@Dao
interface CustomBrushDao {
@Query("SELECT * FROM custom_brushes")
fun getAllCustomBrushes(): Flow<List<CustomBrushEntity>>
@Query("SELECT * FROM custom_brushes WHERE name != :autosaveKey")
fun getAllCustomBrushes(autosaveKey: String = AUTOSAVE_KEY): Flow<List<CustomBrushEntity>>

@Query("SELECT * FROM custom_brushes WHERE name = :autosaveKey")
fun getAutoSaveBrush(autosaveKey: String = AUTOSAVE_KEY): Flow<CustomBrushEntity?>

@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun saveCustomBrush(brush: CustomBrushEntity)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,11 @@ import androidx.compose.ui.unit.dp
import com.example.cahier.R
import com.godaddy.android.colorpicker.ClassicColorPicker
import com.godaddy.android.colorpicker.HsvColor
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ExposedDropdownMenuBox
import androidx.compose.material3.ExposedDropdownMenuDefaults
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.DropdownMenuItem

/**
* A reusable slider control for brush property editing.
Expand Down Expand Up @@ -124,3 +129,52 @@ fun CustomColorPickerDialog(
}
)
}

/**
* A generic [ExposedDropdownMenuBox] for selecting from enum-like value lists.
*/
@OptIn(ExperimentalMaterial3Api::class)
@Composable
internal fun <T> EnumDropdown(
label: String,
currentValue: T,
values: List<T>,
modifier: Modifier = Modifier,
displayName: @Composable (T) -> String,
onSelected: (T) -> Unit
) {
var expanded by remember { mutableStateOf(false) }

ExposedDropdownMenuBox(
expanded = expanded,
onExpandedChange = { expanded = it },
modifier = modifier
) {
OutlinedTextField(
value = displayName(currentValue),
onValueChange = {},
readOnly = true,
label = { Text(label) },
trailingIcon = {
ExposedDropdownMenuDefaults.TrailingIcon(expanded = expanded)
},
modifier = Modifier
.menuAnchor()
.fillMaxWidth()
)
ExposedDropdownMenu(
expanded = expanded,
onDismissRequest = { expanded = false }
) {
values.forEach { value ->
DropdownMenuItem(
text = { Text(displayName(value)) },
onClick = {
onSelected(value)
expanded = false
}
)
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import com.example.cahier.R
import ink.proto.BrushBehavior
import ink.proto.PredefinedEasingFunction as ProtoPredefinedEasingFunction

/**
* Dispatches to the correct editor based on the [BrushBehavior.Node.NodeCase].
Expand Down Expand Up @@ -174,7 +175,7 @@ internal fun ResponseNodeEditor(
when (selected) {
ResponseCurveType.Predefined ->
newResponseBuilder.setPredefinedResponseCurve(
ink.proto.PredefinedEasingFunction.PREDEFINED_EASING_EASE
ProtoPredefinedEasingFunction.PREDEFINED_EASING_EASE
)
ResponseCurveType.CubicBezier ->
newResponseBuilder.setCubicBezierResponseCurve(
Expand Down Expand Up @@ -258,7 +259,7 @@ internal fun ResponseNodeEditor(
EnumDropdown(
label = stringResource(R.string.brush_designer_node_predefined_curve),
currentValue = response.predefinedResponseCurve,
values = ink.proto.PredefinedEasingFunction.entries.toList(),
values = ProtoPredefinedEasingFunction.entries.toList(),
displayName = {
it.name.replace("PREDEFINED_EASING_FUNCTION_", "")
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ internal fun NumericField(
title: String,
value: Float,
limits: NumericLimits,
onValueChangeFinished: (() -> Unit)? = null,
onValueChanged: (Float) -> Unit
) {
val displayValue = limits.fromRealValue(value)
Expand Down Expand Up @@ -191,7 +192,8 @@ internal fun NumericField(
value = displayValue.coerceIn(limits.min, limits.max),
onValueChange = { onValueChanged(limits.toRealValue(it)) },
valueRange = limits.min..limits.max,
modifier = Modifier.weight(1f)
modifier = Modifier.weight(1f),
onValueChangeFinished = onValueChangeFinished
)

IconButton(onClick = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ import com.example.cahier.R
import ink.proto.BrushFamily as ProtoBrushFamily
import ink.proto.BrushPaint as ProtoBrushPaint
import ink.proto.ColorFunction as ProtoColorFunction
import ink.proto.Color as ProtoColor

/**
* Tab 1: Paint & texture controls — multi-layer textures, multi-function colors,
Expand Down Expand Up @@ -438,7 +439,7 @@ private fun ColorFunctionEditor(
1 -> onFunctionChanged(
ProtoColorFunction.newBuilder()
.setReplaceColor(
ink.proto.Color.getDefaultInstance()
ProtoColor.getDefaultInstance()
)
.build()
)
Expand Down Expand Up @@ -477,52 +478,7 @@ private fun ColorFunctionEditor(
}
}

/**
* A generic [ExposedDropdownMenuBox] for selecting from enum-like value lists.
*/
@OptIn(ExperimentalMaterial3Api::class)
@Composable
internal fun <T> EnumDropdown(
label: String,
currentValue: T,
values: List<T>,
displayName: (T) -> String,
onSelected: (T) -> Unit
) {
var expanded by remember { mutableStateOf(false) }

ExposedDropdownMenuBox(
expanded = expanded,
onExpandedChange = { expanded = it }
) {
OutlinedTextField(
value = displayName(currentValue),
onValueChange = {},
readOnly = true,
label = { Text(label) },
trailingIcon = {
ExposedDropdownMenuDefaults.TrailingIcon(expanded = expanded)
},
modifier = Modifier
.menuAnchor()
.fillMaxWidth()
)
ExposedDropdownMenu(
expanded = expanded,
onDismissRequest = { expanded = false }
) {
values.forEach { value ->
DropdownMenuItem(
text = { Text(displayName(value)) },
onClick = {
onSelected(value)
expanded = false
}
)
}
}
}
}

@OptIn(ExperimentalMaterial3Api::class)
@Composable
Expand Down
Loading