first release

This commit is contained in:
Kieran BW 2021-06-11 20:20:42 +01:00
commit cb4ac000bc
54 changed files with 1636 additions and 0 deletions

15
.gitignore vendored Normal file
View File

@ -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

3
.idea/.gitignore generated vendored Normal file
View File

@ -0,0 +1,3 @@
# Default ignored files
/shelf/
/workspace.xml

6
.idea/compiler.xml generated Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CompilerConfiguration">
<bytecodeTargetLevel target="11" />
</component>
</project>

19
.idea/gradle.xml generated Normal file
View File

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="GradleSettings">
<option name="linkedExternalProjectsSettings">
<GradleProjectSettings>
<option name="testRunner" value="PLATFORM" />
<option name="distributionType" value="DEFAULT_WRAPPED" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="modules">
<set>
<option value="$PROJECT_DIR$" />
<option value="$PROJECT_DIR$/app" />
</set>
</option>
<option name="resolveModulePerSourceSet" value="false" />
</GradleProjectSettings>
</option>
</component>
</project>

30
.idea/jarRepositories.xml generated Normal file
View File

@ -0,0 +1,30 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="RemoteRepositoriesConfiguration">
<remote-repository>
<option name="id" value="central" />
<option name="name" value="Maven Central repository" />
<option name="url" value="https://repo1.maven.org/maven2" />
</remote-repository>
<remote-repository>
<option name="id" value="jboss.community" />
<option name="name" value="JBoss Community repository" />
<option name="url" value="https://repository.jboss.org/nexus/content/repositories/public/" />
</remote-repository>
<remote-repository>
<option name="id" value="MavenRepo" />
<option name="name" value="MavenRepo" />
<option name="url" value="https://repo.maven.apache.org/maven2/" />
</remote-repository>
<remote-repository>
<option name="id" value="BintrayJCenter" />
<option name="name" value="BintrayJCenter" />
<option name="url" value="https://jcenter.bintray.com/" />
</remote-repository>
<remote-repository>
<option name="id" value="Google" />
<option name="name" value="Google" />
<option name="url" value="https://dl.google.com/dl/android/maven2/" />
</remote-repository>
</component>
</project>

9
.idea/misc.xml generated Normal file
View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectRootManager" version="2" languageLevel="JDK_11" default="true" project-jdk-name="1.8" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" />
</component>
<component name="ProjectType">
<option name="id" value="Android" />
</component>
</project>

10
.idea/runConfigurations.xml generated Normal file
View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="RunConfigurationProducerService">
<option name="ignoredProducers">
<set>
<option value="com.android.tools.idea.compose.preview.runconfiguration.ComposePreviewRunConfigurationProducer" />
</set>
</option>
</component>
</project>

6
.idea/vcs.xml generated Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>
</project>

1
app/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/build

55
app/build.gradle Normal file
View File

@ -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"
}

21
app/proguard-rules.pro vendored Normal file
View File

@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile

View File

@ -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)
}
}

View File

@ -0,0 +1,50 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.fredhappyface.whoosticker">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<application
android:allowBackup="false"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme"
android:usesCleartextTraffic="true"
tools:ignore="GoogleAppIndexingWarning">
<activity
android:name="com.fredhappyface.whoosticker.MainActivity"
android:configChanges="keyboard|keyboardHidden|orientation|screenSize"
android:exported="true"
android:label="@string/app_name"
android:windowSoftInputMode="adjustResize">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<service
android:name="com.fredhappyface.whoosticker.ImageKeyboard"
android:label="WhooSticker"
android:permission="android.permission.BIND_INPUT_METHOD">
<meta-data
android:name="android.view.im"
android:resource="@xml/method" />
<intent-filter>
<action android:name="android.view.InputMethod" />
</intent-filter>
</service>
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="com.fredhappyface.whoosticker.inputcontent"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
</provider>
</application>
</manifest>

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

View File

