Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] Canvas module #113

Open
wants to merge 29 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
b59a8f7
Create canvas module
hieuwu Nov 9, 2024
6c884b9
Resolve resource & setup build tool
hieuwu Nov 12, 2024
cbba929
Load drawing & remove unused files
hieuwu Nov 13, 2024
c70f229
Load edit screen
hieuwu Nov 16, 2024
7577b30
Remove unused
hieuwu Nov 17, 2024
befade1
Extract dependencies
hieuwu Nov 17, 2024
2bac253
Extract dependencies
hieuwu Nov 17, 2024
c752e78
Extract logic of applying mode
hieuwu Nov 20, 2024
49496c9
Save and load svg
hieuwu Nov 21, 2024
28cd02b
Rename variables
hieuwu Nov 22, 2024
780cc95
Switch based on file extension
hieuwu Nov 23, 2024
14d3c57
Use Ulong color
hieuwu Nov 25, 2024
49b522a
Load resource based on extension
hieuwu Nov 26, 2024
b836a95
Add file picker screen to test canvas
hieuwu Nov 27, 2024
2d63e78
Improve edit current svg
hieuwu Nov 27, 2024
4d01477
Remove unneeded require api annotation
hieuwu Nov 29, 2024
1fa95b6
Handle navigate back
hieuwu Dec 4, 2024
12435bc
Encapsulate logic of edit manager
hieuwu Dec 4, 2024
626cb2f
Remove unused image
hieuwu Dec 4, 2024
40dbb24
Extract logic
hieuwu Dec 6, 2024
4c7dcc2
Extract logic to view model
hieuwu Dec 6, 2024
55b4206
Simplify code to get stroke size
hieuwu Dec 7, 2024
4c3ecaa
Remove unused files
hieuwu Dec 7, 2024
55f59ff
Reduce duplicate code
hieuwu Dec 7, 2024
fd93371
Add color property
hieuwu Dec 7, 2024
1b0c9cc
Fix wrong background setting
hieuwu Dec 7, 2024
eae99c3
Remove unused import
hieuwu Dec 7, 2024
5e043c8
Add theme color for manager
hieuwu Dec 7, 2024
8f61433
Add colors
hieuwu Dec 7, 2024
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
1 change: 1 addition & 0 deletions canvas/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/build
79 changes: 79 additions & 0 deletions canvas/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
plugins {
alias(libs.plugins.androidLibrary)
alias(libs.plugins.jetbrainsKotlinAndroid)
id("org.jetbrains.kotlin.plugin.serialization") version ("1.8.21")
id("kotlin-kapt")
}

android {
namespace = "dev.arkbuilders.canvas"
compileSdk = 34

defaultConfig {
minSdk = 29

testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles("consumer-rules.pro")
}

buildFeatures {
compose = true
}

composeOptions {
kotlinCompilerExtensionVersion = "1.5.10"
}
buildTypes {
release {
isMinifyEnabled = false
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = "1.8"
}
}

dependencies {

implementation(libs.androidx.core.ktx)
implementation(libs.androidx.appcompat)
implementation(libs.android.material)
implementation(libs.androidx.ui.android)
implementation(project(":filepicker"))

val compose_version = "1.5.4"
implementation("androidx.compose.ui:ui:$compose_version")
implementation("androidx.compose.material:material:$compose_version")
implementation("androidx.compose.ui:ui-tooling-preview:$compose_version")
implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.3.1")
implementation("androidx.activity:activity-compose:1.3.1")
implementation("com.jakewharton.timber:timber:5.0.1")


implementation("com.godaddy.android.colorpicker:compose-color-picker:0.7.0")
implementation("androidx.navigation:navigation-compose:2.5.2")
implementation("io.github.hokofly:hoko-blur:1.5.3")

implementation("com.github.bumptech.glide:glide:4.16.0")
kapt("com.github.bumptech.glide:compiler:4.16.0")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.4.0")

implementation("androidx.preference:preference-ktx:1.2.0")

implementation("androidx.preference:preference:1.2.0'")
implementation("com.google.dagger:hilt-android:2.48")
kapt("com.google.dagger:hilt-compiler:2.48")
kapt("androidx.hilt:hilt-compiler:1.0.0")

testImplementation(libs.junit)
androidTestImplementation(libs.androidx.test.junit)
androidTestImplementation(libs.androidx.test.espresso)
}
Empty file added canvas/consumer-rules.pro
Empty file.
21 changes: 21 additions & 0 deletions canvas/proguard-rules.pro
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html

# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}

# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable

# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package dev.arkbuilders.canvas

import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.ext.junit.runners.AndroidJUnit4

import org.junit.Test
import org.junit.runner.RunWith

import org.junit.Assert.*

/**
* Instrumented test, which will execute on an Android device.
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
@RunWith(AndroidJUnit4::class)
class ExampleInstrumentedTest {
@Test
fun useAppContext() {
// Context of the app under test.
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
assertEquals("dev.arkbuilders.canvas.test", appContext.packageName)
}
}
4 changes: 4 additions & 0 deletions canvas/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">

</manifest>
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
package dev.arkbuilders.canvas.presentation

import android.os.Build
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.annotation.RequiresApi
import androidx.compose.ui.platform.ComposeView
import androidx.compose.ui.platform.ViewCompositionStrategy
import androidx.fragment.app.Fragment
import dev.arkbuilders.canvas.R
import dev.arkbuilders.canvas.presentation.data.Preferences
import dev.arkbuilders.canvas.presentation.data.Resolution
import dev.arkbuilders.canvas.presentation.drawing.EditManager
import dev.arkbuilders.canvas.presentation.edit.EditScreen
import dev.arkbuilders.canvas.presentation.edit.EditViewModel
import dev.arkbuilders.canvas.presentation.resourceloader.BitmapResourceManager
import dev.arkbuilders.canvas.presentation.resourceloader.CanvasResourceManager
import dev.arkbuilders.canvas.presentation.resourceloader.SvgResourceManager
import java.nio.file.Path
import kotlin.io.path.Path

private const val imagePath = "image_path_param"

class ArkCanvasFragment : Fragment() {
private lateinit var imagePathParam: String

private lateinit var prefs: Preferences

private lateinit var viewModel: EditViewModel
private lateinit var bitmapResourceManager: CanvasResourceManager
private lateinit var svgResourceManager: CanvasResourceManager

lateinit var editManager: EditManager

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
arguments?.let {
imagePathParam = it.getString(imagePath) ?: ""
}
val context = requireActivity().applicationContext
prefs = Preferences(appCtx = context)
editManager = EditManager()
bitmapResourceManager = BitmapResourceManager(context = context, editManager = editManager)
svgResourceManager = SvgResourceManager(editManager = editManager)
viewModel = EditViewModel(
primaryColor = 0xFF101828,
launchedFromIntent = false,
imagePath = pathFromString(),
imageUri = null,
maxResolution = Resolution(350, 720),
prefs = prefs,
editManager = editManager,
bitMapResourceManager = bitmapResourceManager,
svgResourceManager = svgResourceManager,
)
}

private fun pathFromString(): Path?{
return if (imagePathParam.isEmpty()) {
null
} else {
Path(imagePathParam)
}
}

override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_ark_canvas, container, false)
}

@RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val composeView = view.findViewById<ComposeView>(R.id.compose_view)

composeView.apply {
setViewCompositionStrategy(
ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed
)
setContent {
// Set Content here
EditScreen(
imagePath = null,
imageUri = null,
fragmentManager = requireActivity().supportFragmentManager,
navigateBack = { requireActivity().supportFragmentManager.popBackStackImmediate() },
launchedFromIntent = false,
maxResolution = Resolution(350, 720),
onSaveSvg = { /*TODO*/ },
viewModel = viewModel
)
}
}
}

