Work on app

This commit is contained in:
Oupson 2021-06-25 16:34:25 +02:00
parent 31c7529d45
commit 21f9512688
19 changed files with 323 additions and 250 deletions

View File

@ -1,6 +1,5 @@
apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
android {
@ -29,8 +28,7 @@ android {
}
dependencies {
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.3'
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.0'
implementation 'androidx.appcompat:appcompat:1.3.0'

View File

@ -1,2 +1,3 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest package="oupson.apng">
</manifest>

View File

@ -71,6 +71,7 @@ class ApngDecoder(input: InputStream, val config: Config) {
val inputStream = BufferedInputStream(inputStream)
val bytes = ByteArray(8)
inputStream.mark(8)
withContext(Dispatchers.IO) {
inputStream.read(bytes)
}
@ -108,7 +109,6 @@ class ApngDecoder(input: InputStream, val config: Config) {
if (withContext(Dispatchers.IO) {
byteRead = inputStream.read(lengthChunk)
if (byteRead != -1) {
length = Utils.uIntFromBytesBigEndian(lengthChunk)
@ -149,6 +149,7 @@ class ApngDecoder(input: InputStream, val config: Config) {
)
}
}
cover?.close()
cover = null
} else {
// Add IEND body length : 0
@ -234,9 +235,9 @@ class ApngDecoder(input: InputStream, val config: Config) {
}
else -> buffer = btm
}
}
png?.close()
png = ByteArrayOutputStream(4096)
// Parse Frame ConTroL chunk
@ -318,6 +319,7 @@ class ApngDecoder(input: InputStream, val config: Config) {
)
val pngBytes = png.toByteArray()
png.close()
val decoded = BitmapFactory.decodeByteArray(
pngBytes,
0,
@ -393,11 +395,14 @@ class ApngDecoder(input: InputStream, val config: Config) {
crC32.update(Utils.IEND, 0, Utils.IEND.size)
it.write(Utils.IEND)
it.write(Utils.uIntToByteArray(crC32.value.toInt()))
withContext(Dispatchers.IO) {
inputStream.close()
}
val pngBytes = it.toByteArray()
it.close()
return@withContext BitmapDrawable(
context.resources,
BitmapFactory.decodeByteArray(
@ -530,16 +535,14 @@ class ApngDecoder(input: InputStream, val config: Config) {
suspend fun getDecoded(context: Context): Result<Drawable> {
if (result == null) {
result =
decodeApng(context)
result = decodeApng(context)
kotlin.runCatching {
withContext(Dispatchers.IO) {
inputStream?.close()
}
}.onFailure {
return Result.failure(it)
this.result = Result.failure(it)
}
inputStream = null

View File

@ -10,9 +10,9 @@ import android.widget.ImageView
import androidx.annotation.RawRes
import kotlinx.coroutines.*
import oupson.apng.drawable.ApngDrawable
import oupson.apng.utils.Utils.Companion.mapResult
import java.io.File
import java.io.FileInputStream
import java.io.FileNotFoundException
import java.net.URL
class ApngLoader(parent: Job? = null) {
@ -50,18 +50,18 @@ class ApngLoader(parent: Job? = null) {
file: File,
imageView: ImageView,
config: ApngDecoder.Config = ApngDecoder.Config()
): Result<Drawable> {
val result =
): Result<Drawable> =
kotlin.runCatching {
withContext(Dispatchers.IO) {
FileInputStream(file)
}
}.mapResult { input ->
ApngDecoder(
withContext(Dispatchers.IO) {
FileInputStream(file)
},
input,
config
).getDecoded(context)
if (result.isSuccess) {
}.onSuccess { drawable ->
withContext(Dispatchers.Main) {
val drawable = result.getOrNull()
imageView.setImageDrawable(drawable)
(drawable as? AnimationDrawable)?.start()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
@ -69,8 +69,6 @@ class ApngLoader(parent: Job? = null) {
}
}
}
return result
}
/**
* Load Apng into an imageView.
@ -84,19 +82,16 @@ class ApngLoader(parent: Job? = null) {
uri: Uri,
imageView: ImageView,
config: ApngDecoder.Config = ApngDecoder.Config()
): Result<Drawable> {
val inputStream =
withContext(Dispatchers.IO) { context.contentResolver.openInputStream(uri) }
?: throw FileNotFoundException("Failed to load $uri") // TODO Result
val result =
): Result<Drawable> =
kotlin.runCatching {
withContext(Dispatchers.IO) { context.contentResolver.openInputStream(uri) }!!
}.mapResult { inputStream ->
ApngDecoder(
inputStream,
config
).getDecoded(context)
if (result.isSuccess) {
}.onSuccess { drawable ->
withContext(Dispatchers.Main) {
val drawable = result.getOrNull()
imageView.setImageDrawable(drawable)
(drawable as? AnimationDrawable)?.start()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
@ -104,8 +99,7 @@ class ApngLoader(parent: Job? = null) {
}
}
}
return result
}
/**
* Load Apng into an imageView.
@ -118,27 +112,23 @@ class ApngLoader(parent: Job? = null) {
context: Context, @RawRes res: Int,
imageView: ImageView,
config: ApngDecoder.Config = ApngDecoder.Config()
): Result<Drawable> {
val result =
ApngDecoder(
withContext(Dispatchers.IO) {
context.resources.openRawResource(res)
},
config
).getDecoded(context)
if (result.isSuccess) {
withContext(Dispatchers.Main) {
val drawable = result.getOrNull()
imageView.setImageDrawable(drawable)
(drawable as? AnimationDrawable)?.start()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
(drawable as? AnimatedImageDrawable)?.start()
): Result<Drawable> =
ApngDecoder(
withContext(Dispatchers.IO) {
context.resources.openRawResource(res)
},
config
).getDecoded(context)
.onSuccess { drawable ->
withContext(Dispatchers.Main) {
imageView.setImageDrawable(drawable)
(drawable as? AnimationDrawable)?.start()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
(drawable as? AnimatedImageDrawable)?.start()
}
}
}
}
return result
}
/**
* Load Apng into an imageView, asynchronously.
@ -152,23 +142,18 @@ class ApngLoader(parent: Job? = null) {
url: URL,
imageView: ImageView,
config: ApngDecoder.Config = ApngDecoder.Config()
): Result<Drawable> {
val result =
ApngDecoder.constructFromUrl(url, config).getOrElse { return Result.failure(it) }
.getDecoded(context)
if (result.isSuccess) {
withContext(Dispatchers.Main) {
val drawable = result.getOrNull()
imageView.setImageDrawable(drawable)
(drawable as? AnimationDrawable)?.start()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
(drawable as? AnimatedImageDrawable)?.start()
): Result<Drawable> =
ApngDecoder.constructFromUrl(url, config).getOrElse { return Result.failure(it) }
.getDecoded(context)
.onSuccess { drawable ->
withContext(Dispatchers.Main) {
imageView.setImageDrawable(drawable)
(drawable as? AnimationDrawable)?.start()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
(drawable as? AnimatedImageDrawable)?.start()
}
}
}
}
return result
}
/**
* Load Apng into an imageView, asynchronously.
@ -185,12 +170,16 @@ class ApngLoader(parent: Job? = null) {
config: ApngDecoder.Config = ApngDecoder.Config()
): Result<Drawable> {
return if (string.startsWith("http://") || string.startsWith("https://")) {
decodeApngInto(
context,
URL(string),
imageView,
config
)
kotlin.runCatching { URL(string) }
.mapResult { url ->
decodeApngInto(
context,
url,
imageView,
config
)
}
} else if (File(string).exists()) {
var pathToLoad =
if (string.startsWith("content://")) string else "file://$string"
@ -202,25 +191,21 @@ class ApngLoader(parent: Job? = null) {
config
)
} else if (string.startsWith("file://android_asset/")) {
val inputStream = kotlin.runCatching {
kotlin.runCatching {
withContext(Dispatchers.IO) {
context.assets.open(string.replace("file:///android_asset/", ""))
}
}.getOrElse {
return Result.failure(it)
}
val result = ApngDecoder(inputStream, config).getDecoded(context)
if (result.isSuccess) {
withContext(Dispatchers.Main) {
val drawable = result.getOrNull()
imageView.setImageDrawable(drawable)
(drawable as? AnimationDrawable)?.start()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
(drawable as? AnimatedImageDrawable)?.start()
.mapResult { inputStream -> ApngDecoder(inputStream, config).getDecoded(context) }
.onSuccess { drawable ->
withContext(Dispatchers.Main) {
imageView.setImageDrawable(drawable)
(drawable as? AnimationDrawable)?.start()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
(drawable as? AnimatedImageDrawable)?.start()
}
}
}
}
result
} else {
throw Exception("Cannot open string")
}

View File

@ -291,7 +291,7 @@ class ApngEncoder(
) {
if (currentFrame == 0) {
if (btm.width != width || btm.height != height)
throw InvalidFrameSizeException(
throw InvalidFrameSizeException( // TODO
btm.width,
btm.height,
width,

View File

@ -24,8 +24,11 @@ class Loader {
val connection = url.openConnection() as HttpURLConnection
connection.useCaches = true
connection.connect()
val inputStream = connection.inputStream
if (connection.responseCode == 200) {
val input = BufferedInputStream(connection.inputStream)
val input = BufferedInputStream(inputStream)
val output = ByteArrayOutputStream()
var bytesRead: Int
val buffer = ByteArray(4096)
@ -36,9 +39,13 @@ class Loader {
} while (bytesRead != -1)
input.close()
output.close()
inputStream.close()
connection.disconnect()
output.toByteArray()
} else {
inputStream.close()
connection.disconnect()
throw Exception("Error when downloading file : ${connection.responseCode}")
}

View File

@ -208,7 +208,7 @@ class Utils {
(bytes[1] and 0xFF))
// TODO DOCUMENT AND TEST
fun uShortFromBytesBigEndian(bytes: ByteArray, offset : Int = 0): Int =
fun uShortFromBytesBigEndian(bytes: ByteArray, offset: Int = 0): Int =
(((bytes[offset].toInt() and 0xFF) shl 8) or
(bytes[offset + 1].toInt() and 0xFF))
@ -330,5 +330,14 @@ class Utils {
}
return result
}
suspend fun <T, U> Result<T>.mapResult(block: suspend (T) -> Result<U>): Result<U> {
return this.fold(
onSuccess = {
block.invoke(it)
},
onFailure = { Result.failure(it) }
)
}
}
}

View File

@ -1,6 +1,5 @@
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
android {
compileSdkVersion 30
@ -24,29 +23,35 @@ android {
buildTypes {
release {
minifyEnabled false
minifyEnabled true
shrinkResources true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
productFlavors {
}
buildFeatures {
viewBinding = true
}
}
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.3'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.0'
implementation 'androidx.appcompat:appcompat:1.3.0'
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
implementation 'com.google.android.material:material:1.3.0'
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.3.1'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test:runner:1.3.0'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
implementation 'com.squareup.picasso:picasso:2.71828'
implementation("io.coil-kt:coil:1.2.2")
implementation project(":apng_library")
// implementation fileTree(include: ['*.aar'], dir: 'libs')

View File

@ -14,18 +14,18 @@ import android.util.Log
import android.view.Menu
import android.view.MenuItem
import android.widget.Toast
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
import androidx.core.content.FileProvider
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.DefaultItemAnimator
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.snackbar.Snackbar
import kotlinx.android.synthetic.main.activity_creator.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import oupson.apng.encoder.ApngEncoder
@ -33,6 +33,7 @@ import oupson.apng.utils.Utils
import oupson.apngcreator.BuildConfig
import oupson.apngcreator.R
import oupson.apngcreator.adapter.ImageAdapter
import oupson.apngcreator.databinding.ActivityCreatorBinding
import oupson.apngcreator.dialogs.DelayInputDialog
import java.io.File
import java.io.FileOutputStream
@ -42,8 +43,6 @@ import kotlin.collections.ArrayList
class CreatorActivity : AppCompatActivity() {
companion object {
private const val PICK_IMAGE = 1
private const val WRITE_REQUEST_CODE = 2
private const val TAG = "CreatorActivity"
private const val CREATION_CHANNEL_ID =
@ -57,35 +56,97 @@ class CreatorActivity : AppCompatActivity() {
private var nextImageId: Long = 0
private var binding: ActivityCreatorBinding? = null
private val pickLauncher =
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
if (result.resultCode == Activity.RESULT_OK) {
val data: Intent? = result.data
if (data?.clipData != null) {
for (i in 0 until data.clipData!!.itemCount) {
items.add(Triple(data.clipData!!.getItemAt(i).uri, 1000, nextImageId++))
}
adapter?.notifyDataSetChanged()
} else if (data?.data != null) {
items.add(Triple(data.data!!, 1000, nextImageId++))
adapter?.notifyDataSetChanged()
}
}
}
private val writeLauncher =
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
if (result.resultCode == Activity.RESULT_OK) {
val data: Intent? = result.data
if (data?.data != null) {
if (BuildConfig.DEBUG)
Log.i(TAG, "Intent data : ${data.data}")
val builder = NotificationCompat.Builder(this, CREATION_CHANNEL_ID).apply {
setContentTitle(getString(R.string.create_notification_title))
setContentText(
this@CreatorActivity.resources.getQuantityString(
R.plurals.create_notification_description,
0,
0,
items.size
)
)
setSmallIcon(R.drawable.ic_create_white_24dp)
priority = NotificationCompat.PRIORITY_LOW
}
lifecycleScope.launch(Dispatchers.IO) {
val out = contentResolver.openOutputStream(data.data!!) ?: return@launch
saveToOutputStream(
items.map { Pair(it.first, it.second) },
out,
builder = builder
)
out.close()
if (binding != null) {
withContext(Dispatchers.Main) {
Snackbar.make(
binding!!.imageRecyclerView,
R.string.done,
Snackbar.LENGTH_SHORT
).show()
}
}
}
}
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityCreatorBinding.inflate(layoutInflater)
setContentView(R.layout.activity_creator)
setContentView(binding?.root)
fabAddImage.setOnClickListener {
binding?.fabAddImage?.setOnClickListener {
val getIntent = Intent(Intent.ACTION_GET_CONTENT)
getIntent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true)
getIntent.type = "image/*"
startActivityForResult(
getIntent,
PICK_IMAGE
)
pickLauncher.launch(getIntent)
}
adapter = ImageAdapter(this, items)
adapter = ImageAdapter(this, items, lifecycleScope)
adapter?.setHasStableIds(true)
imageRecyclerView.layoutManager = LinearLayoutManager(this)
imageRecyclerView.setHasFixedSize(true)
imageRecyclerView.itemAnimator = object : DefaultItemAnimator() {
binding?.imageRecyclerView?.layoutManager = LinearLayoutManager(this)
binding?.imageRecyclerView?.setHasFixedSize(true)
binding?.imageRecyclerView?.itemAnimator = object : DefaultItemAnimator() {
override fun canReuseUpdatedViewHolder(viewHolder: RecyclerView.ViewHolder): Boolean {
return true
}
}
imageRecyclerView.setItemViewCacheSize(20)
binding?.imageRecyclerView?.setItemViewCacheSize(20)
if (adapter != null)
ItemTouchHelper(SwipeToDeleteCallback(adapter!!)).attachToRecyclerView(imageRecyclerView)
ItemTouchHelper(SwipeToDeleteCallback(adapter!!)).attachToRecyclerView(binding?.imageRecyclerView)
adapter?.clickListener = { position ->
DelayInputDialog(object : DelayInputDialog.InputSenderDialogListener {
@ -104,8 +165,8 @@ class CreatorActivity : AppCompatActivity() {
}, items[position].second).show(supportFragmentManager, null)
}
setSupportActionBar(creatorBottomAppBar)
imageRecyclerView.adapter = adapter
setSupportActionBar(binding?.creatorBottomAppBar)
binding?.imageRecyclerView?.adapter = adapter
supportActionBar?.setDisplayHomeAsUpEnabled(true)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
@ -148,7 +209,7 @@ class CreatorActivity : AppCompatActivity() {
priority = NotificationCompat.PRIORITY_LOW
}
GlobalScope.launch(Dispatchers.IO) {
lifecycleScope.launch(Dispatchers.IO) {
val randomFileName = UUID.randomUUID().toString()
val f = File(filesDir, "images/$randomFileName.png").apply {
if (!exists()) {
@ -205,7 +266,7 @@ class CreatorActivity : AppCompatActivity() {
setSmallIcon(R.drawable.ic_create_white_24dp)
priority = NotificationCompat.PRIORITY_LOW
}
GlobalScope.launch(Dispatchers.IO) {
lifecycleScope.launch(Dispatchers.IO) {
val randomFileName = UUID.randomUUID().toString()
val f = File(filesDir, "images/$randomFileName.png").apply {
if (!exists()) {
@ -272,7 +333,10 @@ class CreatorActivity : AppCompatActivity() {
type = "image/png"
putExtra(Intent.EXTRA_TITLE, "${items[0].first.lastPathSegment}.png")
}
startActivityForResult(intent, WRITE_REQUEST_CODE)
writeLauncher.launch(
intent
)
}
true
}
@ -409,7 +473,9 @@ class CreatorActivity : AppCompatActivity() {
}
}
encoder.writeEnd()
withContext(Dispatchers.IO) {
encoder.writeEnd()
}
if (builder != null) {
@ -423,71 +489,14 @@ class CreatorActivity : AppCompatActivity() {
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
when (requestCode) {
PICK_IMAGE -> {
if (resultCode == Activity.RESULT_OK) {
if (data?.clipData != null) {
for (i in 0 until data.clipData!!.itemCount) {
items.add(Triple(data.clipData!!.getItemAt(i).uri, 1000, nextImageId++))
}
adapter?.notifyDataSetChanged()
} else if (data?.data != null) {
items.add(Triple(data.data!!, 1000, nextImageId++))
adapter?.notifyDataSetChanged()
}
}
}
WRITE_REQUEST_CODE -> {
if (resultCode == Activity.RESULT_OK) {
if (data?.data != null) {
if (BuildConfig.DEBUG)
Log.i(TAG, "Intent data : ${data.data}")
val builder = NotificationCompat.Builder(this, CREATION_CHANNEL_ID).apply {
setContentTitle(getString(R.string.create_notification_title))
setContentText(
this@CreatorActivity.resources.getQuantityString(
R.plurals.create_notification_description,
0,
0,
items.size
)
)
setSmallIcon(R.drawable.ic_create_white_24dp)
priority = NotificationCompat.PRIORITY_LOW
}
GlobalScope.launch(Dispatchers.IO) {
val out = contentResolver.openOutputStream(data.data!!) ?: return@launch
saveToOutputStream(
items.map { Pair(it.first, it.second) },
out,
builder = builder
)
out.close()
withContext(Dispatchers.Main) {
Snackbar.make(
imageRecyclerView,
R.string.done,
Snackbar.LENGTH_SHORT
).show()
}
}
}
}
}
}
}
override fun onDestroy() {
super.onDestroy()
val deleteResult = File(filesDir, "images").deleteRecursively()
if (BuildConfig.DEBUG)
Log.v(TAG, "Deleted images dir : $deleteResult")
lifecycleScope.launch(Dispatchers.IO) {
val deleteResult = File(filesDir, "images").deleteRecursively()
if (BuildConfig.DEBUG)
Log.v(TAG, "Deleted images dir : $deleteResult")
}
}
inner class SwipeToDeleteCallback(private val adapter: ImageAdapter) :

View File

@ -4,19 +4,25 @@ import android.annotation.SuppressLint
import android.content.Intent
import android.net.http.HttpResponseCache
import android.os.Bundle
import android.os.StrictMode
import android.os.StrictMode.ThreadPolicy
import android.os.StrictMode.VmPolicy
import android.util.Log
import android.view.MenuItem
import androidx.appcompat.app.ActionBarDrawerToggle
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.GravityCompat
import androidx.lifecycle.lifecycleScope
import com.google.android.material.bottomappbar.BottomAppBarTopEdgeTreatment
import com.google.android.material.shape.CutCornerTreatment
import com.google.android.material.shape.MaterialShapeDrawable
import com.google.android.material.shape.ShapeAppearanceModel
import com.google.android.material.shape.ShapePath
import kotlinx.android.synthetic.main.activity_main.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import oupson.apngcreator.BuildConfig
import oupson.apngcreator.R
import oupson.apngcreator.databinding.ActivityMainBinding
import oupson.apngcreator.fragments.ApngDecoderFragment
import oupson.apngcreator.fragments.JavaFragment
import oupson.apngcreator.fragments.KotlinFragment
@ -28,38 +34,67 @@ class MainActivity : AppCompatActivity() {
private const val TAG = "MainActivity"
}
private var binding: ActivityMainBinding? = null
override fun onCreate(savedInstanceState: Bundle?) {
if (BuildConfig.DEBUG) {
StrictMode.setThreadPolicy(
ThreadPolicy.Builder()
.detectDiskReads()
.detectDiskWrites()
.detectAll()
.penaltyLog()
.build()
)
StrictMode.setVmPolicy(
VmPolicy.Builder()
.detectLeakedSqlLiteObjects()
.detectLeakedClosableObjects()
.penaltyLog()
.build()
)
}
super.onCreate(savedInstanceState)
if (BuildConfig.DEBUG)
Log.v(TAG, "supportFragmentManager.fragments.size : ${supportFragmentManager.fragments.size}")
Log.v(
TAG,
"supportFragmentManager.fragments.size : ${supportFragmentManager.fragments.size}"
)
setContentView(R.layout.activity_main)
binding = ActivityMainBinding.inflate(layoutInflater)
setSupportActionBar(bottomAppBar)
setContentView(binding?.root)
setSupportActionBar(binding?.bottomAppBar)
setUpBottomAppBarShapeAppearance()
val httpCacheSize = 10 * 1024 * 1024.toLong() // 10 MiB
val httpCacheDir = File(cacheDir, "http")
HttpResponseCache.install(httpCacheDir, httpCacheSize)
lifecycleScope.launch(Dispatchers.IO) {
val httpCacheDir = File(cacheDir, "http")
HttpResponseCache.install(httpCacheDir, httpCacheSize)
}
fabCreate.setOnClickListener {
binding?.fabCreate?.setOnClickListener {
startActivity(Intent(this, CreatorActivity::class.java))
}
val drawerToggle = ActionBarDrawerToggle(this, drawer_layout, bottomAppBar,
val drawerToggle = ActionBarDrawerToggle(
this, binding?.drawerLayout, binding?.bottomAppBar,
R.string.open,
R.string.close
)
drawer_layout.addDrawerListener(drawerToggle)
binding?.drawerLayout?.addDrawerListener(drawerToggle)
drawerToggle.syncState()
var selected = 0
navigationView.setNavigationItemSelectedListener { menuItem : MenuItem ->
when(menuItem.itemId) {
binding?.navigationView?.setNavigationItemSelectedListener { menuItem: MenuItem ->
when (menuItem.itemId) {
R.id.menu_kotlin_fragment -> {
if (selected != 0) {
supportFragmentManager.beginTransaction().apply {
@ -98,20 +133,21 @@ class MainActivity : AppCompatActivity() {
}
}
drawer_layout.closeDrawer(GravityCompat.START)
binding?.drawerLayout?.closeDrawer(GravityCompat.START)
return@setNavigationItemSelectedListener true
}
if (intent.hasExtra("fragment") && supportFragmentManager.fragments.size == 0) {
when(intent.getStringExtra("fragment")) {
when (intent.getStringExtra("fragment")) {
"kotlin" -> {
supportFragmentManager.beginTransaction().apply {
add(
R.id.fragment_container,
KotlinFragment.newInstance(), "KotlinFragment")
KotlinFragment.newInstance(), "KotlinFragment"
)
}.commit()
navigationView.setCheckedItem(R.id.menu_kotlin_fragment)
binding?.navigationView?.setCheckedItem(R.id.menu_kotlin_fragment)
selected = 0
}
"java" -> {
@ -121,7 +157,7 @@ class MainActivity : AppCompatActivity() {
JavaFragment()
)
}.commit()
navigationView.setCheckedItem(R.id.menu_java_fragment)
binding?.navigationView?.setCheckedItem(R.id.menu_java_fragment)
selected = 1
}
"apng_decoder" -> {
@ -131,7 +167,7 @@ class MainActivity : AppCompatActivity() {
ApngDecoderFragment.newInstance()
)
}.commit()
navigationView.setCheckedItem(R.id.menu_apng_decoder_fragment)
binding?.navigationView?.setCheckedItem(R.id.menu_apng_decoder_fragment)
selected = 2
}
}
@ -139,35 +175,41 @@ class MainActivity : AppCompatActivity() {
supportFragmentManager.beginTransaction().apply {
add(
R.id.fragment_container,
KotlinFragment.newInstance(), "KotlinFragment")
KotlinFragment.newInstance(), "KotlinFragment"
)
}.commit()
navigationView.setCheckedItem(R.id.menu_kotlin_fragment)
binding?.navigationView?.setCheckedItem(R.id.menu_kotlin_fragment)
}
}
override fun onStop() {
super.onStop()
HttpResponseCache.getInstalled()?.flush()
lifecycleScope.launch(Dispatchers.IO) {
HttpResponseCache.getInstalled()?.flush()
}
}
private fun setUpBottomAppBarShapeAppearance() {
val fabShapeAppearanceModel: ShapeAppearanceModel = fabCreate.shapeAppearanceModel
val cutCornersFab =
(fabShapeAppearanceModel.bottomLeftCorner is CutCornerTreatment
&& fabShapeAppearanceModel.bottomRightCorner is CutCornerTreatment)
val topEdge =
if (cutCornersFab) BottomAppBarCutCornersTopEdge(
bottomAppBar.fabCradleMargin,
bottomAppBar.fabCradleRoundedCornerRadius,
bottomAppBar.cradleVerticalOffset
) else BottomAppBarTopEdgeTreatment(
bottomAppBar.fabCradleMargin,
bottomAppBar.fabCradleRoundedCornerRadius,
bottomAppBar.cradleVerticalOffset
)
val babBackground = bottomAppBar.background as MaterialShapeDrawable
babBackground.shapeAppearanceModel =
babBackground.shapeAppearanceModel.toBuilder().setTopEdge(topEdge).build()
if (binding != null) {
val fabShapeAppearanceModel: ShapeAppearanceModel =
binding!!.fabCreate.shapeAppearanceModel
val cutCornersFab =
(fabShapeAppearanceModel.bottomLeftCorner is CutCornerTreatment
&& fabShapeAppearanceModel.bottomRightCorner is CutCornerTreatment)
val topEdge =
if (cutCornersFab) BottomAppBarCutCornersTopEdge(
binding!!.bottomAppBar.fabCradleMargin,
binding!!.bottomAppBar.fabCradleRoundedCornerRadius,
binding!!.bottomAppBar.cradleVerticalOffset
) else BottomAppBarTopEdgeTreatment(
binding!!.bottomAppBar.fabCradleMargin,
binding!!.bottomAppBar.fabCradleRoundedCornerRadius,
binding!!.bottomAppBar.cradleVerticalOffset
)
val babBackground = binding!!.bottomAppBar.background as MaterialShapeDrawable
babBackground.shapeAppearanceModel =
babBackground.shapeAppearanceModel.toBuilder().setTopEdge(topEdge).build()
}
}

View File

@ -9,16 +9,19 @@ import android.view.View
import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import kotlinx.android.synthetic.main.activity_viewer.*
import oupson.apng.decoder.ApngDecoder
import oupson.apng.decoder.ApngLoader
import oupson.apngcreator.R
import oupson.apngcreator.databinding.ActivityViewerBinding
class ViewerActivity : AppCompatActivity() {
private var apngLoader: ApngLoader? = null
private var binding: ActivityViewerBinding? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_viewer)
binding = ActivityViewerBinding.inflate(layoutInflater)
setContentView(binding?.root)
this.apngLoader = ApngLoader()
@ -54,18 +57,20 @@ class ViewerActivity : AppCompatActivity() {
private fun load() {
val uri = intent.data ?: return
apngLoader?.decodeApngAsyncInto(
this,
uri,
viewerImageView,
callback = object : ApngLoader.Callback {
override fun onSuccess(drawable: Drawable) {}
override fun onError(error: Throwable) {
Log.e("ViewerActivity", "Error when loading file", error)
}
},
ApngDecoder.Config(decodeCoverFrame = false)
)
if (binding != null)
apngLoader?.decodeApngAsyncInto(
this,
uri,
binding!!.viewerImageView,
callback = object : ApngLoader.Callback {
override fun onSuccess(drawable: Drawable) {}
override fun onError(error: Throwable) {
Log.e("ViewerActivity", "Error when loading file", error)
}
},
ApngDecoder.Config(decodeCoverFrame = false)
)
}
override fun onRequestPermissionsResult(

View File

@ -10,21 +10,25 @@ import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import oupson.apngcreator.R
class ImageAdapter(private val context : Context, private val list : List<Triple<Uri, Int, Long>>) : RecyclerView.Adapter<ImageAdapter.ImageHolder>() {
inner class ImageHolder(view : View) : RecyclerView.ViewHolder(view) {
val imageView : ImageView? = view.findViewById(R.id.listImageView)
val textDelay : TextView? = view.findViewById(R.id.textDelay)
val positionTextView : TextView? = view.findViewById(R.id.position_textView)
val nameTextView : TextView? = view.findViewById(R.id.name_textView)
class ImageAdapter(
private val context: Context,
private val list: List<Triple<Uri, Int, Long>>,
private val scope: CoroutineScope
) : RecyclerView.Adapter<ImageAdapter.ImageHolder>() {
inner class ImageHolder(view: View) : RecyclerView.ViewHolder(view) {
val imageView: ImageView? = view.findViewById(R.id.listImageView)
val textDelay: TextView? = view.findViewById(R.id.textDelay)
val positionTextView: TextView? = view.findViewById(R.id.position_textView)
val nameTextView: TextView? = view.findViewById(R.id.name_textView)
}
var clickListener : ((position : Int) -> Unit)? = null
var clickListener: ((position: Int) -> Unit)? = null
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ImageHolder {
val inflater = LayoutInflater.from(parent.context)
@ -36,7 +40,7 @@ class ImageAdapter(private val context : Context, private val list : List<Triple
holder.textDelay?.text = String.format("%dms", list[position].second)
holder.positionTextView?.text = String.format("# %03d", holder.adapterPosition + 1)
holder.nameTextView?.text = list[position].first.path?.substringAfterLast("/")
GlobalScope.launch(Dispatchers.IO) {
scope.launch(Dispatchers.IO) {
val inputStream = context.contentResolver.openInputStream(list[position].first)
val btm =
BitmapFactory.decodeStream(inputStream, null, BitmapFactory.Options().apply {

View File

@ -11,8 +11,7 @@ import android.widget.Button
import android.widget.ImageView
import android.widget.SeekBar
import androidx.fragment.app.Fragment
import com.squareup.picasso.Picasso
import kotlinx.android.synthetic.main.activity_creator.*
import coil.load
import oupson.apng.decoder.ApngLoader
import oupson.apng.drawable.ApngDrawable
import oupson.apngcreator.BuildConfig
@ -145,7 +144,7 @@ class KotlinFragment : Fragment() {
}
})
if (animation == null) {
if ((animation == null)) {
apngLoader?.decodeApngAsyncInto(
requireContext(),
imageUrls[selected],
@ -158,13 +157,13 @@ class KotlinFragment : Fragment() {
}
}
override fun onError(error: Exception) {
override fun onError(error: Throwable) {
Log.e(TAG, "Error when decoding apng", error)
}
})
}
Picasso.get().load(imageUrls[selected]).into(normalImageView)
normalImageView?.load(imageUrls[selected])
}
override fun onPause() {

View File

@ -1,8 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
<androidx.constraintlayout.widget.ConstraintLayout 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">
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:viewBindingIgnore="true">
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/delay_textInputLayout"

View File

@ -3,7 +3,8 @@
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".fragments.ApngDecoderFragment">
tools:context=".fragments.ApngDecoderFragment"
tools:viewBindingIgnore="true">
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/apngDecoderImageView"

View File

@ -4,7 +4,8 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
tools:context=".fragments.JavaFragment">
tools:context=".fragments.JavaFragment"
tools:viewBindingIgnore="true">
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/javaImageView"

View File

@ -1,9 +1,11 @@
<?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"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
android:layout_height="match_parent"
tools:viewBindingIgnore="true">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"

View File

@ -4,7 +4,8 @@
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
android:orientation="horizontal"
tools:viewBindingIgnore="true">
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/position_textView"

View File

@ -1,8 +1,7 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
ext.kotlin_version = '1.5.10'
ext.dokka_version = '1.4.3'
ext.kotlin_version = '1.5.20'
repositories {
google()
mavenCentral()