Fixing bug in CreatorActivity.

This commit is contained in:
oupson 2020-04-20 13:56:17 +02:00
parent 9f5f3e1a97
commit 37f715fffa
8 changed files with 167 additions and 89 deletions

View File

@ -9,6 +9,7 @@ import java.util.zip.DeflaterOutputStream
import kotlin.math.max
import kotlin.math.min
// TODO FIND A BETTER SOLUTION
/**
* Taken from http://catcode.com/pngencoder/com/keypoint/PngEncoder.java
*/
@ -395,7 +396,6 @@ class PngEncoder {
System.err.println(e.toString())
return false
}
}
/**

View File

@ -3,12 +3,12 @@ apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
android {
compileSdkVersion 28
compileSdkVersion 29
defaultConfig {
applicationId "oupson.apngcreator"
minSdkVersion 21
targetSdkVersion 28
targetSdkVersion 29
versionCode 1
versionName "1.0.10"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
@ -35,12 +35,12 @@ android {
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.4'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.5'
implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
implementation 'com.google.android.material:material:1.2.0-alpha05'
implementation 'com.google.android.material:material:1.2.0-alpha06'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test:runner:1.2.0'

View File

@ -23,17 +23,20 @@ import oupson.apng.encoder.ApngEncoder
import oupson.apngcreator.BuildConfig
import oupson.apngcreator.R
import oupson.apngcreator.adapter.ImageAdapter
import oupson.apngcreator.dialogs.DelayInputDialog
import java.io.File
import java.io.FileOutputStream
class CreatorActivity : AppCompatActivity() {
companion object {
private const val PICK_IMAGE = 1
private const val WRITE_REQUEST_CODE = 2
private const val TAG = "CreatorActivity"
}
private var items : ArrayList<Uri> = ArrayList()
private var adapter : ImageAdapter? = null
private var items: ArrayList<Pair<Uri, Int>> = ArrayList()
private var adapter: ImageAdapter? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@ -44,7 +47,8 @@ class CreatorActivity : AppCompatActivity() {
val getIntent = Intent(Intent.ACTION_GET_CONTENT)
getIntent.type = "image/*"
startActivityForResult(getIntent,
startActivityForResult(
getIntent,
PICK_IMAGE
)
}
@ -57,6 +61,19 @@ class CreatorActivity : AppCompatActivity() {
if (adapter != null)
ItemTouchHelper(SwipeToDeleteCallback(adapter!!)).attachToRecyclerView(imageRecyclerView)
adapter?.clickListener = { position ->
DelayInputDialog(object : DelayInputDialog.InputSenderDialogListener {
override fun onOK(number: Int?) {
if (number != null) {
items[position] = Pair(items[position].first, number)
adapter?.notifyDataSetChanged()
}
}
override fun onCancel(number: Int?) {}
}, items[position].second).show(supportFragmentManager, null)
}
setSupportActionBar(creatorBottomAppBar)
imageRecyclerView.adapter = adapter
supportActionBar?.setDisplayHomeAsUpEnabled(true)
@ -74,7 +91,7 @@ class CreatorActivity : AppCompatActivity() {
GlobalScope.launch(Dispatchers.IO) {
val f = File(filesDir, "images/apng.png").apply {
if (!exists()) {
parentFile.mkdirs()
parentFile?.mkdirs()
println(createNewFile())
}
}
@ -82,7 +99,7 @@ class CreatorActivity : AppCompatActivity() {
var maxWidth = 0
var maxHeight = 0
items.forEach {
val str = contentResolver.openInputStream(it)
val str = contentResolver.openInputStream(it.first)
val btm = BitmapFactory.decodeStream(str)
if (btm.width > maxWidth)
maxWidth = btm.width
@ -95,12 +112,12 @@ class CreatorActivity : AppCompatActivity() {
Log.i(TAG, "MaxWidth : $maxWidth; MaxHeight : $maxHeight")
val encoder = ApngEncoder(out, maxWidth, maxHeight, items.size)
items.forEachIndexed { i, uri ->
items.forEach { uri ->
// println("delay : ${adapter?.delay?.get(i)?.toFloat() ?: 1000f}ms")
val str = contentResolver.openInputStream(uri) ?: return@forEachIndexed
val str = contentResolver.openInputStream(uri.first) ?: return@forEach
encoder.writeFrame(
str,
delay = adapter?.delay?.get(i)?.toFloat() ?: 1000f
delay = uri.second.toFloat()
)
}
@ -126,7 +143,7 @@ class CreatorActivity : AppCompatActivity() {
GlobalScope.launch(Dispatchers.IO) {
val f = File(filesDir, "images/apng.png").apply {
if (!exists()) {
parentFile.mkdirs()
parentFile?.mkdirs()
println(createNewFile())
}
}
@ -134,7 +151,7 @@ class CreatorActivity : AppCompatActivity() {
var maxWidth = 0
var maxHeight = 0
items.forEach {
val str = contentResolver.openInputStream(it)
val str = contentResolver.openInputStream(it.first)
val btm = BitmapFactory.decodeStream(str)
if (btm.width > maxWidth)
maxWidth = btm.width
@ -144,12 +161,12 @@ class CreatorActivity : AppCompatActivity() {
}
val encoder = ApngEncoder(out, maxWidth, maxHeight, items.size)
items.forEachIndexed { i, uri ->
println("delay : ${adapter?.delay?.get(i)?.toFloat() ?: 1000f}ms")
val str = contentResolver.openInputStream(uri) ?: return@forEachIndexed
items.forEach { uri ->
println("delay : ${uri.second.toFloat()}ms")
val str = contentResolver.openInputStream(uri.first) ?: return@forEach
encoder.writeFrame(
str,
delay = adapter?.delay?.get(i)?.toFloat() ?: 1000f
delay = uri.second.toFloat()
)
}
@ -188,23 +205,23 @@ class CreatorActivity : AppCompatActivity() {
// Create a file with the requested MIME type.
type = "image/png"
putExtra(Intent.EXTRA_TITLE, "${items[0].lastPathSegment}.png")
putExtra(Intent.EXTRA_TITLE, "${items[0].first.lastPathSegment}.png")
}
startActivityForResult(intent, WRITE_REQUEST_CODE)
}
true
}
else -> super.onOptionsItemSelected(item)
else -> if (item != null) super.onOptionsItemSelected(item) else true
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
when(requestCode) {
when (requestCode) {
PICK_IMAGE -> {
if (resultCode == Activity.RESULT_OK) {
if (data?.data != null) {
items.add(data.data!!)
items.add(Pair(data.data!!, 1000))
adapter?.notifyDataSetChanged()
}
}
@ -219,7 +236,7 @@ class CreatorActivity : AppCompatActivity() {
var maxWidth = 0
var maxHeight = 0
items.forEach {
val str = contentResolver.openInputStream(it)
val str = contentResolver.openInputStream(it.first)
val btm = BitmapFactory.decodeStream(str)
if (btm.width > maxWidth)
maxWidth = btm.width
@ -232,12 +249,13 @@ class CreatorActivity : AppCompatActivity() {
Log.i(TAG, "MaxWidth : $maxWidth; MaxHeight : $maxHeight")
val encoder = ApngEncoder(out, maxWidth, maxHeight, items.size)
items.forEachIndexed { i, uri ->
items.forEach { uri ->
// println("delay : ${adapter?.delay?.get(i)?.toFloat() ?: 1000f}ms")
val str = contentResolver.openInputStream(uri) ?: return@forEachIndexed
val str =
contentResolver.openInputStream(uri.first) ?: return@forEach
encoder.writeFrame(
str,
delay = adapter?.delay?.get(i)?.toFloat() ?: 1000f
delay = uri.second.toFloat()
)
}
@ -245,7 +263,11 @@ class CreatorActivity : AppCompatActivity() {
out.close()
withContext(Dispatchers.Main) {
Snackbar.make(imageRecyclerView, R.string.done, Snackbar.LENGTH_SHORT).show()
Snackbar.make(
imageRecyclerView,
R.string.done,
Snackbar.LENGTH_SHORT
).show()
}
}
}
@ -254,27 +276,23 @@ class CreatorActivity : AppCompatActivity() {
}
}
inner class SwipeToDeleteCallback(private val adapter: ImageAdapter) : ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT) {
// TODO MOVE TOP AND BOTTOM
inner class SwipeToDeleteCallback(private val adapter: ImageAdapter) :
ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT) {
override fun onMove(
recyclerView: RecyclerView,
viewHolder: RecyclerView.ViewHolder,
target: RecyclerView.ViewHolder
) : Boolean {
): Boolean {
return false
}
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
val position = viewHolder.adapterPosition
adapter.delay.removeAt(position)
items.removeAt(position)
adapter.notifyDataSetChanged()
adapter.listeners.forEachIndexed { index, listener ->
if (index >= position)
listener.position = index
}
}
override fun isItemViewSwipeEnabled() = true
}
}
}

View File

@ -4,38 +4,26 @@ import android.content.Context
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.net.Uri
import android.text.Editable
import android.text.TextWatcher
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.textfield.TextInputEditText
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<Uri>) : RecyclerView.Adapter<ImageAdapter.ImageHolder>() {
val delay : ArrayList<String> = arrayListOf()
val listeners : ArrayList<Listener> = arrayListOf()
inner class Listener : TextWatcher {
var position : Int = -1
override fun afterTextChanged(s: Editable?) {}
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
if (position > -1)
delay[position] = s?.toString() ?: ""
}
class ImageAdapter(private val context : Context, private val list : List<Pair<Uri, Int>>) : 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)
}
inner class ImageHolder(view : View) : RecyclerView.ViewHolder(view) {
val imageView = view.findViewById<ImageView?>(R.id.listImageView)
val textDelay = view.findViewById<TextInputEditText?>(R.id.textDelay)
val listener = Listener()
}
var clickListener : ((position : Int) -> Unit)? = null
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ImageHolder {
val inflater = LayoutInflater.from(parent.context)
@ -43,14 +31,11 @@ class ImageAdapter(private val context : Context, private val list : List<Uri>)
}
override fun onBindViewHolder(holder: ImageHolder, position: Int) {
if (delay.size <= position)
delay.add("1000")
holder.textDelay?.addTextChangedListener(holder.listener.also{
it.position = position
listeners.add(position, it)
})
holder.itemView.setOnClickListener { clickListener?.invoke(position) }
holder.textDelay?.text = String.format("%dms", list[position].second)
holder.positionTextView?.text = String.format("# %d", position + 1)
GlobalScope.launch(Dispatchers.IO) {
val inputStream = context.contentResolver.openInputStream(list[position])
val inputStream = context.contentResolver.openInputStream(list[position].first)
val btm = BitmapFactory.decodeStream(inputStream, null, BitmapFactory.Options().apply {
inPreferredConfig = Bitmap.Config.RGB_565
})
@ -61,12 +46,6 @@ class ImageAdapter(private val context : Context, private val list : List<Uri>)
}
}
override fun onViewDetachedFromWindow(holder: ImageHolder) {
super.onViewDetachedFromWindow(holder)
holder.textDelay?.removeTextChangedListener(holder.listener)
listeners.remove(holder.listener)
}
override fun getItemCount(): Int = list.count()
}

View File

@ -0,0 +1,46 @@
package oupson.apngcreator.dialogs
import android.app.Dialog
import android.content.DialogInterface
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.widget.EditText
import androidx.fragment.app.DialogFragment
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.textfield.TextInputLayout
import oupson.apngcreator.R
class DelayInputDialog(
private val listener: InputSenderDialogListener?,
private val value : Int? = null
) : DialogFragment() {
interface InputSenderDialogListener {
fun onOK(number: Int?)
fun onCancel(number: Int?)
}
private var mNumberEdit: EditText? = null
override fun getTheme() = R.style.RoundedCornersDialog
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val dialogLayout: View = LayoutInflater.from(activity).inflate(R.layout.dialog_delay, null)
mNumberEdit =
dialogLayout.findViewById<TextInputLayout>(R.id.delay_textInputLayout).editText
if (value != null)
mNumberEdit?.setText(value.toString())
return MaterialAlertDialogBuilder(
context!!,
R.style.RoundedCornersDialog
)
.setTitle(R.string.delay)
.setView(dialogLayout)
.setPositiveButton(android.R.string.ok) { _: DialogInterface?, _: Int ->
listener?.onOK(java.lang.String.valueOf(mNumberEdit?.text).toIntOrNull())
}
.setNegativeButton(android.R.string.cancel) { _: DialogInterface?, _: Int ->
listener?.onCancel(java.lang.String.valueOf(mNumberEdit?.text).toIntOrNull())
}.create()
}
}

View File

@ -0,0 +1,28 @@
<?xml version="1.0" encoding="utf-8"?>
<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">
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/delay_textInputLayout"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="16dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<com.google.android.material.textfield.TextInputEditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/delay"
android:inputType="numberSigned" />
</com.google.android.material.textfield.TextInputLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -6,19 +6,29 @@
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/position_textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
tools:text="#1"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<oupson.apngcreator.views.SquareImageView
android:id="@+id/listImageView"
android:layout_width="wrap_content"
android:layout_height="0dp"
android:layout_width="80dp"
android:layout_height="80dp"
android:layout_marginStart="8dp"
android:scaleType="fitCenter"
app:layout_constraintBottom_toBottomOf="@id/textInputLayout"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@+id/textInputLayout"
app:layout_constraintBottom_toBottomOf="@id/textDelay"
app:layout_constraintStart_toEndOf="@+id/position_textView"
app:layout_constraintTop_toTopOf="@+id/textDelay"
tools:srcCompat="@tools:sample/avatars" />
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/textInputLayout"
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/textDelay"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
android:layout_width="0dp"
android:layout_height="wrap_content"
@ -26,20 +36,8 @@
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
android:layout_marginBottom="8dp"
android:hint="@string/delay"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/listImageView"
app:layout_constraintTop_toTopOf="parent">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/textDelay"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="8dp"
android:inputType="number"
android:text="1000"
android:textColorHint="@color/colorPrimary"
tools:ignore="HardcodedText" />
</com.google.android.material.textfield.TextInputLayout>
app:layout_constraintTop_toTopOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -21,4 +21,13 @@
<item name="cornerFamily">cut</item>
<item name="cornerSize">8dp</item>
</style>
<style name="RoundedCornersDialog" parent="@style/ThemeOverlay.MaterialComponents.MaterialAlertDialog">
<item name="shapeAppearanceOverlay">@style/RoundedCornersDialog.ShapeOverlay</item>
</style>
<style name="RoundedCornersDialog.ShapeOverlay">
<item name="cornerFamily">rounded</item>
<item name="cornerSize">8dp</item>
</style>
</resources>