20210810
2
.idea/deploymentTargetDropDown.xml
generated
@ -12,6 +12,6 @@
|
||||
</deviceKey>
|
||||
</Target>
|
||||
</targetSelectedWithDropDown>
|
||||
<timeTargetWasSelectedWithDropDown value="2021-08-09T19:37:01.560785100Z" />
|
||||
<timeTargetWasSelectedWithDropDown value="2021-08-10T19:32:45.536947900Z" />
|
||||
</component>
|
||||
</project>
|
3
.idea/misc.xml
generated
@ -5,6 +5,9 @@
|
||||
<map>
|
||||
<entry key="..\:/Users/Dell/Documents/GitHub/Android.EweSticker/app/src/main/res/drawable/ic_chevron_left.xml" value="0.14635416666666667" />
|
||||
<entry key="..\:/Users/Dell/Documents/GitHub/Android.EweSticker/app/src/main/res/drawable/ic_clock.xml" value="0.14635416666666667" />
|
||||
<entry key="..\:/Users/Dell/Documents/GitHub/Android.EweSticker/app/src/main/res/layout/image_container.xml" value="0.13020833333333334" />
|
||||
<entry key="..\:/Users/Dell/Documents/GitHub/Android.EweSticker/app/src/main/res/layout/image_container_column.xml" value="0.13020833333333334" />
|
||||
<entry key="..\:/Users/Dell/Documents/GitHub/Android.EweSticker/app/src/main/res/layout/keyboard_layout.xml" value="0.13020833333333334" />
|
||||
</map>
|
||||
</option>
|
||||
</component>
|
||||
|
10
CHANGELOG.md
@ -3,15 +3,17 @@ All major and minor version changes will be documented in this file. Details of
|
||||
patch-level version changes can be found in [commit messages](../../commits/master).
|
||||
|
||||
|
||||
|
||||
## (no version) - 2021/08/09
|
||||
## 20210810 - 2021/08/10
|
||||
- Code optimisations
|
||||
- Code clean-up
|
||||
- Removed APNG animation due to memory leak
|
||||
- Linting fixes
|
||||
- Added caching functionality
|
||||
- to improve performance of fallback stickers
|
||||
- to enable addition of recent list
|
||||
- Updated gradle
|
||||
- to improve switching packs performance
|
||||
- Updated gradle and deps
|
||||
- Add recent icon
|
||||
- (In progress ...) testing, cleanup
|
||||
|
||||
|
||||
## 20210723 - 2021/07/23
|
||||
|
@ -11,8 +11,8 @@ android {
|
||||
applicationId "com.fredhappyface.ewesticker"
|
||||
minSdkVersion 28
|
||||
targetSdkVersion 30
|
||||
versionCode 20210723
|
||||
versionName "2021.07.23"
|
||||
versionCode 20210810
|
||||
versionName "2021.08.10"
|
||||
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
@ -39,14 +39,15 @@ android {
|
||||
|
||||
dependencies {
|
||||
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
|
||||
implementation 'androidx.core:core-ktx:1.5.0'
|
||||
implementation 'androidx.appcompat:appcompat:1.3.0'
|
||||
implementation 'com.google.android.material:material:1.3.0'
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib:1.5.21"
|
||||
implementation 'androidx.core:core-ktx:1.6.0'
|
||||
implementation "androidx.fragment:fragment-ktx"
|
||||
implementation 'androidx.appcompat:appcompat:1.3.1'
|
||||
implementation 'com.google.android.material:material:1.4.0'
|
||||
implementation 'androidx.preference:preference-ktx:1.1.1'
|
||||
testImplementation 'junit:junit:4.13.2'
|
||||
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
|
||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
|
||||
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
|
||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
|
||||
|
||||
implementation "com.github.penfeizhou.android.animation:apng:2.10.0"
|
||||
//implementation "com.github.penfeizhou.android.animation:apng:2.11.0"
|
||||
}
|
||||
|
@ -8,10 +8,9 @@ import java.util.*
|
||||
* an element is removed from the start
|
||||
*
|
||||
*/
|
||||
class Cache(size: Int = 30) {
|
||||
class Cache(private val size: Int = 30) {
|
||||
|
||||
private var data: LinkedList<String> = LinkedList()
|
||||
private val size = size
|
||||
|
||||
/**
|
||||
* Logic to add an element
|
||||
@ -31,14 +30,6 @@ class Cache(size: Int = 30) {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove an element
|
||||
*
|
||||
* @param elem
|
||||
*/
|
||||
fun remove(elem: String) {
|
||||
data.remove(elem)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an element
|
||||
|
@ -7,13 +7,11 @@ import android.graphics.ImageDecoder
|
||||
import android.graphics.drawable.AnimatedImageDrawable
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.inputmethodservice.InputMethodService
|
||||
import android.util.Log
|
||||
import android.view.View
|
||||
import android.view.inputmethod.EditorInfo
|
||||
import android.view.inputmethod.InputMethodManager
|
||||
import android.widget.ImageButton
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.RelativeLayout
|
||||
import android.widget.Toast
|
||||
import androidx.cardview.widget.CardView
|
||||
import androidx.core.content.FileProvider
|
||||
@ -22,23 +20,24 @@ import androidx.core.view.inputmethod.EditorInfoCompat
|
||||
import androidx.core.view.inputmethod.InputConnectionCompat
|
||||
import androidx.core.view.inputmethod.InputContentInfoCompat
|
||||
import androidx.preference.PreferenceManager
|
||||
import com.github.penfeizhou.animation.apng.APNGDrawable
|
||||
//import com.github.penfeizhou.animation.apng.APNGDrawable
|
||||
import java.io.File
|
||||
import java.io.FileOutputStream
|
||||
import java.io.IOException
|
||||
import java.util.*
|
||||
import kotlin.collections.HashMap
|
||||
|
||||
class ImageKeyboard : InputMethodService() {
|
||||
// Attributes
|
||||
private lateinit var supportedMimes: MutableMap<String, String>
|
||||
private var loadedPacks = HashMap<String, StickerPack>()
|
||||
private var imageContainer: LinearLayout? = null
|
||||
private var packContainer: LinearLayout? = null
|
||||
private lateinit var imageContainer: LinearLayout
|
||||
private lateinit var packContainer: LinearLayout
|
||||
private lateinit var internalDir: File
|
||||
|
||||
// SharedPref
|
||||
private lateinit var sharedPreferences: SharedPreferences
|
||||
private var iconsPerRow = 0
|
||||
private var iconsPerColumn = 0
|
||||
private var iconSize = 0
|
||||
private var disableAnimations = false
|
||||
|
||||
@ -46,6 +45,9 @@ class ImageKeyboard : InputMethodService() {
|
||||
private var compatCache = Cache()
|
||||
private var recentCache = Cache()
|
||||
|
||||
// Cache for image container
|
||||
private var imageContainerCache = HashMap<Int, LinearLayout>()
|
||||
|
||||
/**
|
||||
* Adds a back button as a PackCard to keyboard that shows the InputMethodPicker
|
||||
*/
|
||||
@ -59,7 +61,7 @@ class ImageKeyboard : InputMethodService() {
|
||||
.getSystemService(INPUT_METHOD_SERVICE) as InputMethodManager
|
||||
inputMethodManager.showInputMethodPicker()
|
||||
}
|
||||
packContainer!!.addView(packCard)
|
||||
packContainer.addView(packCard)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -70,11 +72,11 @@ class ImageKeyboard : InputMethodService() {
|
||||
val recentButton = packCard.findViewById<ImageButton>(R.id.ib3)
|
||||
val icon = ResourcesCompat.getDrawable(resources, R.drawable.ic_clock, null)
|
||||
recentButton.setImageDrawable(icon)
|
||||
recentButton.setOnClickListener { view: View ->
|
||||
imageContainer!!.removeAllViewsInLayout()
|
||||
recreateImageContainer(recentCache.toFiles())
|
||||
recentButton.setOnClickListener {
|
||||
imageContainer.removeAllViewsInLayout()
|
||||
imageContainer.addView(createImageContainer(recentCache.toFiles()))
|
||||
}
|
||||
packContainer!!.addView(packCard)
|
||||
packContainer.addView(packCard)
|
||||
|
||||
}
|
||||
|
||||
@ -89,10 +91,9 @@ class ImageKeyboard : InputMethodService() {
|
||||
setStickerButtonImage(pack.thumbSticker, packButton)
|
||||
packButton.tag = pack
|
||||
packButton.setOnClickListener { view: View ->
|
||||
imageContainer!!.removeAllViewsInLayout()
|
||||
recreateImageContainer((view.tag as StickerPack).stickerList)
|
||||
switchImageContainer((view.tag as StickerPack).stickerList)
|
||||
}
|
||||
packContainer!!.addView(packCard)
|
||||
packContainer.addView(packCard)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -170,19 +171,19 @@ class ImageKeyboard : InputMethodService() {
|
||||
drawable = ImageDecoder.decodeDrawable(ImageDecoder.createSource(sticker))
|
||||
} catch (ignore: IOException) {
|
||||
}
|
||||
if (sName.contains(".png") || sName.contains(".apng")) {
|
||||
drawable = APNGDrawable.fromFile(sticker.absolutePath)
|
||||
drawable!!.setAutoPlay(false)
|
||||
drawable.start()
|
||||
}
|
||||
//if (sName.contains(".png") || sName.contains(".apng")) {
|
||||
// drawable = APNGDrawable.fromFile(sticker.absolutePath)
|
||||
// drawable!!.setAutoPlay(false)
|
||||
// drawable.start()
|
||||
//}
|
||||
// Disable animations?
|
||||
if (drawable is AnimatedImageDrawable && !disableAnimations
|
||||
) {
|
||||
drawable.start()
|
||||
}
|
||||
if (drawable is APNGDrawable && disableAnimations) {
|
||||
drawable.stop()
|
||||
}
|
||||
//if (drawable is APNGDrawable && disableAnimations) {
|
||||
// drawable.stop()
|
||||
//}
|
||||
// Apply
|
||||
btn.setImageDrawable(drawable)
|
||||
}
|
||||
@ -215,14 +216,14 @@ class ImageKeyboard : InputMethodService() {
|
||||
}
|
||||
|
||||
/**
|
||||
* When the activity is crated, grab the number of icons per row and the configured icon size
|
||||
* When the activity is crated, grab the number of icons per column and the configured icon size
|
||||
* before reloading the packs
|
||||
*
|
||||
*/
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(baseContext)
|
||||
iconsPerRow = sharedPreferences.getInt("iconsPerRow", 3)
|
||||
iconsPerColumn = sharedPreferences.getInt("iconsPerColumn", 3)
|
||||
iconSize = sharedPreferences.getInt("iconSize", 160)
|
||||
disableAnimations = sharedPreferences.getBoolean(
|
||||
"disableAnimations",
|
||||
@ -234,19 +235,28 @@ class ImageKeyboard : InputMethodService() {
|
||||
reloadPacks()
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the keyboard is first drawn
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
override fun onCreateInputView(): View {
|
||||
val keyboardLayout =
|
||||
layoutInflater.inflate(R.layout.keyboard_layout, null) as RelativeLayout
|
||||
View.inflate(applicationContext, R.layout.keyboard_layout, null)
|
||||
packContainer = keyboardLayout.findViewById(R.id.packContainer)
|
||||
imageContainer = keyboardLayout.findViewById(R.id.imageContainer)
|
||||
imageContainer?.layoutParams?.height = (iconSize * iconsPerRow * 1.4).toInt()
|
||||
imageContainer.layoutParams?.height = (iconSize * iconsPerColumn * 1.4).toInt()
|
||||
recreatePackContainer()
|
||||
return keyboardLayout
|
||||
}
|
||||
|
||||
/**
|
||||
* In full-screen mode the inserted content is likely to be hidden by the IME. Hence in this
|
||||
* sample we simply disable full-screen mode.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
override fun onEvaluateFullscreenMode(): Boolean {
|
||||
// In full-screen mode the inserted content is likely to be hidden by the IME. Hence in this
|
||||
// sample we simply disable full-screen mode.
|
||||
return false
|
||||
}
|
||||
|
||||
@ -267,27 +277,45 @@ class ImageKeyboard : InputMethodService() {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Swap the image container every time a new pack is selected. If already cached use that otherwise create
|
||||
*
|
||||
* @param stickers
|
||||
*/
|
||||
private fun switchImageContainer(stickers: Array<File>) {
|
||||
// Check the cache
|
||||
val imageContainerHash = stickers.hashCode()
|
||||
lateinit var imageContainerLayout: LinearLayout
|
||||
if (imageContainerHash !in imageContainerCache.keys) {
|
||||
imageContainerLayout = createImageContainer(stickers)
|
||||
imageContainerCache[imageContainerHash] = createImageContainer(stickers)
|
||||
} else {
|
||||
imageContainerLayout = imageContainerCache[imageContainerHash]!!
|
||||
}
|
||||
// Swap the image container
|
||||
imageContainer.removeAllViews()
|
||||
imageContainer.addView(imageContainerLayout)
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Recreate the image container every time a new pack is selected
|
||||
*
|
||||
* @param pack
|
||||
* @param stickers
|
||||
*/
|
||||
private fun recreateImageContainer(stickers: Array<File>) {
|
||||
// Clear the image view
|
||||
imageContainer!!.removeAllViewsInLayout()
|
||||
// And rebuild...
|
||||
var imageContainerColumn = layoutInflater.inflate(
|
||||
R.layout.image_container_column,
|
||||
imageContainer,
|
||||
false
|
||||
) as LinearLayout
|
||||
private fun createImageContainer(stickers: Array<File>): LinearLayout {
|
||||
val tempImageContainer =
|
||||
View.inflate(applicationContext, R.layout.image_container, null) as LinearLayout
|
||||
lateinit var imageContainerColumn: LinearLayout
|
||||
for (i in stickers.indices) {
|
||||
if (i % iconsPerRow == 0) {
|
||||
// Add a new column
|
||||
if (i % iconsPerColumn == 0) {
|
||||
imageContainerColumn = layoutInflater.inflate(
|
||||
R.layout.image_container_column,
|
||||
imageContainer,
|
||||
tempImageContainer,
|
||||
false
|
||||
) as LinearLayout
|
||||
tempImageContainer.addView(imageContainerColumn)
|
||||
}
|
||||
val imageCard = layoutInflater.inflate(
|
||||
R.layout.sticker_card,
|
||||
@ -302,7 +330,6 @@ class ImageKeyboard : InputMethodService() {
|
||||
imgButton.setOnClickListener { view: View ->
|
||||
val file = view.tag as File
|
||||
recentCache.add(file.absolutePath)
|
||||
Log.e("qwerty", recentCache.toSharedPref() )
|
||||
val stickerType = supportedMimes[Utils.getFileExtension(file.name)]
|
||||
if (stickerType == null) {
|
||||
// Sticker is unsupported by input
|
||||
@ -312,10 +339,8 @@ class ImageKeyboard : InputMethodService() {
|
||||
doCommitContent(file.name, stickerType, file)
|
||||
}
|
||||
imageContainerColumn.addView(imageCard)
|
||||
if (i % iconsPerRow == 0) {
|
||||
imageContainer!!.addView(imageContainerColumn)
|
||||
}
|
||||
}
|
||||
return tempImageContainer
|
||||
}
|
||||
|
||||
/**
|
||||
@ -323,7 +348,7 @@ class ImageKeyboard : InputMethodService() {
|
||||
*
|
||||
*/
|
||||
private fun recreatePackContainer() {
|
||||
packContainer!!.removeAllViewsInLayout()
|
||||
packContainer.removeAllViewsInLayout()
|
||||
// Back button
|
||||
if (sharedPreferences.getBoolean("showBackButton", false)) {
|
||||
addBackButtonToContainer()
|
||||
@ -338,7 +363,7 @@ class ImageKeyboard : InputMethodService() {
|
||||
addPackToContainer(loadedPacks[sortedPackName]!!)
|
||||
}
|
||||
if (sortedPackNames.isNotEmpty()) {
|
||||
recreateImageContainer(loadedPacks[sortedPackNames[0]]!!.stickerList)
|
||||
switchImageContainer(loadedPacks[sortedPackNames[0]]!!.stickerList)
|
||||
}
|
||||
}
|
||||
|
||||
@ -386,4 +411,4 @@ class ImageKeyboard : InputMethodService() {
|
||||
// Constants
|
||||
private const val AUTHORITY = "com.fredhappyface.ewesticker.inputcontent"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -189,18 +189,18 @@ class MainActivity : AppCompatActivity() {
|
||||
editor.putBoolean("disableAnimations", isChecked)
|
||||
editor.apply()
|
||||
}
|
||||
val iconsPerRowSeekBar = findViewById<SeekBar>(R.id.iconsPerRowSeekBar)
|
||||
iconsPerRowSeekBar.progress = sharedPreferences.getInt("iconsPerRow", 3)
|
||||
iconsPerRowSeekBar.setOnSeekBarChangeListener(object : OnSeekBarChangeListener {
|
||||
var iconsPerRow = 3
|
||||
val iconsPerColumnSeekBar = findViewById<SeekBar>(R.id.iconsPerColumnSeekBar)
|
||||
iconsPerColumnSeekBar.progress = sharedPreferences.getInt("iconsPerColumn", 3)
|
||||
iconsPerColumnSeekBar.setOnSeekBarChangeListener(object : OnSeekBarChangeListener {
|
||||
var iconsPerColumn = 3
|
||||
override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {
|
||||
iconsPerRow = progress
|
||||
iconsPerColumn = progress
|
||||
}
|
||||
|
||||
override fun onStartTrackingTouch(seekBar: SeekBar) {}
|
||||
override fun onStopTrackingTouch(seekBar: SeekBar) {
|
||||
val editor = sharedPreferences.edit()
|
||||
editor.putInt("iconsPerRow", iconsPerRow)
|
||||
editor.putInt("iconsPerColumn", iconsPerColumn)
|
||||
editor.apply()
|
||||
refreshKeyboardConfig()
|
||||
showChangedPrefText()
|
||||
@ -229,9 +229,9 @@ class MainActivity : AppCompatActivity() {
|
||||
* Refreshes config from preferences
|
||||
*/
|
||||
fun refreshKeyboardConfig() {
|
||||
val iconsPerRow = sharedPreferences.getInt("iconsPerRow", 3)
|
||||
val iconsPerRowValue = findViewById<TextView>(R.id.iconsPerRowValue)
|
||||
iconsPerRowValue.text = iconsPerRow.toString()
|
||||
val iconsPerColumn = sharedPreferences.getInt("iconsPerColumn", 3)
|
||||
val iconsPerColumnValue = findViewById<TextView>(R.id.iconsPerColumnValue)
|
||||
iconsPerColumnValue.text = iconsPerColumn.toString()
|
||||
val iconSize = sharedPreferences.getInt("iconSize", 160)
|
||||
val iconSizeValue = findViewById<TextView>(R.id.iconSizeValue)
|
||||
iconSizeValue.text = String.format("%dpx", iconSize)
|
||||
|
@ -102,14 +102,14 @@
|
||||
android:text="@string/icons_per_row_status" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/iconsPerRowValue"
|
||||
android:id="@+id/iconsPerColumnValue"
|
||||
style="@style/body_text"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textIsSelectable="false" />
|
||||
|
||||
<SeekBar
|
||||
android:id="@+id/iconsPerRowSeekBar"
|
||||
android:id="@+id/iconsPerColumnSeekBar"
|
||||
style="@style/body_text"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
@ -187,6 +187,3 @@
|
||||
</LinearLayout>
|
||||
|
||||
</ScrollView>
|
||||
|
||||
|
||||
|
||||
|
7
app/src/main/res/layout/image_container.xml
Normal file
@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@android:color/transparent"
|
||||
android:orientation="horizontal" />
|
||||
|
@ -13,7 +13,7 @@
|
||||
<string name="sub_options"> Options</string>
|
||||
<string name="show_back_button">Show back button in navbar?</string>
|
||||
<string name="disable_animations">Disable Animations?</string>
|
||||
<string name="icons_per_row_status">"Icons per row: "</string>
|
||||
<string name="icons_per_row_status">"Icons per column: "</string>
|
||||
<string name="icon_size_status">"Icon size: "</string>
|
||||
|
||||
<!-- About -->
|
||||
|
@ -1,13 +1,12 @@
|
||||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||
buildscript {
|
||||
ext.kotlin_version = "1.5.10"
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:7.0.0'
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.5.21"
|
||||
|
||||
// NOTE: Do not place your application dependencies here; they belong
|
||||
// in the individual module build.gradle files
|
||||
|
16
metadata/en-US/changelogs/20210810.txt
Normal file
@ -0,0 +1,16 @@
|
||||
<ul>
|
||||
<li>Code optimisations
|
||||
<ul>
|
||||
<li>Code clean-up</li>
|
||||
<li>Removed APNG animation due to memory leak</li>
|
||||
<li>Linting fixes</li>
|
||||
</ul></li>
|
||||
<li>Added caching functionality
|
||||
<ul>
|
||||
<li>to improve performance of fallback stickers</li>
|
||||
<li>to enable addition of recent list</li>
|
||||
<li>to improve switching packs performance</li>
|
||||
</ul></li>
|
||||
<li>Updated gradle and deps</li>
|
||||
<li>Add recent icon</li>
|
||||
</ul>
|
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 52 KiB |
Before Width: | Height: | Size: 123 KiB After Width: | Height: | Size: 126 KiB |
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 59 KiB |
Before Width: | Height: | Size: 93 KiB After Width: | Height: | Size: 191 KiB |