From cb4ac000bc76888e2b331e530aeded1f819fe74c Mon Sep 17 00:00:00 2001 From: Kieran BW <41634689+FredHappyface@users.noreply.github.com> Date: Fri, 11 Jun 2021 20:20:42 +0100 Subject: [PATCH] first release --- .gitignore | 15 + .idea/.gitignore | 3 + .idea/compiler.xml | 6 + .idea/gradle.xml | 19 + .idea/jarRepositories.xml | 30 ++ .idea/misc.xml | 9 + .idea/runConfigurations.xml | 10 + .idea/vcs.xml | 6 + app/.gitignore | 1 + app/build.gradle | 55 +++ app/proguard-rules.pro | 21 ++ .../whoosticker/ExampleInstrumentedTest.kt | 24 ++ app/src/main/AndroidManifest.xml | 50 +++ app/src/main/ic_launcher-playstore.png | Bin 0 -> 41296 bytes .../whoosticker/ImageKeyboard.kt | 329 ++++++++++++++++++ .../fredhappyface/whoosticker/MainActivity.kt | 266 ++++++++++++++ .../fredhappyface/whoosticker/StickerPack.kt | 32 ++ .../com/fredhappyface/whoosticker/Utils.kt | 35 ++ .../res/drawable/ic_launcher_background.xml | 74 ++++ .../res/drawable/ic_launcher_foreground.xml | 31 ++ .../drawable/tabler_icon_arrow_back_white.png | Bin 0 -> 9135 bytes app/src/main/res/layout/activity_main.xml | 118 +++++++ .../res/layout/image_container_column.xml | 8 + app/src/main/res/layout/keyboard_layout.xml | 34 ++ app/src/main/res/layout/pack_card.xml | 21 ++ app/src/main/res/layout/sticker_card.xml | 21 ++ app/src/main/res/mipmap-anydpi | 0 .../res/mipmap-anydpi-v26/ic_launcher.xml | 5 + .../mipmap-anydpi-v26/ic_launcher_round.xml | 5 + app/src/main/res/mipmap-hdpi/ic_launcher.png | Bin 0 -> 3735 bytes .../res/mipmap-hdpi/ic_launcher_round.png | Bin 0 -> 5523 bytes app/src/main/res/mipmap-mdpi/ic_launcher.png | Bin 0 -> 2689 bytes .../res/mipmap-mdpi/ic_launcher_round.png | Bin 0 -> 3472 bytes app/src/main/res/mipmap-xhdpi/ic_launcher.png | Bin 0 -> 5145 bytes .../res/mipmap-xhdpi/ic_launcher_round.png | Bin 0 -> 7810 bytes .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin 0 -> 8263 bytes .../res/mipmap-xxhdpi/ic_launcher_round.png | Bin 0 -> 12604 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.png | Bin 0 -> 11238 bytes .../res/mipmap-xxxhdpi/ic_launcher_round.png | Bin 0 -> 17524 bytes app/src/main/res/values-night/themes.xml | 6 + app/src/main/res/values/colors.xml | 6 + app/src/main/res/values/strings.xml | 14 + app/src/main/res/values/styles.xml | 44 +++ app/src/main/res/values/themes.xml | 6 + app/src/main/res/xml/file_paths.xml | 4 + app/src/main/res/xml/method.xml | 2 + .../whoosticker/ExampleUnitTest.kt | 17 + build.gradle | 26 ++ gradle.properties | 19 + gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 54329 bytes gradle/wrapper/gradle-wrapper.properties | 6 + gradlew | 172 +++++++++ gradlew.bat | 84 +++++ settings.gradle | 2 + 54 files changed, 1636 insertions(+) create mode 100644 .gitignore create mode 100644 .idea/.gitignore create mode 100644 .idea/compiler.xml create mode 100644 .idea/gradle.xml create mode 100644 .idea/jarRepositories.xml create mode 100644 .idea/misc.xml create mode 100644 .idea/runConfigurations.xml create mode 100644 .idea/vcs.xml create mode 100644 app/.gitignore create mode 100644 app/build.gradle create mode 100644 app/proguard-rules.pro create mode 100644 app/src/androidTest/java/com/fredhappyface/whoosticker/ExampleInstrumentedTest.kt create mode 100644 app/src/main/AndroidManifest.xml create mode 100644 app/src/main/ic_launcher-playstore.png create mode 100644 app/src/main/java/com/fredhappyface/whoosticker/ImageKeyboard.kt create mode 100644 app/src/main/java/com/fredhappyface/whoosticker/MainActivity.kt create mode 100644 app/src/main/java/com/fredhappyface/whoosticker/StickerPack.kt create mode 100644 app/src/main/java/com/fredhappyface/whoosticker/Utils.kt create mode 100644 app/src/main/res/drawable/ic_launcher_background.xml create mode 100644 app/src/main/res/drawable/ic_launcher_foreground.xml create mode 100644 app/src/main/res/drawable/tabler_icon_arrow_back_white.png create mode 100644 app/src/main/res/layout/activity_main.xml create mode 100644 app/src/main/res/layout/image_container_column.xml create mode 100644 app/src/main/res/layout/keyboard_layout.xml create mode 100644 app/src/main/res/layout/pack_card.xml create mode 100644 app/src/main/res/layout/sticker_card.xml create mode 100644 app/src/main/res/mipmap-anydpi create mode 100644 app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml create mode 100644 app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml create mode 100644 app/src/main/res/mipmap-hdpi/ic_launcher.png create mode 100644 app/src/main/res/mipmap-hdpi/ic_launcher_round.png create mode 100644 app/src/main/res/mipmap-mdpi/ic_launcher.png create mode 100644 app/src/main/res/mipmap-mdpi/ic_launcher_round.png create mode 100644 app/src/main/res/mipmap-xhdpi/ic_launcher.png create mode 100644 app/src/main/res/mipmap-xhdpi/ic_launcher_round.png create mode 100644 app/src/main/res/mipmap-xxhdpi/ic_launcher.png create mode 100644 app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png create mode 100644 app/src/main/res/mipmap-xxxhdpi/ic_launcher.png create mode 100644 app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png create mode 100644 app/src/main/res/values-night/themes.xml create mode 100644 app/src/main/res/values/colors.xml create mode 100644 app/src/main/res/values/strings.xml create mode 100644 app/src/main/res/values/styles.xml create mode 100644 app/src/main/res/values/themes.xml create mode 100644 app/src/main/res/xml/file_paths.xml create mode 100644 app/src/main/res/xml/method.xml create mode 100644 app/src/test/java/com/fredhappyface/whoosticker/ExampleUnitTest.kt create mode 100644 build.gradle create mode 100644 gradle.properties create mode 100644 gradle/wrapper/gradle-wrapper.jar create mode 100644 gradle/wrapper/gradle-wrapper.properties create mode 100644 gradlew create mode 100644 gradlew.bat create mode 100644 settings.gradle diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..aa724b7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,15 @@ +*.iml +.gradle +/local.properties +/.idea/caches +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +/.idea/navEditor.xml +/.idea/assetWizardSettings.xml +.DS_Store +/build +/captures +.externalNativeBuild +.cxx +local.properties diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..26d3352 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,3 @@ +# Default ignored files +/shelf/ +/workspace.xml diff --git a/.idea/compiler.xml b/.idea/compiler.xml new file mode 100644 index 0000000..fb7f4a8 --- /dev/null +++ b/.idea/compiler.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/gradle.xml b/.idea/gradle.xml new file mode 100644 index 0000000..440480e --- /dev/null +++ b/.idea/gradle.xml @@ -0,0 +1,19 @@ + + + + + + \ No newline at end of file diff --git a/.idea/jarRepositories.xml b/.idea/jarRepositories.xml new file mode 100644 index 0000000..0380d8d --- /dev/null +++ b/.idea/jarRepositories.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..860da66 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,9 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/runConfigurations.xml b/.idea/runConfigurations.xml new file mode 100644 index 0000000..797acea --- /dev/null +++ b/.idea/runConfigurations.xml @@ -0,0 +1,10 @@ + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..35eb1dd --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 0000000..ef60d96 --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,55 @@ +plugins { + id 'com.android.application' + id 'kotlin-android' +} + +android { + compileSdkVersion 30 + buildToolsVersion "30.0.3" + + defaultConfig { + applicationId "com.fredhappyface.whoosticker" + minSdkVersion 28 + targetSdkVersion 30 + versionCode 1 + versionName "1.0" + + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + } + + buildTypes { + android.applicationVariants.all { variant -> + variant.outputs.each { output -> + output.outputFileName = output.outputFileName.replace(".apk", "_" + defaultConfig.versionName + "_" + LocalDate.now() + ".apk") + } + } + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + kotlinOptions { + jvmTarget = '1.8' + } +} + +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 'androidx.constraintlayout:constraintlayout:2.0.4' + implementation 'androidx.navigation:navigation-fragment-ktx:2.3.5' + implementation 'androidx.navigation:navigation-ui-ktx:2.3.5' + 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' + + implementation "com.github.penfeizhou.android.animation:apng:2.10.0" +} \ No newline at end of file diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/app/proguard-rules.pro @@ -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 \ No newline at end of file diff --git a/app/src/androidTest/java/com/fredhappyface/whoosticker/ExampleInstrumentedTest.kt b/app/src/androidTest/java/com/fredhappyface/whoosticker/ExampleInstrumentedTest.kt new file mode 100644 index 0000000..0554e6f --- /dev/null +++ b/app/src/androidTest/java/com/fredhappyface/whoosticker/ExampleInstrumentedTest.kt @@ -0,0 +1,24 @@ +package com.fredhappyface.whoosticker + +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.ext.junit.runners.AndroidJUnit4 + +import org.junit.Test +import org.junit.runner.RunWith + +import org.junit.Assert.* + +/** + * Instrumented test, which will execute on an Android device. + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +@RunWith(AndroidJUnit4::class) +class ExampleInstrumentedTest { + @Test + fun useAppContext() { + // Context of the app under test. + val appContext = InstrumentationRegistry.getInstrumentation().targetContext + assertEquals("com.fredhappyface.whoosticker", appContext.packageName) + } +} \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..9ad4911 --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/ic_launcher-playstore.png b/app/src/main/ic_launcher-playstore.png new file mode 100644 index 0000000000000000000000000000000000000000..55c35b139dc27c096f9d86f3989fb5bd5d40f8ab GIT binary patch literal 41296 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 + private var loadedPacks = HashMap() + private var imageContainer: LinearLayout? = null + private var packContainer: LinearLayout? = null + private lateinit var internalDir: File + private var iconsPerRow = 0 + private var iconSize = 0 + private lateinit var sharedPreferences: SharedPreferences + + /** + * Adds a back button as a PackCard to keyboard that shows the InputMethodPicker + */ + private fun addBackButtonToContainer() { + val packCard = layoutInflater.inflate(R.layout.pack_card, packContainer, false) + val backButton = packCard.findViewById(R.id.ib3) + val icon = + ResourcesCompat.getDrawable(resources, R.drawable.tabler_icon_arrow_back_white, null) + backButton.setImageDrawable(icon) + backButton.setOnClickListener { + val inputMethodManager = applicationContext + .getSystemService(INPUT_METHOD_SERVICE) as InputMethodManager + inputMethodManager.showInputMethodPicker() + } + packContainer!!.addView(packCard) + } + + /** + * Adds a pack card to the keyboard from a StickerPack + * + * @param pack: StickerPack - the sticker pack to add + */ + private fun addPackToContainer(pack: StickerPack) { + val packCard = layoutInflater.inflate(R.layout.pack_card, packContainer, false) + val packButton = packCard.findViewById(R.id.ib3) + setPackButtonImage(pack, packButton) + packButton.tag = pack + packButton.setOnClickListener { view: View -> + imageContainer!!.removeAllViewsInLayout() + recreateImageContainer(view.tag as StickerPack) + } + packContainer!!.addView(packCard) + } + + /** + * In the event that a mimetype is unsupported by a InputConnectionCompat (looking at you, Signal) + * Create a temporary png and send that. In the event that png is not supported, create a toast as before + * + * @param file: File + */ + private fun doFallbackCommitContent(file: File) { + // PNG might not be supported so fallback to toast + if (supportedMimes[".png"] == null) { + Toast.makeText( + applicationContext, Utils.getFileExtension(file.name) + + " not supported here.", Toast.LENGTH_LONG + ).show() + return + } + // Create a new compatSticker and convert the sticker to png + val compatSticker = File(filesDir, "stickers/__compatSticker__/__compatSticker__.png") + compatSticker.parentFile?.mkdirs() // Protect against null pointer exception + try { + ImageDecoder.decodeBitmap(ImageDecoder.createSource(file)) + .compress(Bitmap.CompressFormat.PNG, 90, FileOutputStream(compatSticker)) + } catch (ignore: IOException) { + } + // Send the compatSticker! + doCommitContent("description", "image/png", compatSticker) + } + + /** + * @param description: String + * @param mimeType: String + * @param file: File + */ + private fun doCommitContent( + description: String, mimeType: String, + file: File + ) { + val contentUri = FileProvider.getUriForFile(this, AUTHORITY, file) + val flag = InputConnectionCompat.INPUT_CONTENT_GRANT_READ_URI_PERMISSION + val inputContentInfoCompat = InputContentInfoCompat( + contentUri, + ClipDescription(description, arrayOf(mimeType)), + null + ) + InputConnectionCompat.commitContent( + currentInputConnection, currentInputEditorInfo, inputContentInfoCompat, + flag, null + ) + } + + /** + * Apply a sticker file to the image button + * + * @param sticker: File - the file object representing the sticker + * @param btn: ImageButton - the button + */ + private fun setStickerButtonImage(sticker: File, btn: ImageButton) { + val sName = sticker.name + // Create drawable from file + var drawable: Drawable? = null + try { + 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() + } + // Disable animations? + if (drawable is AnimatedImageDrawable && !sharedPreferences.getBoolean( + "disable_animations", + false + ) + ) { + drawable.start() + } + if (drawable is APNGDrawable && sharedPreferences.getBoolean("disable_animations", false)) { + drawable.stop() + } + // Apply + btn.setImageDrawable(drawable) + } + + /** + * Apply a sticker the the pack icon (imagebutton) + * + * @param pack: StickerPack - the stickerpack to grab the pack icon from + * @param btn: ImageButton - the button + */ + private fun setPackButtonImage(pack: StickerPack, btn: ImageButton) { + setStickerButtonImage(pack.thumbSticker, btn) + } + + /** + * Check if the sticker is supported by the receiver + * + * @param editorInfo: EditorInfo - the editor/ receiver + * @param mimeType: String - the image mimetype + * @return boolean - is the mimetype supported? + */ + private fun isCommitContentSupported( + editorInfo: EditorInfo?, mimeType: String + ): Boolean { + if (editorInfo == null) { + return false + } + currentInputConnection ?: return false + if (!validatePackageName(editorInfo)) { + return false + } + val supportedMimeTypes = EditorInfoCompat.getContentMimeTypes(editorInfo) + for (supportedMimeType in supportedMimeTypes) { + if (ClipDescription.compareMimeTypes(mimeType, supportedMimeType)) { + return true + } + } + return false + } + + override fun onCreate() { + super.onCreate() + sharedPreferences = PreferenceManager.getDefaultSharedPreferences(baseContext) + iconsPerRow = sharedPreferences.getInt("iconsPerRow", 3) + iconSize = sharedPreferences.getInt("iconSize", 160) + reloadPacks() + } + + override fun onCreateInputView(): View { + val keyboardLayout = + layoutInflater.inflate(R.layout.keyboard_layout, null) as RelativeLayout + packContainer = keyboardLayout.findViewById(R.id.packContainer) + imageContainer = keyboardLayout.findViewById(R.id.imageContainer) + imageContainer?.layoutParams?.height = (iconSize * iconsPerRow * 1.4).toInt() + recreatePackContainer() + return keyboardLayout + } + + 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 + } + + override fun onStartInputView(info: EditorInfo?, restarting: Boolean) { + supportedMimes = Utils.getSupportedMimes() + var allSupported = true + val mimesToCheck = supportedMimes.keys.toTypedArray() + for (s in mimesToCheck) { + val mimeSupported = isCommitContentSupported(info, supportedMimes[s]!!) + allSupported = allSupported && mimeSupported + if (!mimeSupported) { + supportedMimes.remove(s) + } + } + if (!allSupported) { + Toast.makeText( + applicationContext, + "One or more image formats not supported here. Some stickers may not send correctly.", + Toast.LENGTH_LONG + ).show() + } + } + + private fun recreateImageContainer(pack: StickerPack) { + val imagesDir = File(filesDir, "stickers/$pack") + imagesDir.mkdirs() + imageContainer!!.removeAllViewsInLayout() + var imageContainerColumn = layoutInflater.inflate( + R.layout.image_container_column, + imageContainer, + false + ) as LinearLayout + val stickers = pack.stickerList + for (i in stickers.indices) { + if (i % iconsPerRow == 0) { + imageContainerColumn = layoutInflater.inflate( + R.layout.image_container_column, + imageContainer, + false + ) as LinearLayout + } + val imageCard = layoutInflater.inflate( + R.layout.sticker_card, + imageContainerColumn, + false + ) as CardView + val imgButton = imageCard.findViewById(R.id.ib3) + imgButton.layoutParams.height = iconSize + imgButton.layoutParams.width = iconSize + setStickerButtonImage(stickers[i], imgButton) + imgButton.tag = stickers[i] + imgButton.setOnClickListener { view: View -> + val file = view.tag as File + val stickerType = supportedMimes[Utils.getFileExtension(file.name)] + if (stickerType == null) { + doFallbackCommitContent(file) + return@setOnClickListener + } + doCommitContent(file.name, stickerType, file) + } + imageContainerColumn.addView(imageCard) + if (i % iconsPerRow == 0) { + imageContainer!!.addView(imageContainerColumn) + } + } + } + + private fun recreatePackContainer() { + packContainer!!.removeAllViewsInLayout() + // Back button + if (sharedPreferences.getBoolean("showBackButton", false)) { + addBackButtonToContainer() + } + // Packs + val sortedPackNames = loadedPacks.keys.toTypedArray() + Arrays.sort(sortedPackNames) + for (sortedPackName in sortedPackNames) { + addPackToContainer(loadedPacks[sortedPackName]!!) + } + if (sortedPackNames.isNotEmpty()) { + recreateImageContainer(loadedPacks[sortedPackNames[0]]!!) + } + } + + private fun reloadPacks() { + loadedPacks = HashMap() + internalDir = File(filesDir, "stickers") + val packs = internalDir.listFiles { obj: File -> obj.isDirectory } + if (packs != null) { + for (file in packs) { + val pack = StickerPack(file) + if (pack.stickerList.isNotEmpty()) { + loadedPacks[file.name] = pack + } + } + } + val baseStickers = internalDir.listFiles { obj: File -> obj.isFile } + if (baseStickers != null && baseStickers.isNotEmpty()) { + loadedPacks[""] = StickerPack(internalDir) + } + } + + private fun validatePackageName(editorInfo: EditorInfo?): Boolean { + if (editorInfo == null) { + return false + } + val packageName = editorInfo.packageName + return packageName != null + } + + companion object { + // Constants + private const val AUTHORITY = "com.fredhappyface.whoosticker.inputcontent" + } +} \ No newline at end of file diff --git a/app/src/main/java/com/fredhappyface/whoosticker/MainActivity.kt b/app/src/main/java/com/fredhappyface/whoosticker/MainActivity.kt new file mode 100644 index 0000000..f71c5b2 --- /dev/null +++ b/app/src/main/java/com/fredhappyface/whoosticker/MainActivity.kt @@ -0,0 +1,266 @@ +package com.fredhappyface.whoosticker + +import android.content.Intent +import android.content.SharedPreferences +import android.net.Uri +import android.os.Bundle +import android.os.Handler +import android.os.Looper +import android.view.View +import android.widget.* +import android.widget.SeekBar.OnSeekBarChangeListener +import androidx.appcompat.app.AppCompatActivity +import androidx.documentfile.provider.DocumentFile +import androidx.preference.PreferenceManager +import java.io.File +import java.io.IOException +import java.nio.file.Files +import java.util.* +import java.util.concurrent.Executors + +class MainActivity : AppCompatActivity() { + private val chooseStickerDir = 62519 + private val supportedMimes = Utils.getSupportedMimes() + lateinit var sharedPreferences: SharedPreferences + + /** + * For each sticker, check if it is in a compatible file format with WhooSticker + * + * @param sticker sticker to check compatibility with WhooSticker for + * @return true if supported image type + */ + private fun canImportSticker(sticker: DocumentFile): Boolean { + val mimesToCheck = ArrayList(supportedMimes.keys) + return !(sticker.isDirectory || + !mimesToCheck.contains(sticker.name?.let { Utils.getFileExtension(it) })) + } + + /** + * Called on button press to choose a new directory + * + * @param view: View + */ + fun chooseDir(view: View) { + val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE) + intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) + intent.addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION) + startActivityForResult(intent, chooseStickerDir) + } + + /** + * Delete everything from input File + * + * @param fileOrDirectory File to start deleting from + */ + private fun deleteRecursive(fileOrDirectory: File) { + if (fileOrDirectory.isDirectory) { + for (child in Objects.requireNonNull(fileOrDirectory.listFiles())) { + deleteRecursive(child) + } + } + fileOrDirectory.delete() + } + + /** + * Copies images from pack directory by calling importSticker() on all of them + * + * @param pack source pack + */ + private fun importPack(pack: DocumentFile): Int { + var stickersInPack = 0 + val stickers = pack.listFiles() + for (sticker in stickers) { + stickersInPack += importSticker(sticker, pack.name + "/") + } + return stickersInPack + } + + /** + * Copies stickers from source to internal storage + * + * @param sticker sticker to copy over + * @param pack the pack which the sticker belongs to + */ + private fun importSticker(sticker: DocumentFile, pack: String): Int { + if (!canImportSticker(sticker)) { + return 0 + } + val destSticker = File(filesDir, "stickers/" + pack + sticker.name) + destSticker.parentFile?.mkdirs() + try { + val inputStream = contentResolver.openInputStream(sticker.uri) + Files.copy(inputStream, destSticker.toPath()) + inputStream!!.close() + } catch (e: IOException) { + e.printStackTrace() + } + return 1 + } + + /** + * Import files from storage to internal directory + */ + private fun importStickers() { + //Use worker thread because this takes several seconds + val executor = Executors.newSingleThreadExecutor() + val handler = Handler(Looper.getMainLooper()) + Toast.makeText( + applicationContext, + "Starting import. You will not be able to reselect directory until finished. This might take a bit!", + Toast.LENGTH_LONG + ).show() + val button = findViewById