Add new APNG renderer, just for internal users for now.
@@ -613,6 +613,7 @@ dependencies {
|
||||
implementation(project(":core:models-jvm"))
|
||||
implementation(project(":feature:camera"))
|
||||
implementation(project(":feature:registration"))
|
||||
implementation(project(":lib:apng"))
|
||||
|
||||
implementation(libs.androidx.fragment.ktx)
|
||||
implementation(libs.androidx.appcompat) {
|
||||
|
||||
@@ -12,4 +12,5 @@ sealed interface LabsSettingsEvents {
|
||||
data class ToggleGroupSuggestionsForMembers(val enabled: Boolean) : LabsSettingsEvents
|
||||
data class ToggleBetterSearch(val enabled: Boolean) : LabsSettingsEvents
|
||||
data class ToggleAutoLowerHand(val enabled: Boolean) : LabsSettingsEvents
|
||||
data class ToggleNewApngRenderer(val enabled: Boolean) : LabsSettingsEvents
|
||||
}
|
||||
|
||||
@@ -142,6 +142,15 @@ private fun LabsSettingsContent(
|
||||
onCheckChanged = { onEvent(LabsSettingsEvents.ToggleAutoLowerHand(it)) }
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
Rows.ToggleRow(
|
||||
checked = state.newApngRenderer,
|
||||
text = "New APNG Renderer",
|
||||
label = "Use the new custom APNG renderer instead of the existing third-party library. Requires an app restart to take effect.",
|
||||
onCheckChanged = { onEvent(LabsSettingsEvents.ToggleNewApngRenderer(it)) }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,5 +14,6 @@ data class LabsSettingsState(
|
||||
val incognito: Boolean = false,
|
||||
val groupSuggestionsForMembers: Boolean = false,
|
||||
val betterSearch: Boolean = false,
|
||||
val autoLowerHand: Boolean = false
|
||||
val autoLowerHand: Boolean = false,
|
||||
val newApngRenderer: Boolean = false
|
||||
)
|
||||
|
||||
@@ -41,6 +41,10 @@ class LabsSettingsViewModel : ViewModel() {
|
||||
SignalStore.labs.autoLowerHand = event.enabled
|
||||
_state.value = _state.value.copy(autoLowerHand = event.enabled)
|
||||
}
|
||||
is LabsSettingsEvents.ToggleNewApngRenderer -> {
|
||||
SignalStore.labs.newApngRenderer = event.enabled
|
||||
_state.value = _state.value.copy(newApngRenderer = event.enabled)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,7 +55,8 @@ class LabsSettingsViewModel : ViewModel() {
|
||||
incognito = SignalStore.labs.incognito,
|
||||
groupSuggestionsForMembers = SignalStore.labs.groupSuggestionsForMembers,
|
||||
betterSearch = SignalStore.labs.betterSearch,
|
||||
autoLowerHand = SignalStore.labs.autoLowerHand
|
||||
autoLowerHand = SignalStore.labs.autoLowerHand,
|
||||
newApngRenderer = SignalStore.labs.newApngRenderer
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,11 +16,12 @@ import com.bumptech.glide.load.resource.gif.ByteBufferGifDecoder;
|
||||
import com.bumptech.glide.load.resource.gif.GifDrawable;
|
||||
import com.bumptech.glide.load.resource.gif.StreamGifDecoder;
|
||||
|
||||
import org.signal.apng.ApngDecoder;
|
||||
import org.signal.blurhash.BlurHash;
|
||||
import org.signal.glide.load.resource.apng.decode.APNGDecoder;
|
||||
import org.signal.glide.blurhash.BlurHashModelLoader;
|
||||
import org.signal.glide.blurhash.BlurHashResourceDecoder;
|
||||
import org.signal.glide.common.io.InputStreamFactory;
|
||||
import org.signal.glide.load.resource.apng.decode.APNGDecoder;
|
||||
import org.thoughtcrime.securesms.badges.load.BadgeLoader;
|
||||
import org.thoughtcrime.securesms.badges.load.GiftBadgeModel;
|
||||
import org.thoughtcrime.securesms.badges.models.Badge;
|
||||
@@ -29,9 +30,13 @@ import org.thoughtcrime.securesms.contacts.avatars.ContactPhotoLoader;
|
||||
import org.thoughtcrime.securesms.crypto.AttachmentSecret;
|
||||
import org.thoughtcrime.securesms.crypto.AttachmentSecretProvider;
|
||||
import org.thoughtcrime.securesms.giph.model.ChunkedImageUrl;
|
||||
import org.thoughtcrime.securesms.glide.cache.ApngDrawableTranscoder;
|
||||
import org.thoughtcrime.securesms.glide.cache.ApngFrameDrawableTranscoder;
|
||||
import org.thoughtcrime.securesms.glide.cache.ApngInputStreamFactoryResourceDecoder;
|
||||
import org.thoughtcrime.securesms.glide.cache.ApngInputStreamResourceDecoder;
|
||||
import org.thoughtcrime.securesms.glide.cache.ByteBufferApngDecoder;
|
||||
import org.thoughtcrime.securesms.glide.cache.EncryptedApngCacheEncoder;
|
||||
import org.thoughtcrime.securesms.glide.cache.EncryptedApngResourceEncoder;
|
||||
import org.thoughtcrime.securesms.glide.cache.EncryptedBitmapResourceEncoder;
|
||||
import org.thoughtcrime.securesms.glide.cache.EncryptedCacheDecoder;
|
||||
import org.thoughtcrime.securesms.glide.cache.EncryptedCacheEncoder;
|
||||
@@ -46,6 +51,7 @@ import org.signal.glide.decryptableuri.DecryptableUri;
|
||||
import org.signal.glide.decryptableuri.DecryptableUriStreamLoader;
|
||||
import org.thoughtcrime.securesms.mms.RegisterGlideComponents;
|
||||
import org.thoughtcrime.securesms.mms.SignalGlideModule;
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||
import org.thoughtcrime.securesms.stickers.StickerRemoteUri;
|
||||
import org.thoughtcrime.securesms.stickers.StickerRemoteUriLoader;
|
||||
import org.thoughtcrime.securesms.stories.StoryTextPostModel;
|
||||
@@ -85,16 +91,25 @@ public class SignalGlideComponents implements RegisterGlideComponents {
|
||||
registry.prepend(Bitmap.class, new EncryptedBitmapResourceEncoder(secret));
|
||||
registry.prepend(BitmapDrawable.class, new BitmapDrawableEncoder(glide.getBitmapPool(), encryptedBitmapResourceEncoder));
|
||||
|
||||
ByteBufferApngDecoder byteBufferApngDecoder = new ByteBufferApngDecoder();
|
||||
StreamApngDecoder streamApngDecoder = new StreamApngDecoder(byteBufferApngDecoder);
|
||||
StreamFactoryApngDecoder streamFactoryApngDecoder = new StreamFactoryApngDecoder(byteBufferApngDecoder, glide, registry);
|
||||
|
||||
registry.prepend(InputStream.class, APNGDecoder.class, streamApngDecoder);
|
||||
registry.prepend(InputStreamFactory.class, APNGDecoder.class, streamFactoryApngDecoder);
|
||||
registry.prepend(ByteBuffer.class, APNGDecoder.class, byteBufferApngDecoder);
|
||||
registry.prepend(APNGDecoder.class, new EncryptedApngCacheEncoder(secret));
|
||||
registry.prepend(File.class, APNGDecoder.class, new EncryptedCacheDecoder<>(secret, streamApngDecoder));
|
||||
registry.register(APNGDecoder.class, Drawable.class, new ApngFrameDrawableTranscoder());
|
||||
if (SignalStore.labs().getNewApngRenderer()) {
|
||||
registry.prepend(InputStream.class, ApngDecoder.class, new ApngInputStreamResourceDecoder());
|
||||
registry.prepend(InputStreamFactory.class, ApngDecoder.class, new ApngInputStreamFactoryResourceDecoder());
|
||||
registry.prepend(ApngDecoder.class, new EncryptedApngResourceEncoder(secret));
|
||||
registry.prepend(File.class, ApngDecoder.class, new EncryptedCacheDecoder<>(secret, new ApngInputStreamResourceDecoder()));
|
||||
registry.register(ApngDecoder.class, Drawable.class, new ApngDrawableTranscoder());
|
||||
} else {
|
||||
ByteBufferApngDecoder byteBufferApngDecoder = new ByteBufferApngDecoder();
|
||||
StreamApngDecoder streamApngDecoder = new StreamApngDecoder(byteBufferApngDecoder);
|
||||
StreamFactoryApngDecoder streamFactoryApngDecoder = new StreamFactoryApngDecoder(byteBufferApngDecoder, glide, registry);
|
||||
|
||||
registry.prepend(InputStream.class, APNGDecoder.class, streamApngDecoder);
|
||||
registry.prepend(InputStreamFactory.class, APNGDecoder.class, streamFactoryApngDecoder);
|
||||
registry.prepend(ByteBuffer.class, APNGDecoder.class, byteBufferApngDecoder);
|
||||
registry.prepend(APNGDecoder.class, new EncryptedApngCacheEncoder(secret));
|
||||
registry.prepend(File.class, APNGDecoder.class, new EncryptedCacheDecoder<>(secret, streamApngDecoder));
|
||||
registry.register(APNGDecoder.class, Drawable.class, new ApngFrameDrawableTranscoder());
|
||||
}
|
||||
|
||||
registry.prepend(BlurHash.class, Bitmap.class, new BlurHashResourceDecoder());
|
||||
registry.prepend(StoryTextPostModel.class, Bitmap.class, new StoryTextPostModel.Decoder());
|
||||
|
||||
28
app/src/main/java/org/thoughtcrime/securesms/glide/cache/ApngDrawableTranscoder.kt
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
package org.thoughtcrime.securesms.glide.cache
|
||||
|
||||
import android.graphics.drawable.Drawable
|
||||
import com.bumptech.glide.load.Options
|
||||
import com.bumptech.glide.load.engine.Resource
|
||||
import com.bumptech.glide.load.resource.drawable.DrawableResource
|
||||
import com.bumptech.glide.load.resource.transcode.ResourceTranscoder
|
||||
import org.signal.apng.ApngDecoder
|
||||
import org.signal.apng.ApngDrawable
|
||||
|
||||
class ApngDrawableTranscoder : ResourceTranscoder<ApngDecoder, Drawable> {
|
||||
override fun transcode(toTranscode: Resource<ApngDecoder>, options: Options): Resource<Drawable> {
|
||||
val decoder = toTranscode.get()
|
||||
val drawable = ApngDrawable(decoder).apply {
|
||||
loopForever = true
|
||||
}
|
||||
|
||||
return object : DrawableResource<Drawable>(drawable) {
|
||||
override fun getResourceClass(): Class<Drawable> = Drawable::class.java
|
||||
|
||||
override fun getSize(): Int = 0
|
||||
|
||||
override fun recycle() {
|
||||
(get() as ApngDrawable).recycle()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright 2024 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.securesms.glide.cache
|
||||
|
||||
import com.bumptech.glide.load.Options
|
||||
import com.bumptech.glide.load.ResourceDecoder
|
||||
import com.bumptech.glide.load.engine.Resource
|
||||
import org.signal.apng.ApngDecoder
|
||||
import org.signal.core.util.readFully
|
||||
import org.signal.glide.apng.ApngOptions
|
||||
import org.signal.glide.common.io.InputStreamFactory
|
||||
import java.io.ByteArrayInputStream
|
||||
import java.io.IOException
|
||||
|
||||
class ApngInputStreamFactoryResourceDecoder : ResourceDecoder<InputStreamFactory, ApngDecoder> {
|
||||
|
||||
override fun handles(source: InputStreamFactory, options: Options): Boolean {
|
||||
return if (options.get(ApngOptions.ANIMATE) == true) {
|
||||
ApngDecoder.isApng(source.create())
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
override fun decode(source: InputStreamFactory, width: Int, height: Int, options: Options): Resource<ApngDecoder>? {
|
||||
val data: ByteArray = source.create().readFully()
|
||||
val decoder = ApngDecoder(ByteArrayInputStream(data))
|
||||
return ApngResource(decoder, data.size)
|
||||
}
|
||||
}
|
||||
39
app/src/main/java/org/thoughtcrime/securesms/glide/cache/ApngInputStreamResourceDecoder.kt
vendored
Normal file
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Copyright 2024 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.securesms.glide.cache
|
||||
|
||||
import com.bumptech.glide.load.Options
|
||||
import com.bumptech.glide.load.ResourceDecoder
|
||||
import com.bumptech.glide.load.engine.Resource
|
||||
import org.signal.apng.ApngDecoder
|
||||
import org.signal.core.util.readFully
|
||||
import org.signal.core.util.stream.LimitedInputStream
|
||||
import org.signal.glide.apng.ApngOptions
|
||||
import java.io.ByteArrayInputStream
|
||||
import java.io.IOException
|
||||
import java.io.InputStream
|
||||
|
||||
class ApngInputStreamResourceDecoder : ResourceDecoder<InputStream, ApngDecoder> {
|
||||
companion object {
|
||||
/** Set to match [com.bumptech.glide.load.data.InputStreamRewinder]'s read limit */
|
||||
private const val READ_LIMIT: Long = 5 * 1024 * 1024
|
||||
}
|
||||
|
||||
override fun handles(source: InputStream, options: Options): Boolean {
|
||||
return if (options.get(ApngOptions.ANIMATE)!!) {
|
||||
ApngDecoder.isApng(LimitedInputStream(source, READ_LIMIT))
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
override fun decode(source: InputStream, width: Int, height: Int, options: Options): Resource<ApngDecoder>? {
|
||||
val data: ByteArray = source.readFully()
|
||||
val decoder = ApngDecoder(ByteArrayInputStream(data))
|
||||
return ApngResource(decoder, data.size)
|
||||
}
|
||||
}
|
||||
21
app/src/main/java/org/thoughtcrime/securesms/glide/cache/ApngResource.kt
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
/*
|
||||
* Copyright 2024 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.securesms.glide.cache
|
||||
|
||||
import com.bumptech.glide.load.engine.Resource
|
||||
import org.signal.apng.ApngDecoder
|
||||
|
||||
class ApngResource(private val decoder: ApngDecoder, private val size: Int) : Resource<ApngDecoder> {
|
||||
override fun getResourceClass(): Class<ApngDecoder> = ApngDecoder::class.java
|
||||
|
||||
override fun get(): ApngDecoder = decoder
|
||||
|
||||
override fun getSize(): Int = size
|
||||
|
||||
override fun recycle() {
|
||||
decoder.inputStream.close()
|
||||
}
|
||||
}
|
||||
36
app/src/main/java/org/thoughtcrime/securesms/glide/cache/EncryptedApngResourceEncoder.kt
vendored
Normal file
@@ -0,0 +1,36 @@
|
||||
package org.thoughtcrime.securesms.glide.cache
|
||||
|
||||
import com.bumptech.glide.load.EncodeStrategy
|
||||
import com.bumptech.glide.load.Options
|
||||
import com.bumptech.glide.load.ResourceEncoder
|
||||
import com.bumptech.glide.load.engine.Resource
|
||||
import org.signal.apng.ApngDecoder
|
||||
import org.signal.core.util.logging.Log.tag
|
||||
import org.signal.core.util.logging.Log.w
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
|
||||
internal class EncryptedApngResourceEncoder(private val secret: ByteArray) : EncryptedCoder(), ResourceEncoder<ApngDecoder> {
|
||||
override fun getEncodeStrategy(options: Options): EncodeStrategy {
|
||||
return EncodeStrategy.SOURCE
|
||||
}
|
||||
|
||||
override fun encode(data: Resource<ApngDecoder>, file: File, options: Options): Boolean {
|
||||
try {
|
||||
val input = data.get().inputStream
|
||||
val output = createEncryptedOutputStream(secret, file)
|
||||
|
||||
input.reset()
|
||||
input.copyTo(output)
|
||||
|
||||
return true
|
||||
} catch (e: IOException) {
|
||||
w(TAG, e)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val TAG = tag(EncryptedApngResourceEncoder::class.java)
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,7 @@ class LabsValues internal constructor(store: KeyValueStore) : SignalStoreValues(
|
||||
const val GROUP_SUGGESTIONS_FOR_MEMBERS: String = "labs.group_suggestions_for_members"
|
||||
const val BETTER_SEARCH: String = "labs.better_search"
|
||||
const val AUTO_LOWER_HAND: String = "labs.auto_lower_hand"
|
||||
const val NEW_APNG_RENDERER: String = "labs.new_apng_renderer"
|
||||
}
|
||||
|
||||
public override fun onFirstEverAppLaunch() = Unit
|
||||
@@ -28,6 +29,8 @@ class LabsValues internal constructor(store: KeyValueStore) : SignalStoreValues(
|
||||
|
||||
var autoLowerHand by booleanValue(AUTO_LOWER_HAND, true).falseForExternalUsers()
|
||||
|
||||
var newApngRenderer by booleanValue(NEW_APNG_RENDERER, true).falseForExternalUsers()
|
||||
|
||||
private fun SignalStoreValueDelegate<Boolean>.falseForExternalUsers(): SignalStoreValueDelegate<Boolean> {
|
||||
return this.map { actualValue -> RemoteConfig.internalUser && actualValue }
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ import androidx.annotation.RequiresApi;
|
||||
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.dependencies.AppDependencies;
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||
|
||||
/**
|
||||
* Easy access to various properties of the device, typically to make performance-related decisions.
|
||||
@@ -25,6 +26,10 @@ public final class DeviceProperties {
|
||||
* large numbers of APNGs simultaneously.
|
||||
*/
|
||||
public static boolean shouldAllowApngStickerAnimation(@NonNull Context context) {
|
||||
if (SignalStore.labs().getNewApngRenderer()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
MemoryInfo memoryInfo = getMemoryInfo(context);
|
||||
int memoryMb = (int) ByteUnit.BYTES.toMegabytes(memoryInfo.totalMem);
|
||||
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* Copyright 2024 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.signal.core.util
|
||||
|
||||
import java.nio.ByteBuffer
|
||||
import java.nio.ByteOrder
|
||||
|
||||
fun ByteArray.toUInt(): UInt {
|
||||
return ByteBuffer.wrap(this).order(ByteOrder.BIG_ENDIAN).int.toUInt()
|
||||
}
|
||||
|
||||
fun ByteArray.toUShort(): UShort {
|
||||
return ByteBuffer.wrap(this).order(ByteOrder.BIG_ENDIAN).getShort().toUShort()
|
||||
}
|
||||
@@ -10,6 +10,7 @@ import java.io.ByteArrayOutputStream
|
||||
import java.io.IOException
|
||||
import java.io.InputStream
|
||||
import java.io.OutputStream
|
||||
import kotlin.jvm.Throws
|
||||
import kotlin.math.min
|
||||
|
||||
/**
|
||||
@@ -107,6 +108,13 @@ fun InputStream.readLength(): Long {
|
||||
return count
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a 32-bit unsigned integer from the stream.
|
||||
*/
|
||||
fun InputStream.readUInt(): UInt {
|
||||
return this.readNBytesOrThrow(4).toUInt()
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the contents of the stream and discards them.
|
||||
*/
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
package org.signal.core.util
|
||||
|
||||
import java.io.OutputStream
|
||||
import java.nio.ByteBuffer
|
||||
import java.nio.ByteOrder
|
||||
|
||||
/**
|
||||
* Writes a 32-bit variable-length integer to the stream.
|
||||
@@ -30,3 +32,11 @@ fun OutputStream.writeVarInt32(value: Int) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes a 32-bit unsigned integer to the stream.
|
||||
*/
|
||||
fun OutputStream.writeUInt(value: UInt) {
|
||||
// Note that casting to an int here is fine, because at the end of the day, we're just writing 4 bytes to the stream
|
||||
this.write(ByteBuffer.allocate(4).order(ByteOrder.BIG_ENDIAN).putInt(value.toInt()).array())
|
||||
}
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright 2024 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.signal.core.util.stream
|
||||
|
||||
import java.io.FilterOutputStream
|
||||
import java.io.OutputStream
|
||||
import java.util.zip.CRC32
|
||||
|
||||
/**
|
||||
* A simple pass-through stream that calculates a CRC32 as data is written to the target [OutputStream].
|
||||
*/
|
||||
class Crc32OutputStream(private val wrapped: OutputStream) : FilterOutputStream(wrapped) {
|
||||
private val crc32 = CRC32()
|
||||
|
||||
val currentCrc32: Long
|
||||
get() = crc32.value
|
||||
|
||||
override fun write(byte: Int) {
|
||||
wrapped.write(byte)
|
||||
crc32.update(byte)
|
||||
}
|
||||
|
||||
override fun write(data: ByteArray) {
|
||||
write(data, 0, data.size)
|
||||
}
|
||||
|
||||
override fun write(data: ByteArray, offset: Int, length: Int) {
|
||||
wrapped.write(data, offset, length)
|
||||
crc32.update(data, offset, length)
|
||||
}
|
||||
}
|
||||
1
demo/apng/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/build
|
||||
16
demo/apng/build.gradle.kts
Normal file
@@ -0,0 +1,16 @@
|
||||
plugins {
|
||||
id("signal-sample-app")
|
||||
alias(libs.plugins.compose.compiler)
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "org.signal.apng"
|
||||
|
||||
defaultConfig {
|
||||
applicationId = "org.signal.apng"
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(project(":lib:apng"))
|
||||
}
|
||||
21
demo/apng/proguard-rules.pro
vendored
Normal file
@@ -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
|
||||
39
demo/apng/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1,39 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ Copyright 2024 Signal Messenger, LLC
|
||||
~ SPDX-License-Identifier: AGPL-3.0-only
|
||||
-->
|
||||
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/Theme.Signal">
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:exported="true"
|
||||
android:label="@string/app_name"
|
||||
android:theme="@style/Theme.Signal">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name=".DemoActivity"
|
||||
android:exported="false"
|
||||
android:theme="@style/Theme.Signal" />
|
||||
|
||||
<activity
|
||||
android:name=".PlayerActivity"
|
||||
android:exported="false"
|
||||
android:theme="@style/Theme.Signal" />
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
BIN
demo/apng/src/main/assets/ball.png
Normal file
|
After Width: | Height: | Size: 60 KiB |
BIN
demo/apng/src/main/assets/broken01.png
Normal file
|
After Width: | Height: | Size: 91 KiB |
BIN
demo/apng/src/main/assets/broken02.png
Normal file
|
After Width: | Height: | Size: 126 KiB |
BIN
demo/apng/src/main/assets/broken03.png
Normal file
|
After Width: | Height: | Size: 221 KiB |
BIN
demo/apng/src/main/assets/broken05.png
Normal file
|
After Width: | Height: | Size: 296 KiB |
BIN
demo/apng/src/main/assets/clock.png
Normal file
|
After Width: | Height: | Size: 23 KiB |
BIN
demo/apng/src/main/assets/elephant.png
Normal file
|
After Width: | Height: | Size: 370 KiB |
BIN
demo/apng/src/main/assets/test00.png
Normal file
|
After Width: | Height: | Size: 249 B |
BIN
demo/apng/src/main/assets/test01.png
Normal file
|
After Width: | Height: | Size: 307 B |
BIN
demo/apng/src/main/assets/test02.png
Normal file
|
After Width: | Height: | Size: 470 B |
BIN
demo/apng/src/main/assets/test03.png
Normal file
|
After Width: | Height: | Size: 261 B |
BIN
demo/apng/src/main/assets/test04.png
Normal file
|
After Width: | Height: | Size: 273 B |
BIN
demo/apng/src/main/assets/test05.png
Normal file
|
After Width: | Height: | Size: 486 B |
BIN
demo/apng/src/main/assets/test06.png
Normal file
|
After Width: | Height: | Size: 502 B |
BIN
demo/apng/src/main/assets/test07.png
Normal file
|
After Width: | Height: | Size: 617 B |
BIN
demo/apng/src/main/assets/test08.png
Normal file
|
After Width: | Height: | Size: 572 B |
BIN
demo/apng/src/main/assets/test09.png
Normal file
|
After Width: | Height: | Size: 508 B |
BIN
demo/apng/src/main/assets/test10.png
Normal file
|
After Width: | Height: | Size: 780 B |
BIN
demo/apng/src/main/assets/test11.png
Normal file
|
After Width: | Height: | Size: 508 B |
BIN
demo/apng/src/main/assets/test12.png
Normal file
|
After Width: | Height: | Size: 371 B |
BIN
demo/apng/src/main/assets/test13.png
Normal file
|
After Width: | Height: | Size: 613 B |
BIN
demo/apng/src/main/assets/test14.png
Normal file
|
After Width: | Height: | Size: 327 B |
BIN
demo/apng/src/main/assets/test15.png
Normal file
|
After Width: | Height: | Size: 492 B |
BIN
demo/apng/src/main/assets/test16.png
Normal file
|
After Width: | Height: | Size: 677 B |
BIN
demo/apng/src/main/assets/test17.png
Normal file
|
After Width: | Height: | Size: 671 B |
BIN
demo/apng/src/main/assets/test18.png
Normal file
|
After Width: | Height: | Size: 534 B |
BIN
demo/apng/src/main/assets/test19.png
Normal file
|
After Width: | Height: | Size: 614 B |
BIN
demo/apng/src/main/assets/test20.png
Normal file
|
After Width: | Height: | Size: 579 B |
BIN
demo/apng/src/main/assets/test21.png
Normal file
|
After Width: | Height: | Size: 28 KiB |
BIN
demo/apng/src/main/assets/test22.png
Normal file
|
After Width: | Height: | Size: 3.3 KiB |
BIN
demo/apng/src/main/assets/test23.png
Normal file
|
After Width: | Height: | Size: 3.4 KiB |
BIN
demo/apng/src/main/assets/test24.png
Normal file
|
After Width: | Height: | Size: 508 B |
BIN
demo/apng/src/main/assets/test25.png
Normal file
|
After Width: | Height: | Size: 1.0 KiB |
BIN
demo/apng/src/main/assets/test26.png
Normal file
|
After Width: | Height: | Size: 646 B |
BIN
demo/apng/src/main/assets/test27.png
Normal file
|
After Width: | Height: | Size: 646 B |
BIN
demo/apng/src/main/assets/test28.png
Normal file
|
After Width: | Height: | Size: 646 B |
BIN
demo/apng/src/main/assets/test29.png
Normal file
|
After Width: | Height: | Size: 1.0 KiB |
BIN
demo/apng/src/main/assets/test30.png
Normal file
|
After Width: | Height: | Size: 646 B |
BIN
demo/apng/src/main/assets/test31.png
Normal file
|
After Width: | Height: | Size: 646 B |
BIN
demo/apng/src/main/assets/test32.png
Normal file
|
After Width: | Height: | Size: 646 B |
BIN
demo/apng/src/main/assets/test33.png
Normal file
|
After Width: | Height: | Size: 915 B |
BIN
demo/apng/src/main/assets/test34.png
Normal file
|
After Width: | Height: | Size: 331 B |
BIN
demo/apng/src/main/assets/test35.png
Normal file
|
After Width: | Height: | Size: 668 B |
BIN
demo/apng/src/main/assets/test36.png
Normal file
|
After Width: | Height: | Size: 262 B |
BIN
demo/apng/src/main/assets/test37.png
Normal file
|
After Width: | Height: | Size: 308 B |
BIN
demo/apng/src/main/assets/test38.png
Normal file
|
After Width: | Height: | Size: 276 B |
298
demo/apng/src/main/java/org/signal/apng/DemoActivity.kt
Normal file
@@ -0,0 +1,298 @@
|
||||
/*
|
||||
* Copyright 2024 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.signal.apng
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.ListAdapter
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import androidx.recyclerview.widget.RecyclerView.ViewHolder
|
||||
|
||||
class DemoActivity : ComponentActivity() {
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.demo_activity)
|
||||
|
||||
val myAdapter = MyAdapter()
|
||||
|
||||
findViewById<RecyclerView?>(R.id.list).apply {
|
||||
layoutManager = LinearLayoutManager(this@DemoActivity)
|
||||
adapter = myAdapter
|
||||
}
|
||||
|
||||
// All test cases taken from:
|
||||
// https://philip.html5.org/tests/apng/tests.html
|
||||
myAdapter.submitList(
|
||||
listOf(
|
||||
TestModel.Heading("Basic cases"),
|
||||
// TODO we don't yet render non-apngs
|
||||
// TestModel(
|
||||
// filename = "test00.png",
|
||||
// description = "Trivial static image. This should be solid green."
|
||||
// ),
|
||||
TestModel.ApngModel(
|
||||
filename = "test01.png",
|
||||
description = "Trivial animated image - one frame; using default image. This should be solid green."
|
||||
),
|
||||
TestModel.ApngModel(
|
||||
filename = "test02.png",
|
||||
description = "Trivial animated image - one frame; ignoring default image. This should be solid green."
|
||||
),
|
||||
|
||||
// IDAT, fdAT splitting
|
||||
// TODO we don't yet support split IDAT/fdAT
|
||||
// _root_ide_package_.org.signal.apng.MainActivity.TestModel.ApngModel(
|
||||
// filename = "test03.png",
|
||||
// description = "Basic split IDAT. This should be solid green."
|
||||
// ),
|
||||
// _root_ide_package_.org.signal.apng.MainActivity.TestModel.ApngModel(
|
||||
// filename = "test04.png",
|
||||
// description = "Split IDAT with zero-length chunk. This should be solid green."
|
||||
// ),
|
||||
// _root_ide_package_.org.signal.apng.MainActivity.TestModel.ApngModel(
|
||||
// filename = "test05.png",
|
||||
// description = "Basic split fdAT. This should be solid green."
|
||||
// ),
|
||||
// _root_ide_package_.org.signal.apng.MainActivity.TestModel.ApngModel(
|
||||
// filename = "test06.png",
|
||||
// description = "Split fdAT with zero-length chunk. This should be solid green."
|
||||
// ),
|
||||
|
||||
TestModel.Heading("Dispose ops"),
|
||||
TestModel.ApngModel(
|
||||
filename = "test07.png",
|
||||
description = "APNG_DISPOSE_OP_NONE - basic. This should be solid green."
|
||||
),
|
||||
TestModel.ApngModel(
|
||||
filename = "test08.png",
|
||||
description = "APNG_DISPOSE_OP_BACKGROUND - basic. This should be transparent."
|
||||
),
|
||||
TestModel.ApngModel(
|
||||
filename = "test09.png",
|
||||
description = "APNG_DISPOSE_OP_BACKGROUND - final frame. This should be solid green."
|
||||
),
|
||||
TestModel.ApngModel(
|
||||
filename = "test10.png",
|
||||
description = "APNG_DISPOSE_OP_PREVIOUS - basic. This should be solid green."
|
||||
),
|
||||
TestModel.ApngModel(
|
||||
filename = "test11.png",
|
||||
description = "APNG_DISPOSE_OP_PREVIOUS - final frame. This should be solid green."
|
||||
),
|
||||
TestModel.ApngModel(
|
||||
filename = "test12.png",
|
||||
description = "APNG_DISPOSE_OP_PREVIOUS - first frame. This should be transparent."
|
||||
),
|
||||
|
||||
TestModel.Heading("Dispose ops and regions"),
|
||||
TestModel.ApngModel(
|
||||
filename = "test13.png",
|
||||
description = "APNG_DISPOSE_OP_NONE in region. This should be solid green."
|
||||
),
|
||||
TestModel.ApngModel(
|
||||
filename = "test14.png",
|
||||
description = "APNG_DISPOSE_OP_BACKGROUND before region. This should be transparent."
|
||||
),
|
||||
TestModel.ApngModel(
|
||||
filename = "test15.png",
|
||||
description = "APNG_DISPOSE_OP_BACKGROUND in region. This should be a solid blue rectangle containing a smaller transparent rectangle."
|
||||
),
|
||||
TestModel.ApngModel(
|
||||
filename = "test16.png",
|
||||
description = "APNG_DISPOSE_OP_PREVIOUS in region. This should be solid green."
|
||||
),
|
||||
|
||||
TestModel.Heading("Blend ops"),
|
||||
TestModel.ApngModel(
|
||||
filename = "test17.png",
|
||||
description = "APNG_BLEND_OP_SOURCE on solid colour. This should be solid green."
|
||||
),
|
||||
TestModel.ApngModel(
|
||||
filename = "test18.png",
|
||||
description = "APNG_BLEND_OP_SOURCE on transparent colour. This should be transparent."
|
||||
),
|
||||
TestModel.ApngModel(
|
||||
filename = "test19.png",
|
||||
description = "APNG_BLEND_OP_SOURCE on nearly-transparent colour. This should be very nearly transparent."
|
||||
),
|
||||
TestModel.ApngModel(
|
||||
filename = "test20.png",
|
||||
description = "APNG_BLEND_OP_OVER on solid and transparent colours. This should be solid green. "
|
||||
),
|
||||
TestModel.ApngModel(
|
||||
filename = "test21.png",
|
||||
description = "APNG_BLEND_OP_OVER repeatedly with nearly-transparent colours. This should be solid green."
|
||||
),
|
||||
|
||||
TestModel.Heading("Blending and gamma"),
|
||||
TestModel.ApngModel(
|
||||
filename = "test22.png",
|
||||
description = "APNG_BLEND_OP_OVER This should be solid slightly-dark green."
|
||||
),
|
||||
TestModel.ApngModel(
|
||||
filename = "test23.png",
|
||||
description = "APNG_BLEND_OP_OVER This should be solid nearly-black."
|
||||
),
|
||||
|
||||
TestModel.Heading("Chunk ordering"),
|
||||
TestModel.ApngModel(
|
||||
filename = "test24.png",
|
||||
description = "fcTL before acTL. This should be solid green."
|
||||
),
|
||||
|
||||
TestModel.Heading("Delays"),
|
||||
TestModel.ApngModel(
|
||||
filename = "test25.png",
|
||||
description = "Basic delays. This should flash blue for half a second, then yellow for one second, then repeat."
|
||||
),
|
||||
TestModel.ApngModel(
|
||||
filename = "test26.png",
|
||||
description = "Rounding of division. This should flash blue for half a second, then yellow for one second, then repeat."
|
||||
),
|
||||
TestModel.ApngModel(
|
||||
filename = "test27.png",
|
||||
description = "16-bit numerator/denominator. This should flash blue for half a second, then yellow for one second, then repeat."
|
||||
),
|
||||
TestModel.ApngModel(
|
||||
filename = "test28.png",
|
||||
description = "Zero denominator. This should flash blue for half a second, then yellow for one second, then repeat."
|
||||
),
|
||||
TestModel.ApngModel(
|
||||
filename = "test29.png",
|
||||
description = "Zero numerator. This should flash cyan for a short period of time (perhaps zero), then magenta for the same short period of time, then blue for half a second, then yellow for one second, then repeat."
|
||||
),
|
||||
|
||||
TestModel.Heading("num_plays"),
|
||||
TestModel.ApngModel(
|
||||
filename = "test30.png",
|
||||
description = "num_plays = 0. This should flash yellow for one second, then blue for one second, then repeat forever."
|
||||
),
|
||||
TestModel.ApngModel(
|
||||
filename = "test31.png",
|
||||
description = "num_plays = 1. When first loaded, this should flash yellow for one second, then stay blue forever."
|
||||
),
|
||||
TestModel.ApngModel(
|
||||
filename = "test32.png",
|
||||
description = "num_plays = 2. When first loaded, this should flash yellow for one second, then blue for one second, then yellow for one second, then blue forever."
|
||||
),
|
||||
|
||||
TestModel.Heading("Other depths and color types"),
|
||||
TestModel.ApngModel(
|
||||
filename = "test33.png",
|
||||
description = "16-bit colour. This should be dark blue."
|
||||
),
|
||||
TestModel.ApngModel(
|
||||
filename = "test34.png",
|
||||
description = "8-bit greyscale. This should be a solid grey rectangle containing a solid white rectangle."
|
||||
),
|
||||
TestModel.ApngModel(
|
||||
filename = "test35.png",
|
||||
description = "8-bit greyscale and alpha, with blending. This should be solid grey."
|
||||
),
|
||||
TestModel.ApngModel(
|
||||
filename = "test36.png",
|
||||
description = "2-color palette. This should be solid green."
|
||||
),
|
||||
TestModel.ApngModel(
|
||||
filename = "test37.png",
|
||||
description = "2-bit palette and alpha. This should be solid green."
|
||||
),
|
||||
TestModel.ApngModel(
|
||||
filename = "test38.png",
|
||||
description = "1-bit palette and alpha, with blending. This should be solid dark blue."
|
||||
),
|
||||
|
||||
TestModel.Heading("Random sample images"),
|
||||
TestModel.ApngModel(
|
||||
filename = "clock.png",
|
||||
description = "A clock that uses BLEND_OP_OVER to draw moving hands."
|
||||
),
|
||||
TestModel.ApngModel(
|
||||
filename = "ball.png",
|
||||
description = "Classic bouncing ball."
|
||||
),
|
||||
TestModel.ApngModel(
|
||||
filename = "elephant.png",
|
||||
description = "A cute elephant"
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
sealed class TestModel {
|
||||
data class Heading(val description: String) : TestModel()
|
||||
data class ApngModel(val filename: String, val description: String) : TestModel()
|
||||
}
|
||||
|
||||
private class MyAdapter : ListAdapter<TestModel, TestViewHolder>(object : DiffUtil.ItemCallback<TestModel>() {
|
||||
override fun areItemsTheSame(oldItem: TestModel, newItem: TestModel): Boolean {
|
||||
return oldItem == newItem
|
||||
}
|
||||
|
||||
override fun areContentsTheSame(oldItem: TestModel, newItem: TestModel): Boolean {
|
||||
return oldItem == newItem
|
||||
}
|
||||
}) {
|
||||
override fun getItemViewType(position: Int): Int {
|
||||
return when (getItem(position)) {
|
||||
is TestModel.Heading -> 1
|
||||
is TestModel.ApngModel -> 2
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TestViewHolder {
|
||||
when (viewType) {
|
||||
1 -> return TestViewHolder.HeadingViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.list_item_heading, parent, false))
|
||||
2 -> return TestViewHolder.ApngViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.list_item_apng, parent, false))
|
||||
else -> throw IllegalStateException()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: TestViewHolder, position: Int) {
|
||||
holder.bind(getItem(position))
|
||||
}
|
||||
}
|
||||
|
||||
sealed class TestViewHolder(itemView: View) : ViewHolder(itemView) {
|
||||
abstract fun bind(testModel: TestModel)
|
||||
|
||||
class HeadingViewHolder(itemView: View) : TestViewHolder(itemView) {
|
||||
val description: TextView = itemView.findViewById(R.id.description)
|
||||
|
||||
override fun bind(testModel: TestModel) {
|
||||
if (testModel !is TestModel.Heading) {
|
||||
throw IllegalStateException()
|
||||
}
|
||||
|
||||
description.text = testModel.description
|
||||
}
|
||||
}
|
||||
|
||||
class ApngViewHolder(itemView: View) : TestViewHolder(itemView) {
|
||||
val description: TextView = itemView.findViewById(R.id.description)
|
||||
val image: ImageView = itemView.findViewById(R.id.image)
|
||||
|
||||
override fun bind(testModel: TestModel) {
|
||||
if (testModel !is TestModel.ApngModel) {
|
||||
throw IllegalStateException()
|
||||
}
|
||||
|
||||
description.text = testModel.description
|
||||
|
||||
val decoder = ApngDecoder(itemView.context.assets.open(testModel.filename))
|
||||
val drawable = ApngDrawable(decoder)
|
||||
image.setImageDrawable(drawable)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
25
demo/apng/src/main/java/org/signal/apng/MainActivity.kt
Normal file
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Copyright 2024 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.signal.apng
|
||||
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.widget.Button
|
||||
import androidx.activity.ComponentActivity
|
||||
|
||||
class MainActivity : ComponentActivity() {
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.main_activity)
|
||||
|
||||
findViewById<Button>(R.id.demo_button).setOnClickListener {
|
||||
startActivity(Intent(this, DemoActivity::class.java))
|
||||
}
|
||||
findViewById<Button>(R.id.player_button).setOnClickListener {
|
||||
startActivity(Intent(this, PlayerActivity::class.java))
|
||||
}
|
||||
}
|
||||
}
|
||||
67
demo/apng/src/main/java/org/signal/apng/PlayerActivity.kt
Normal file
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
* Copyright 2024 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.signal.apng
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.os.Bundle
|
||||
import android.widget.Button
|
||||
import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
import androidx.activity.ComponentActivity
|
||||
|
||||
class PlayerActivity : ComponentActivity() {
|
||||
lateinit var frameMetadata: TextView
|
||||
lateinit var disposeOpText: TextView
|
||||
lateinit var blendOpText: TextView
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.player_activity)
|
||||
|
||||
val image = findViewById<ImageView>(R.id.player_image)
|
||||
val prevButton = findViewById<Button>(R.id.prev_button)
|
||||
val nextButton = findViewById<Button>(R.id.next_button)
|
||||
frameMetadata = findViewById<TextView>(R.id.frame_metadata)
|
||||
|
||||
val decoder = ApngDecoder(assets.open("broken03.png"))
|
||||
val drawable = ApngDrawable(decoder).apply {
|
||||
stop()
|
||||
debugDrawBounds = true
|
||||
}
|
||||
|
||||
image.setImageDrawable(drawable)
|
||||
|
||||
prevButton.setOnClickListener {
|
||||
drawable.prevFrame()
|
||||
updateFrameInfo(drawable)
|
||||
}
|
||||
|
||||
nextButton.setOnClickListener {
|
||||
drawable.nextFrame()
|
||||
updateFrameInfo(drawable)
|
||||
}
|
||||
|
||||
updateFrameInfo(drawable)
|
||||
}
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
private fun updateFrameInfo(drawable: ApngDrawable) {
|
||||
frameMetadata.text = """
|
||||
Frame: ${drawable.position + 1}/${drawable.frameCount}
|
||||
--
|
||||
Width: ${drawable.currentFrame.fcTL.width}
|
||||
Height: ${drawable.currentFrame.fcTL.height}
|
||||
--
|
||||
xOffset: ${drawable.currentFrame.fcTL.xOffset}
|
||||
yOffset: ${drawable.currentFrame.fcTL.yOffset}
|
||||
--
|
||||
DisposeOp: ${drawable.currentFrame.fcTL.disposeOp.name}
|
||||
BlendOp: ${drawable.currentFrame.fcTL.blendOp.name}
|
||||
|
||||
WARNING: Going backwards can break rendering.
|
||||
""".trimIndent()
|
||||
}
|
||||
}
|
||||
11
demo/apng/src/main/java/org/signal/apng/ui/theme/Color.kt
Normal file
@@ -0,0 +1,11 @@
|
||||
package org.signal.apng.ui.theme
|
||||
|
||||
import androidx.compose.ui.graphics.Color
|
||||
|
||||
val Purple80 = Color(0xFFD0BCFF)
|
||||
val PurpleGrey80 = Color(0xFFCCC2DC)
|
||||
val Pink80 = Color(0xFFEFB8C8)
|
||||
|
||||
val Purple40 = Color(0xFF6650a4)
|
||||
val PurpleGrey40 = Color(0xFF625b71)
|
||||
val Pink40 = Color(0xFF7D5260)
|
||||
75
demo/apng/src/main/java/org/signal/apng/ui/theme/Theme.kt
Normal file
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* Copyright 2024 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.signal.apng.ui.theme
|
||||
|
||||
import android.app.Activity
|
||||
import android.os.Build
|
||||
import androidx.compose.foundation.isSystemInDarkTheme
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.darkColorScheme
|
||||
import androidx.compose.material3.dynamicDarkColorScheme
|
||||
import androidx.compose.material3.dynamicLightColorScheme
|
||||
import androidx.compose.material3.lightColorScheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.SideEffect
|
||||
import androidx.compose.ui.graphics.toArgb
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalView
|
||||
import androidx.core.view.WindowCompat
|
||||
|
||||
private val DarkColorScheme = darkColorScheme(
|
||||
primary = Purple80,
|
||||
secondary = PurpleGrey80,
|
||||
tertiary = Pink80
|
||||
)
|
||||
|
||||
private val LightColorScheme = lightColorScheme(
|
||||
primary = Purple40,
|
||||
secondary = PurpleGrey40,
|
||||
tertiary = Pink40
|
||||
|
||||
/* Other default colors to override
|
||||
background = Color(0xFFFFFBFE),
|
||||
surface = Color(0xFFFFFBFE),
|
||||
onPrimary = Color.White,
|
||||
onSecondary = Color.White,
|
||||
onTertiary = Color.White,
|
||||
onBackground = Color(0xFF1C1B1F),
|
||||
onSurface = Color(0xFF1C1B1F),
|
||||
*/
|
||||
)
|
||||
|
||||
@Composable
|
||||
fun SignalTheme(
|
||||
darkTheme: Boolean = isSystemInDarkTheme(),
|
||||
// Dynamic color is available on Android 12+
|
||||
dynamicColor: Boolean = true,
|
||||
content: @Composable () -> Unit
|
||||
) {
|
||||
val colorScheme = when {
|
||||
dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
|
||||
val context = LocalContext.current
|
||||
if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
|
||||
}
|
||||
|
||||
darkTheme -> DarkColorScheme
|
||||
else -> LightColorScheme
|
||||
}
|
||||
val view = LocalView.current
|
||||
if (!view.isInEditMode) {
|
||||
SideEffect {
|
||||
val window = (view.context as Activity).window
|
||||
window.statusBarColor = colorScheme.primary.toArgb()
|
||||
WindowCompat.getInsetsController(window, view).isAppearanceLightStatusBars = darkTheme
|
||||
}
|
||||
}
|
||||
|
||||
MaterialTheme(
|
||||
colorScheme = colorScheme,
|
||||
typography = Typography,
|
||||
content = content
|
||||
)
|
||||
}
|
||||
39
demo/apng/src/main/java/org/signal/apng/ui/theme/Type.kt
Normal file
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Copyright 2024 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.signal.apng.ui.theme
|
||||
|
||||
import androidx.compose.material3.Typography
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.text.font.FontFamily
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.sp
|
||||
|
||||
// Set of Material typography styles to start with
|
||||
val Typography = Typography(
|
||||
bodyLarge = TextStyle(
|
||||
fontFamily = FontFamily.Default,
|
||||
fontWeight = FontWeight.Normal,
|
||||
fontSize = 16.sp,
|
||||
lineHeight = 24.sp,
|
||||
letterSpacing = 0.5.sp
|
||||
)
|
||||
/* Other default text styles to override
|
||||
titleLarge = TextStyle(
|
||||
fontFamily = FontFamily.Default,
|
||||
fontWeight = FontWeight.Normal,
|
||||
fontSize = 22.sp,
|
||||
lineHeight = 28.sp,
|
||||
letterSpacing = 0.sp
|
||||
),
|
||||
labelSmall = TextStyle(
|
||||
fontFamily = FontFamily.Default,
|
||||
fontWeight = FontWeight.Medium,
|
||||
fontSize = 11.sp,
|
||||
lineHeight = 16.sp,
|
||||
letterSpacing = 0.5.sp
|
||||
)
|
||||
*/
|
||||
)
|
||||
@@ -0,0 +1,35 @@
|
||||
<!--
|
||||
~ Copyright 2024 Signal Messenger, LLC
|
||||
~ SPDX-License-Identifier: AGPL-3.0-only
|
||||
-->
|
||||
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:aapt="http://schemas.android.com/aapt"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportWidth="108"
|
||||
android:viewportHeight="108">
|
||||
<path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z">
|
||||
<aapt:attr name="android:fillColor">
|
||||
<gradient
|
||||
android:endX="85.84757"
|
||||
android:endY="92.4963"
|
||||
android:startX="42.9492"
|
||||
android:startY="49.59793"
|
||||
android:type="linear">
|
||||
<item
|
||||
android:color="#44000000"
|
||||
android:offset="0.0" />
|
||||
<item
|
||||
android:color="#00000000"
|
||||
android:offset="1.0" />
|
||||
</gradient>
|
||||
</aapt:attr>
|
||||
</path>
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:fillType="nonZero"
|
||||
android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z"
|
||||
android:strokeWidth="1"
|
||||
android:strokeColor="#00000000" />
|
||||
</vector>
|
||||
175
demo/apng/src/main/res/drawable/ic_launcher_background.xml
Normal file
@@ -0,0 +1,175 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ Copyright 2024 Signal Messenger, LLC
|
||||
~ SPDX-License-Identifier: AGPL-3.0-only
|
||||
-->
|
||||
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportWidth="108"
|
||||
android:viewportHeight="108">
|
||||
<path
|
||||
android:fillColor="#3DDC84"
|
||||
android:pathData="M0,0h108v108h-108z" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M9,0L9,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,0L19,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M29,0L29,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M39,0L39,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M49,0L49,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M59,0L59,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M69,0L69,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M79,0L79,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M89,0L89,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M99,0L99,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,9L108,9"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,19L108,19"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,29L108,29"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,39L108,39"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,49L108,49"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,59L108,59"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,69L108,69"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,79L108,79"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,89L108,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,99L108,99"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,29L89,29"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,39L89,39"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,49L89,49"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,59L89,59"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,69L89,69"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,79L89,79"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M29,19L29,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M39,19L39,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M49,19L49,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M59,19L59,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M69,19L69,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M79,19L79,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
</vector>
|
||||
16
demo/apng/src/main/res/layout/demo_activity.xml
Normal file
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
tools:context=".DemoActivity">
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/list"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" />
|
||||
|
||||
</LinearLayout>
|
||||
23
demo/apng/src/main/res/layout/list_item_apng.xml
Normal file
@@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:padding="8dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/description"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="4dp"
|
||||
android:text="@string/apng_description_placeholder"/>
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/image"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
</LinearLayout>
|
||||
20
demo/apng/src/main/res/layout/list_item_heading.xml
Normal file
@@ -0,0 +1,20 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:padding="8dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/description"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="4dp"
|
||||
android:textAppearance="@android:style/TextAppearance.Material.Headline"
|
||||
tools:text="Some title"/>
|
||||
|
||||
</LinearLayout>
|
||||
24
demo/apng/src/main/res/layout/main_activity.xml
Normal file
@@ -0,0 +1,24 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:gravity="center_vertical"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
tools:context=".DemoActivity">
|
||||
|
||||
<Button
|
||||
android:id="@+id/demo_button"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/apng_demo" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/player_button"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/apng_player" />
|
||||
|
||||
</LinearLayout>
|
||||
41
demo/apng/src/main/res/layout/player_activity.xml
Normal file
@@ -0,0 +1,41 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:gravity="center"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
tools:context=".DemoActivity">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/player_image"
|
||||
android:layout_width="300dp"
|
||||
android:layout_height="300dp" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<Button
|
||||
android:id="@+id/prev_button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/apng_prev" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/next_button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/apng_next" />
|
||||
</LinearLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/frame_metadata"
|
||||
android:layout_width="250dp"
|
||||
android:layout_height="wrap_content"
|
||||
tools:text="10/15" />
|
||||
|
||||
</LinearLayout>
|
||||
11
demo/apng/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
Normal file
@@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ Copyright 2024 Signal Messenger, LLC
|
||||
~ SPDX-License-Identifier: AGPL-3.0-only
|
||||
-->
|
||||
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@drawable/ic_launcher_background" />
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground" />
|
||||
<monochrome android:drawable="@drawable/ic_launcher_foreground" />
|
||||
</adaptive-icon>
|
||||
@@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ Copyright 2024 Signal Messenger, LLC
|
||||
~ SPDX-License-Identifier: AGPL-3.0-only
|
||||
-->
|
||||
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@drawable/ic_launcher_background" />
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground" />
|
||||
<monochrome android:drawable="@drawable/ic_launcher_foreground" />
|
||||
</adaptive-icon>
|
||||
BIN
demo/apng/src/main/res/mipmap-hdpi/ic_launcher.webp
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
demo/apng/src/main/res/mipmap-hdpi/ic_launcher_round.webp
Normal file
|
After Width: | Height: | Size: 2.8 KiB |
BIN
demo/apng/src/main/res/mipmap-mdpi/ic_launcher.webp
Normal file
|
After Width: | Height: | Size: 982 B |
BIN
demo/apng/src/main/res/mipmap-mdpi/ic_launcher_round.webp
Normal file
|
After Width: | Height: | Size: 1.7 KiB |
BIN
demo/apng/src/main/res/mipmap-xhdpi/ic_launcher.webp
Normal file
|
After Width: | Height: | Size: 1.9 KiB |
BIN
demo/apng/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
Normal file
|
After Width: | Height: | Size: 3.8 KiB |
BIN
demo/apng/src/main/res/mipmap-xxhdpi/ic_launcher.webp
Normal file
|
After Width: | Height: | Size: 2.8 KiB |
BIN
demo/apng/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
Normal file
|
After Width: | Height: | Size: 5.8 KiB |
BIN
demo/apng/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
Normal file
|
After Width: | Height: | Size: 3.8 KiB |
BIN
demo/apng/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
Normal file
|
After Width: | Height: | Size: 7.6 KiB |
15
demo/apng/src/main/res/values/colors.xml
Normal file
@@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ Copyright 2024 Signal Messenger, LLC
|
||||
~ SPDX-License-Identifier: AGPL-3.0-only
|
||||
-->
|
||||
|
||||
<resources>
|
||||
<color name="purple_200">#FFBB86FC</color>
|
||||
<color name="purple_500">#FF6200EE</color>
|
||||
<color name="purple_700">#FF3700B3</color>
|
||||
<color name="teal_200">#FF03DAC5</color>
|
||||
<color name="teal_700">#FF018786</color>
|
||||
<color name="black">#FF000000</color>
|
||||
<color name="white">#FFFFFFFF</color>
|
||||
</resources>
|
||||
13
demo/apng/src/main/res/values/strings.xml
Normal file
@@ -0,0 +1,13 @@
|
||||
<!--
|
||||
~ Copyright 2024 Signal Messenger, LLC
|
||||
~ SPDX-License-Identifier: AGPL-3.0-only
|
||||
-->
|
||||
|
||||
<resources>
|
||||
<string name="app_name">apng-app</string>
|
||||
<string name="apng_description_placeholder">idk</string>
|
||||
<string name="apng_demo">Demo</string>
|
||||
<string name="apng_player">Player</string>
|
||||
<string name="apng_prev">Prev</string>
|
||||
<string name="apng_next">Next</string>
|
||||
</resources>
|
||||
10
demo/apng/src/main/res/values/themes.xml
Normal file
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ Copyright 2024 Signal Messenger, LLC
|
||||
~ SPDX-License-Identifier: AGPL-3.0-only
|
||||
-->
|
||||
|
||||
<resources>
|
||||
|
||||
<style name="Theme.Signal" parent="android:Theme.Material.Light.NoActionBar" />
|
||||
</resources>
|
||||
21
demo/apng/src/test/java/org/signal/apng/ExampleUnitTest.kt
Normal file
@@ -0,0 +1,21 @@
|
||||
/*
|
||||
* Copyright 2024 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.signal.apng
|
||||
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Test
|
||||
|
||||
/**
|
||||
* Example local unit test, which will execute on the development machine (host).
|
||||
*
|
||||
* See [testing documentation](http://d.android.com/tools/testing).
|
||||
*/
|
||||
class ExampleUnitTest {
|
||||
@Test
|
||||
fun addition_isCorrect() {
|
||||
assertEquals(4, 2 + 2)
|
||||
}
|
||||
}
|
||||
1
lib/apng/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/build
|
||||
13
lib/apng/build.gradle.kts
Normal file
@@ -0,0 +1,13 @@
|
||||
plugins {
|
||||
id("signal-library")
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "org.signal.apng"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(project(":core:util"))
|
||||
testImplementation(testLibs.junit.junit)
|
||||
testImplementation(testLibs.robolectric.robolectric)
|
||||
}
|
||||
0
lib/apng/consumer-rules.pro
Normal file
21
lib/apng/proguard-rules.pro
vendored
Normal file
@@ -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
|
||||