companion object {
@JvmStatic
fun newInstance(param1: String) =
ArkCanvasFragment().apply {
arguments = Bundle().apply {
putString(imagePath, param1)
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package dev.arkbuilders.canvas.presentation.data

import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.IntSize
import kotlinx.serialization.Serializable

@Serializable
data class ImageDefaults(
val colorValue: ULong = Color.White.value,
val resolution: Resolution? = null
)

@Serializable
data class Resolution(
val width: Int,
val height: Int
) {
fun toIntSize() = IntSize(this.width, this.height)

companion object {
fun fromIntSize(intSize: IntSize) = Resolution(intSize.width, intSize.height)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
package dev.arkbuilders.canvas.presentation.data

import android.content.Context
import androidx.compose.ui.graphics.Color
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import java.io.IOException
import java.nio.file.Files
import javax.inject.Inject
import javax.inject.Singleton
import kotlin.io.path.exists
import kotlin.io.path.readText
import kotlin.io.path.writeText
import kotlin.text.Charsets.UTF_8

@Singleton
class Preferences @Inject constructor(private val appCtx: Context) {

suspend fun persistUsedColors(
colors: List<Color>
) = withContext(Dispatchers.IO) {
try {
val colorsStorage = appCtx.filesDir.resolve(COLORS_STORAGE)
.toPath()
val lines = colors.map { it.value.toString() }
Files.write(colorsStorage, lines, UTF_8)
} catch (e: IOException) {
e.printStackTrace()
}
}

suspend fun readUsedColors(): List<Color> {
val colors = mutableListOf<Color>()
withContext(Dispatchers.IO) {

try {
val colorsStorage = appCtx
.filesDir
.resolve(COLORS_STORAGE)
.toPath()

if (colorsStorage.exists()) {
Files.readAllLines(colorsStorage, UTF_8).forEach { line ->
val color = Color(line.toULong())
colors.add(color)
}
}
} catch (e: IOException) {
e.printStackTrace()
}
}
return colors
}

suspend fun persistDefaults(color: Color, resolution: Resolution) {
withContext(Dispatchers.IO) {
val defaultsStorage = appCtx.filesDir.resolve(DEFAULTS_STORAGE)
.toPath()
val defaults = ImageDefaults(
color.value,
resolution
)
val jsonString = Json.encodeToString(defaults)
defaultsStorage.writeText(jsonString, UTF_8)
}
}

suspend fun readDefaults(): ImageDefaults {
var defaults = ImageDefaults()
try {
withContext(Dispatchers.IO) {
val defaultsStorage = appCtx.filesDir.resolve(DEFAULTS_STORAGE)
.toPath()
if (defaultsStorage.exists()) {
val jsonString = defaultsStorage.readText(UTF_8)
defaults = Json.decodeFromString(jsonString)
}
}
} catch (e: IOException) {
e.printStackTrace()
}
return defaults
}

companion object {
private const val COLORS_STORAGE = "colors"
private const val DEFAULTS_STORAGE = "defaults"
}
}
Loading
Loading