@ -0,0 +1,329 @@
package com.fredhappyface.whoosticker
import android.content.ClipDescription
import android.content.SharedPreferences
import android.graphics.Bitmap
import android.graphics.ImageDecoder
import android.graphics.drawable.AnimatedImageDrawable
import android.graphics.drawable.Drawable
import android.inputmethodservice.InputMethodService
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
import androidx.core.content.res.ResourcesCompat
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 java.io.File
import java.io.FileOutputStream
import java.io.IOException
import java.util.*
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 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<ImageButton>(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<ImageButton>(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<ImageButton>(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"
}
}

View File

@ -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<Button>(R.id.chooseStickerDir)
button.isEnabled = false
executor.execute {
val oldStickers = File(filesDir, "stickers")
deleteRecursive(oldStickers)
var stickersInDir = 0
val stickerDirPath = sharedPreferences.getString("stickerDirPath", "none set")
val tree = DocumentFile.fromTreeUri(applicationContext, Uri.parse(stickerDirPath))
val files = tree!!.listFiles()
for (file in files) {
if (file.isFile) stickersInDir += importSticker(file, "")
if (file.isDirectory) stickersInDir += importPack(file)
}
handler.post {
Toast.makeText(
applicationContext,
"Imported $stickersInDir stickers. You may need to reload the keyboard for new stickers to show up.",
Toast.LENGTH_LONG
).show()
val editor = sharedPreferences.edit()
editor.putInt("numStickersImported", stickersInDir)
editor.apply()
refreshStickerDirPath()
button.isEnabled = true
}
}
}
/**
* Handles ACTION_OPEN_DOCUMENT_TREE result and adds the returned Uri to shared prefs
*
* @param requestCode Int - RequestCode as defined under the Activity private vars
* @param resultCode Int - The result code, we only want to do stuff if successful
* @param data Intent? - Extra data in the form of an intent. tend to access .data
*/
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == chooseStickerDir && resultCode == RESULT_OK) {
if (data != null) {
val editor = sharedPreferences.edit()
editor.putString("stickerDirPath", data.data.toString())
editor.putString("lastUpdateDate", Calendar.getInstance().time.toString())
editor.apply()
refreshStickerDirPath()
importStickers()
}
}
}
/**
* Sets up content view, shared prefs, etc.
*
* @param savedInstanceState saved state
*/
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this)
refreshStickerDirPath()
refreshKeyboardConfig()
val backButtonToggle = findViewById<CompoundButton>(R.id.backButtonToggle)
backButtonToggle.isChecked = sharedPreferences.getBoolean("showBackButton", false)
backButtonToggle.setOnCheckedChangeListener { _: CompoundButton?, isChecked: Boolean ->
showChangedPrefText()
val editor = sharedPreferences.edit()
editor.putBoolean("showBackButton", isChecked)
editor.apply()
}
val disableAnimations = findViewById<CompoundButton>(R.id.disableAnimations)
disableAnimations.isChecked = sharedPreferences.getBoolean("disable_animations", false)
disableAnimations.setOnCheckedChangeListener { _: CompoundButton?, isChecked: Boolean ->
showChangedPrefText()
val editor = sharedPreferences.edit()
editor.putBoolean("disable_animations", isChecked)
editor.apply()
}
val iconsPerRowSeekBar = findViewById<SeekBar>(R.id.iconsPerRowSeekBar)
iconsPerRowSeekBar.progress = sharedPreferences.getInt("iconsPerRow", 3)
iconsPerRowSeekBar.setOnSeekBarChangeListener(object : OnSeekBarChangeListener {
var iconsPerRow = 3
override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {
iconsPerRow = progress
}
override fun onStartTrackingTouch(seekBar: SeekBar) {}
override fun onStopTrackingTouch(seekBar: SeekBar) {
val editor = sharedPreferences.edit()
editor.putInt("iconsPerRow", iconsPerRow)
editor.apply()
refreshKeyboardConfig()
showChangedPrefText()
}
})
val iconSizeSeekBar = findViewById<SeekBar>(R.id.iconSizeSeekBar)
iconSizeSeekBar.progress = sharedPreferences.getInt("iconSize", 160) / 10
iconSizeSeekBar.setOnSeekBarChangeListener(object : OnSeekBarChangeListener {
var iconSize = 160
override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {
iconSize = progress * 10
}
override fun onStartTrackingTouch(seekBar: SeekBar) {}
override fun onStopTrackingTouch(seekBar: SeekBar) {
val editor = sharedPreferences.edit()
editor.putInt("iconSize", iconSize)
editor.apply()
refreshKeyboardConfig()
showChangedPrefText()
}
})
}
/**
* Refreshes config from preferences
*/
fun refreshKeyboardConfig() {
val iconsPerRow = sharedPreferences.getInt("iconsPerRow", 3)
val iconsPerRowValue = findViewById<TextView>(R.id.iconsPerRowValue)
iconsPerRowValue.text = iconsPerRow.toString()
val iconSize = sharedPreferences.getInt("iconSize", 160)
val iconSizeValue = findViewById<TextView>(R.id.iconSizeValue)
iconSizeValue.text = String.format("%dpx", iconSize)
}
/**
* Rereads saved sticker dir path from preferences
*/
private fun refreshStickerDirPath() {
val stickerDirPath = sharedPreferences.getString("stickerDirPath", "none set")
val lastUpdateDate = sharedPreferences.getString("lastUpdateDate", "never")
val numStickersImported = sharedPreferences.getInt("numStickersImported", 0)
val dirStatus = findViewById<TextView>(R.id.stickerDirStatus)
dirStatus.text = String.format(
"%s on %s with %d stickers loaded.",
stickerDirPath,
lastUpdateDate,
numStickersImported
)
}
/**
* Reusable function to warn about changing preferences
*/
fun showChangedPrefText() {
Toast.makeText(
applicationContext,
"Preferences changed. You may need to reload the keyboard for settings to apply.",
Toast.LENGTH_LONG
).show()
}
}

