حفظ دولتی و ذخیره سازی مداوم

حفظ حالت و ذخیره سازی دائمی جنبه های غیر ضروری برنامه های جوهرسازی هستند، به خصوص در Compose. اشیاء داده های اصلی، مانند ویژگی های قلم مو و نقاطی که یک سکته مغزی را تشکیل می دهند، پیچیده هستند و به طور خودکار باقی نمی مانند. این به یک استراتژی عمدی برای ذخیره وضعیت در طول سناریوهایی مانند تغییرات پیکربندی و ذخیره دائمی نقشه های کاربر در پایگاه داده نیاز دارد.

حفظ دولت

در Jetpack Compose، حالت رابط کاربری معمولاً با استفاده از remember and rememberSaveable مدیریت می‌شود. در حالی که rememberSaveable حفظ حالت خودکار را در میان تغییرات پیکربندی ارائه می‌کند، قابلیت‌های داخلی آن به انواع داده‌های اولیه و اشیایی که Parcelable یا Serializable پیاده‌سازی می‌کنند محدود می‌شود.

برای اشیاء سفارشی که حاوی ویژگی‌های پیچیده هستند، مانند Brush ، باید مکانیسم‌های سریال‌سازی و سریال‌زدایی صریح را تعریف کنید. یک محافظ حالت سفارشی برای این کار مفید است. همانطور که در مثال brushStateSaver زیر نشان داده شده است، با تعریف یک Saver سفارشی برای شی Brush ، می‌توانید ویژگی‌های ضروری آن را هنگام رخ دادن تغییرات پیکربندی حفظ کنید.

fun brushStateSaver(converters: Converters): Saver<MutableState<Brush>, String> = Saver(
    save = { state ->
        converters.brushToString(state.value)
    },
    restore = { jsonString ->
        val brush = converters.stringToBrush(jsonString)
        mutableStateOf(brush)
    }
)

سپس می توانید از Saver سفارشی برای حفظ حالت قلم مو انتخاب شده استفاده کنید:

val converters = Converters()
val currentBrush = rememberSaveable(saver = brushStateSaver(converters)) { mutableStateOf(defaultBrush) }

ذخیره سازی دائمی

برای فعال کردن ویژگی‌هایی مانند ذخیره سند، بارگیری و همکاری بالقوه بلادرنگ، Strokes و داده‌های مرتبط را در قالب سریالی ذخیره کنید. برای Ink API، سریال‌سازی دستی و سریال‌زدایی ضروری است.

برای بازیابی دقیق یک Stroke، Brush و StrokeInputBatch آن را ذخیره کنید.

  • Brush : شامل فیلدهای عددی (اندازه، اپسیلون)، رنگ، و BrushFamily .
  • StrokeInputBatch : فهرستی از نقاط ورودی با فیلدهای عددی.

سریال سازی پایه

ساختار شی سریال سازی را تعریف کنید که اشیاء کتابخانه جوهر را منعکس کند.

داده های سریال شده را با استفاده از چارچوب دلخواه خود مانند Gson، Moshi، Protobuf و دیگران رمزگذاری کنید و از فشرده سازی برای بهینه سازی استفاده کنید.

data class SerializedStroke(
  val inputs: SerializedStrokeInputBatch,
  val brush: SerializedBrush
)

data class SerializedBrush(
  val size: Float,
  val color: Long,
  val epsilon: Float,
  val stockBrush: SerializedStockBrush
)

enum class SerializedStockBrush {
  MARKER_V1,
  PRESSURE_PEN_V1,
  HIGHLIGHTER_V1
}

data class SerializedStrokeInputBatch(
  val toolType: SerializedToolType,
  val strokeUnitLengthCm: Float,
  val inputs: List<SerializedStrokeInput>
)

data class SerializedStrokeInput(
  val x: Float,
  val y: Float,
  val timeMillis: Float,
  val pressure: Float,
  val tiltRadians: Float,
  val orientationRadians: Float,
  val strokeUnitLengthCm: Float
)

enum class SerializedToolType {
  STYLUS,
  TOUCH,
  MOUSE,
  UNKNOWN
}
class Converters {

  private val gson: Gson = GsonBuilder().create()

  companion object {
    private val stockBrushToEnumValues =
      mapOf(
        StockBrushes.markerV1 to SerializedStockBrush.MARKER_V1,
        StockBrushes.pressurePenV1 to SerializedStockBrush.PRESSURE_PEN_V1,
        StockBrushes.highlighterV1 to SerializedStockBrush.HIGHLIGHTER_V1,
      )

    private val enumToStockBrush =
      stockBrushToEnumValues.entries.associate { (key, value) -> value to key }
  }