View File

@ -0,0 +1,32 @@
package com.fredhappyface.whoosticker
import java.io.File
/**
* Helper class to provide pack-related information
* A "Pack" is informally represented as a File
*/
class StickerPack(packDir: File) {
private val stickers: Array<File>? =
packDir.listFiles { obj: File -> obj.isFile }?.sortedArray()
/**
* Note: When MainActivity copies files over, it filters out all non-supported files (i.e. any
* file that is not supported as well as directories). Because of this there is no extra filter
* in this function. The exception is the base directory, which is handled in the constructor.
*
* @return Array of Files corresponding to all stickers found in this pack
*/
val stickerList: Array<File>
get() = stickers!!
/**
* Provides a sticker to use as the pack-nav container thumbnail.
* Currently just takes the first element, but could theoretically include any selection logic.
*
* @return File that should be used for thumbnail
*/
val thumbSticker: File
get() = stickers!![0]
}

View File

@ -0,0 +1,35 @@
package com.fredhappyface.whoosticker
/**
* Class to provide utils that are shared across whoosticker.
*/
object Utils {
/**
* @param name the File's name. Takes in a string here instead of a File because in certain
* places we have to use DocumentFile instead-- String name can be found by calling
* .getName() on both, but they are different classes.
* @return returns "." inclusive file extension.
*/
fun getFileExtension(name: String): String {
val lastIndexOf = name.lastIndexOf(".")
return if (lastIndexOf == -1) {
""
} else name.substring(lastIndexOf)
}
/**
* Needs to create a new HashMap on every call because shallow copies will cause issues between
* different input areas that support different media types.
*
* @return HashMap of whoosticker-supported mimes. Keys are "." inclusive.
*/
fun getSupportedMimes(): MutableMap<String, String> {
return mutableMapOf(
".gif" to "image/gif",
".png" to "image/png",
".apng" to "image/png",
".jpg" to "image/jpg",
".webp" to "image/webp"
)
}
}

View File

@ -0,0 +1,74 @@
<?xml version="1.0" encoding="utf-8"?>
<vector
android:height="108dp"
android:width="108dp"
android:viewportHeight="108"
android:viewportWidth="108"
xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#3DDC84"
android:pathData="M0,0h108v108h-108z"/>
<path android:fillColor="#00000000" android:pathData="M9,0L9,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,0L19,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M29,0L29,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M39,0L39,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M49,0L49,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M59,0L59,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M69,0L69,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M79,0L79,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M89,0L89,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M99,0L99,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,9L108,9"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,19L108,19"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,29L108,29"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,39L108,39"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,49L108,49"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,59L108,59"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,69L108,69"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,79L108,79"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,89L108,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,99L108,99"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,29L89,29"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,39L89,39"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,49L89,49"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,59L89,59"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,69L89,69"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,79L89,79"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M29,19L29,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M39,19L39,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M49,19L49,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M59,19L59,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M69,19L69,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M79,19L79,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
</vector>

View File