  private fun serializeBrush(brush: Brush): SerializedBrush {
    return SerializedBrush(
      size = brush.size,
      color = brush.colorLong,
      epsilon = brush.epsilon,
      stockBrush = stockBrushToEnumValues[brush.family] ?: SerializedStockBrush.MARKER_V1,
    )
  }

  private fun serializeStrokeInputBatch(inputs: StrokeInputBatch): SerializedStrokeInputBatch {
    val serializedInputs = mutableListOf<SerializedStrokeInput>()
    val scratchInput = StrokeInput()

    for (i in 0 until inputs.size) {
      inputs.populate(i, scratchInput)
      serializedInputs.add(
        SerializedStrokeInput(
          x = scratchInput.x,
          y = scratchInput.y,
          timeMillis = scratchInput.elapsedTimeMillis.toFloat(),
          pressure = scratchInput.pressure,
          tiltRadians = scratchInput.tiltRadians,
          orientationRadians = scratchInput.orientationRadians,
          strokeUnitLengthCm = scratchInput.strokeUnitLengthCm,
        )
      )
    }

    val toolType =
      when (inputs.getToolType()) {
        InputToolType.STYLUS -> SerializedToolType.STYLUS
        InputToolType.TOUCH -> SerializedToolType.TOUCH
        InputToolType.MOUSE -> SerializedToolType.MOUSE
        else -> SerializedToolType.UNKNOWN
      }

    return SerializedStrokeInputBatch(
      toolType = toolType,
      strokeUnitLengthCm = inputs.getStrokeUnitLengthCm(),
      inputs = serializedInputs,
    )
  }

  private fun deserializeStroke(serializedStroke: SerializedStroke): Stroke? {
    val inputs = deserializeStrokeInputBatch(serializedStroke.inputs) ?: return null
    val brush = deserializeBrush(serializedStroke.brush) ?: return null
    return Stroke(brush = brush, inputs = inputs)
  }

  private fun deserializeBrush(serializedBrush: SerializedBrush): Brush {
    val stockBrushFamily = enumToStockBrush[serializedBrush.stockBrush] ?: StockBrushes.markerV1

    return Brush.createWithColorLong(
      family = stockBrushFamily,
      colorLong = serializedBrush.color,
      size = serializedBrush.size,
      epsilon = serializedBrush.epsilon,
    )
  }

  private fun deserializeStrokeInputBatch(
    serializedBatch: SerializedStrokeInputBatch
  ): StrokeInputBatch {
    val toolType =
      when (serializedBatch.toolType) {
        SerializedToolType.STYLUS -> InputToolType.STYLUS
        SerializedToolType.TOUCH -> InputToolType.TOUCH
        SerializedToolType.MOUSE -> InputToolType.MOUSE
        else -> InputToolType.UNKNOWN
      }

    val batch = MutableStrokeInputBatch()

    serializedBatch.inputs.forEach { input ->
      batch.addOrThrow(
        type = toolType,
        x = input.x,
        y = input.y,
        elapsedTimeMillis = input.timeMillis.toLong(),
        pressure = input.pressure,
        tiltRadians = input.tiltRadians,
        orientationRadians = input.orientationRadians,
      )
    }

    return batch
  }

  fun serializeStrokeToEntity(stroke: Stroke): StrokeEntity {
    val serializedBrush = serializeBrush(stroke.brush)
    val serializedInputs = serializeStrokeInputBatch(stroke.inputs)
    return StrokeEntity(
      brushSize = serializedBrush.size,
      brushColor = serializedBrush.color,
      brushEpsilon = serializedBrush.epsilon,
      stockBrush = serializedBrush.stockBrush,
      strokeInputs = gson.toJson(serializedInputs),
    )
  }

  fun deserializeEntityToStroke(entity: StrokeEntity): Stroke {
    val serializedBrush =
      SerializedBrush(
        size = entity.brushSize,
        color = entity.brushColor,
        epsilon = entity.brushEpsilon,
        stockBrush = entity.stockBrush,
      )

    val serializedInputs =
      gson.fromJson(entity.strokeInputs, SerializedStrokeInputBatch::class.java)

    val brush = deserializeBrush(serializedBrush)
    val inputs = deserializeStrokeInputBatch(serializedInputs)

    return Stroke(brush = brush, inputs = inputs)
  }

  fun brushToString(brush: Brush): String {
        val serializedBrush = serializeBrush(brush)
        return gson.toJson(serializedBrush)
    }

  fun stringToBrush(jsonString: String): Brush {
        val serializedBrush = gson.fromJson(jsonString, SerializedBrush::class.java)
        return deserializeBrush(serializedBrush)
    }

}