@ -0,0 +1,31 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path
android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z">
<aapt:attr name="android:fillColor">
<gradient
android:startY="49.59793"
android:startX="42.9492"
android:endY="92.4963"
android:endX="85.84757"
android:type="linear">
<item
android:color="#44000000"
android:offset="0.0" />
<item
android:color="#00000000"
android:offset="1.0" />
</gradient>
</aapt:attr>
</path>
<path
android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z"
android:fillColor="#FFFFFF"
android:fillType="nonZero"
android:strokeWidth="1"
android:strokeColor="#00000000"/>
</vector>

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.9 KiB

View File

@ -0,0 +1,118 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:paddingLeft="16dp"
android:paddingRight="16dp">
<TextView
style="@style/widget"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/current_info" />
<TextView
android:id="@+id/stickerDirStatus"
style="@style/widget"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="" />
<Button
android:id="@+id/chooseStickerDir"
style="@style/widget"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="chooseDir"
android:text="@string/choose_dir" />
<View
style="@style/widget"
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="?android:attr/listDivider" />
<androidx.appcompat.widget.AppCompatCheckBox
android:id="@+id/backButtonToggle"
style="@style/widget"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/show_back_button" />
<androidx.appcompat.widget.AppCompatCheckBox
android:id="@+id/disableAnimations"
style="@style/widget"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/disable_animations" />
<LinearLayout
style="@style/widget"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/icons_per_row_status" />
<TextView
android:id="@+id/iconsPerRowValue"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<SeekBar
android:id="@+id/iconsPerRowSeekBar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:max="10"
android:min="1"
android:progress="4" />
</LinearLayout>
<LinearLayout
style="@style/widget"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/icon_size_status" />
<TextView
android:id="@+id/iconSizeValue"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<SeekBar
android:id="@+id/iconSizeSeekBar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:max="32"
android:min="8"
android:progress="16" />
</LinearLayout>
<View
style="@style/widget"
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="?android:attr/listDivider" />
<TextView
style="@style/widget"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:autoLink="web"
android:linksClickable="true"
android:text="@string/github" />
</LinearLayout>

View File

@ -0,0 +1,8 @@
<?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="vertical"
/>

View File

@ -0,0 +1,34 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/bg"
tools:context="com.fredhappyface.whoosticker.ImageKeyboard">
<HorizontalScrollView
android:id="@+id/topHScrollView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:background="@color/sticker">
<LinearLayout
android:id="@+id/packContainer"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal" />
</HorizontalScrollView>
<HorizontalScrollView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/topHScrollView">
<LinearLayout
android:id="@+id/imageContainer"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal" />
</HorizontalScrollView>
</RelativeLayout>

View File

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:cardCornerRadius="0dp"
app:cardUseCompatPadding="true"
android:layout_margin="4dp"
android:layout_marginHorizontal="8dp"
>
<ImageButton
android:id="@+id/ib3"
android:layout_width="40dp"
android:layout_height="40dp"
android:background="@color/sticker"
android:cropToPadding="true"
android:scaleType="fitCenter"
android:contentDescription="@string/pack_icon" />
</FrameLayout>

View File

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:cardCornerRadius="8dp"
app:cardUseCompatPadding="true"
>
<ImageButton
android:id="@+id/ib3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentStart="true"
android:layout_alignParentBottom="true"
android:background="@color/sticker"
android:cropToPadding="true"
android:scaleType="fitCenter"
android:contentDescription="@string/sticker_icon" />
</androidx.cardview.widget.CardView>

View File

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background"/>
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
</adaptive-icon>

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background"/>
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
</adaptive-icon>

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

View File

@ -0,0 +1,6 @@
<resources>
<!-- Base application theme. -->
<color name="bg">#212121</color>
<color name="fg">#e0e0e0</color>
<color name="sticker">#323232</color>
</resources>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="accent">#00796B</color>
<color name="accentDark">#00695C</color>
<color name="onAccent">#e0e0e0</color>
</resources>

View File

@ -0,0 +1,14 @@
<resources>
<string name="app_name">WhooSticker</string>
<string name="github">This is an early version of WhooSticker available at https://github.com/FredHappyface/Android.WhooSticker
Checkout woosticker (upstream) on GitHub: https://github.com/rzhou1999/woosticker</string>
<string name="choose_dir">Choose a new sticker source directory</string>
<string name="current_info">Current loaded sticker information: </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="icon_size_status">Icon size: </string>
<string name="pack_icon">Pack icon</string>
<string name="sticker_icon">Sticker icon</string>
</resources>

View File

@ -0,0 +1,44 @@
<resources>
<!-- Base application theme. -->
<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
<!-- Primary brand color. -->
<item name="colorPrimaryVariant">@color/accent</item>
<item name="colorOnPrimary">@color/onAccent</item>
<!-- Customize your theme here. -->
<item name="android:navigationBarColor">@color/accentDark</item>
<item name="colorPrimaryDark">@color/accentDark</item>
<item name="colorPrimary">@color/accent</item>
<item name="colorAccent">@color/accent</item>
<item name="android:textColorPrimary">@color/fg</item>
<item name="android:textColorSecondary">@color/fg</item>
<item name="android:textColor">@color/fg</item>
<item name="android:windowBackground">@color/bg</item>
<item name="actionOverflowButtonStyle">@style/OverflowButtonStyle</item>
<item name="actionBarStyle">@style/AppBarBackground</item>
<item name="actionBarTheme">@style/AppBarText</item>
</style>
<style name="OverflowButtonStyle" parent="Widget.AppCompat.ActionButton.Overflow">
<item name="android:tint">@color/onAccent</item>
</style>
<style name="AppBarBackground" parent="Widget.MaterialComponents.ActionBar.Solid">
<item name="android:background">@color/accent</item>
</style>
<style name="AppBarText" parent="ThemeOverlay.MaterialComponents.ActionBar.Surface">
<item name="android:textColorPrimary">@color/onAccent</item>
</style>
<style name="widget">
<item name="android:padding">8dp</item>
</style>
</resources>

View File

@ -0,0 +1,6 @@
<resources>
<!-- Base application theme. -->
<color name="bg">#fafafa</color>
<color name="fg">#424242</color>
<color name="sticker">#e0e0e0</color>
</resources>

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<paths>
<files-path name="added_stickers" path="stickers/"/>
</paths>

View File

@ -0,0 +1,2 @@
<?xml version="1.0" encoding="utf-8"?>
<input-method />

View File

@ -0,0 +1,17 @@
package com.fredhappyface.whoosticker
import org.junit.Test
import org.junit.Assert.*
/**
* Example local unit test, which will execute on the development machine (host).
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
class ExampleUnitTest {
@Test
fun addition_isCorrect() {
assertEquals(4, 2 + 2)
}
}

26
build.gradle Normal file
View File

@ -0,0 +1,26 @@
// 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:4.2.1"
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
allprojects {
repositories {
google()
mavenCentral()
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}

19
gradle.properties Normal file
View File

@ -0,0 +1,19 @@
# Project-wide Gradle settings.
# IDE (e.g. Android Studio) users:
# Gradle settings configured through the IDE *will override*
# any settings specified in this file.
# For more details on how to configure your build environment visit
# http://www.gradle.org/docs/current/userguide/build_environment.html
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true
# AndroidX package structure to make it clearer which packages are bundled with the
# Android operating system, and which are packaged with your app"s APK
# https://developer.android.com/topic/libraries/support-library/androidx-rn
android.useAndroidX=true
# Kotlin code style for this project: "official" or "obsolete":
kotlin.code.style=official

BIN
gradle/wrapper/gradle-wrapper.jar vendored Normal file

Binary file not shown.

View File

@ -0,0 +1,6 @@
#Fri Jun 11 17:07:37 BST 2021
distributionBase=GRADLE_USER_HOME
distributionUrl=https\://services.gradle.org/distributions/gradle-6.7.1-bin.zip
distributionPath=wrapper/dists
zipStorePath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME

172
gradlew vendored Normal file
View File

@ -0,0 +1,172 @@
#!/usr/bin/env sh
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS=""
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn () {
echo "$*"
}
die () {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin, switch paths to Windows format before running java
if $cygwin ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=$((i+1))
done
case $i in
(0) set -- ;;
(1) set -- "$args0" ;;
(2) set -- "$args0" "$args1" ;;
(3) set -- "$args0" "$args1" "$args2" ;;
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Escape application args
save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
APP_ARGS=$(save "$@")
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
cd "$(dirname "$0")"
fi
exec "$JAVACMD" "$@"

84
gradlew.bat vendored Normal file
View File

@ -0,0 +1,84 @@
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS=
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:init
@rem Get command-line arguments, handling Windows variants
if not "%OS%" == "Windows_NT" goto win9xME_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

2
settings.gradle Normal file
View File

@ -0,0 +1,2 @@
rootProject.name = "WhooSticker"
include ':app'