launcher/app/src/main/java/com/finnmglas/launcher/Functions.kt
Finn M Glas 08d4a6c2ef
Ensure upwards compatibility of themes and preferences
The app now saves which version was used the last time and recognizes, 
if something changed / settings have to be adjusted to fit the version.
2020-06-24 11:11:09 +02:00

407 lines
No EOL
12 KiB
Kotlin

package com.finnmglas.launcher
import android.app.Activity
import android.app.AlertDialog
import android.content.Context
import android.content.DialogInterface
import android.content.Intent
import android.content.SharedPreferences
import android.content.pm.PackageManager
import android.graphics.*
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.provider.Settings
import android.util.DisplayMetrics
import android.view.View
import android.view.animation.*
import android.widget.Button
import android.widget.ImageView
import android.widget.Toast
import com.finnmglas.launcher.list.ListActivity
import com.finnmglas.launcher.list.apps.AppsRecyclerAdapter
import com.finnmglas.launcher.settings.SettingsActivity
import com.finnmglas.launcher.settings.intendedSettingsPause
import com.finnmglas.launcher.tutorial.TutorialActivity
import kotlin.math.roundToInt
/* Preferences (global, initialised when app is started) */
lateinit var launcherPreferences: SharedPreferences
/* Preference Key Constants */
const val ACTION_UP = "action_upApp"
const val ACTION_DOWN = "action_downApp"
const val ACTION_RIGHT = "action_rightApp"
const val ACTION_LEFT = "action_leftApp"
const val ACTION_VOL_UP = "action_volumeUpApp"
const val ACTION_VOL_DOWN = "action_volumeDownApp"
const val ACTION_DOUBLE_CLICK = "action_doubleClickApp"
const val ACTION_LONG_CLICK = "action_longClickApp"
const val ACTION_CALENDAR = "action_calendarApp"
const val ACTION_CLOCK = "action_clockApp"
val ACTIONS = listOf(ACTION_UP, ACTION_DOWN, ACTION_RIGHT, ACTION_LEFT,
ACTION_VOL_UP, ACTION_VOL_DOWN, ACTION_DOUBLE_CLICK, ACTION_LONG_CLICK,
ACTION_CALENDAR, ACTION_CLOCK)
const val PREF_DOMINANT = "custom_dominant"
const val PREF_VIBRANT = "custom_vibrant"
const val PREF_WALLPAPER = "background_uri"
const val PREF_THEME = "theme"
const val PREF_STARTED = "startedBefore"
const val PREF_STARTED_TIME = "firstStartup"
const val PREF_VERSION = "version"
/* Objects used by multiple activities */
lateinit var appListViewAdapter: AppsRecyclerAdapter
/* Variables containing settings */
val displayMetrics = DisplayMetrics()
var upApp = ""
var downApp = ""
var rightApp = ""
var leftApp = ""
var volumeUpApp = ""
var volumeDownApp = ""
var doubleClickApp = ""
var longClickApp = ""
var calendarApp = ""
var clockApp = ""
var background : Bitmap? = null
var dominantColor = 0
var vibrantColor = 0
/* REQUEST CODES */
const val REQUEST_PICK_IMAGE = 1
const val REQUEST_CHOOSE_APP = 2
const val REQUEST_UNINSTALL = 3
const val REQUEST_PERMISSION_STORAGE = 4
/* Animate */
// Taken from https://stackoverflow.com/questions/47293269
fun View.blink(
times: Int = Animation.INFINITE,
duration: Long = 1000L,
offset: Long = 20L,
minAlpha: Float = 0.2f,
maxAlpha: Float = 1.0f,
repeatMode: Int = Animation.REVERSE
) {
startAnimation(AlphaAnimation(minAlpha, maxAlpha).also {
it.duration = duration
it.startOffset = offset
it.repeatMode = repeatMode
it.repeatCount = times
})
}
fun View.fadeIn(duration: Long = 300L) {
startAnimation(AlphaAnimation(0f, 1f).also {
it.interpolator = DecelerateInterpolator()
it.duration = duration
})
}
fun View.fadeOut(duration: Long = 300L) {
startAnimation(AlphaAnimation(1f, 0f).also {
it.interpolator = DecelerateInterpolator()
it.duration = duration
})
}
fun View.fadeRotateIn(duration: Long = 500L) {
val combined = AnimationSet(false)
combined.addAnimation(
AlphaAnimation(0f, 1F).also {
it.interpolator = DecelerateInterpolator()
it.duration = duration
}
)
combined.addAnimation(
RotateAnimation(0F, 180F, Animation.RELATIVE_TO_SELF,
0.5f, Animation.RELATIVE_TO_SELF,0.5f).also {
it.duration = duration * 2
it.interpolator = DecelerateInterpolator()
}
)
startAnimation(combined)
}
fun View.fadeRotateOut(duration: Long = 500L) {
val combined = AnimationSet(false)
combined.addAnimation(
AlphaAnimation(1F, 0F).also {
it.interpolator = AccelerateInterpolator()
it.duration = duration
}
)
combined.addAnimation(
RotateAnimation(0F, 180F, Animation.RELATIVE_TO_SELF,
0.5f, Animation.RELATIVE_TO_SELF,0.5f).also {
it.duration = duration
it.interpolator = AccelerateInterpolator()
}
)
startAnimation(combined)
}
/* Activity related */
fun isInstalled(uri: String, context: Context): Boolean {
if (uri.startsWith("launcher:")) return true // All internal actions
try {
context.packageManager.getPackageInfo(uri, PackageManager.GET_ACTIVITIES)
return true
} catch (e: PackageManager.NameNotFoundException) {
}
return false
}
private fun getIntent(packageName: String, context: Context): Intent? {
val intent: Intent? = context.packageManager.getLaunchIntentForPackage(packageName)
intent?.addCategory(Intent.CATEGORY_LAUNCHER)
return intent
}
fun launch(data: String, activity: Activity,
animationIn: Int = android.R.anim.fade_in, animationOut: Int = android.R.anim.fade_out) {
if (data.startsWith("launcher:")) // [type]:[info]
when(data.split(":")[1]) {
"settings" -> openSettings(activity)
"choose" -> openAppsList(activity)
"tutorial" -> openTutorial(activity)
}
else launchApp(data, activity) // app
activity.overridePendingTransition(animationIn, animationOut)
}
fun launchApp(packageName: String, context: Context) {
val intent = getIntent(packageName, context)
if (intent != null) {
context.startActivity(intent)
} else {
if (isInstalled(packageName, context)){
AlertDialog.Builder(context,
R.style.AlertDialogCustom
)
.setTitle(context.getString(R.string.alert_cant_open_title))
.setMessage(context.getString(R.string.alert_cant_open_message))
.setPositiveButton(android.R.string.yes,
DialogInterface.OnClickListener { dialog, which ->
openAppSettings(
packageName,
context
)
})
.setNegativeButton(android.R.string.no, null)
.setIcon(android.R.drawable.ic_dialog_info)
.show()
} else {
Toast.makeText( context, context.getString(R.string.toast_cant_open_message), Toast.LENGTH_SHORT).show()
}
}
}
fun openNewTabWindow(urls: String, context : Context) {
val uris = Uri.parse(urls)
val intents = Intent(Intent.ACTION_VIEW, uris)
val b = Bundle()
b.putBoolean("new_window", true)
intents.putExtras(b)
context.startActivity(intents)
}
/* Settings related functions */
fun getSavedTheme(context : Context) : String {
return launcherPreferences.getString(PREF_THEME, "finn").toString()
}
fun saveTheme(themeName : String) : String {
launcherPreferences.edit()
.putString(PREF_THEME, themeName)
.apply()
return themeName
}
fun resetToDefaultTheme(activity: Activity) {
dominantColor = activity.resources.getColor(R.color.finnmglasTheme_background_color)
vibrantColor = activity.resources.getColor(R.color.finnmglasTheme_accent_color)
launcherPreferences.edit()
.putString(PREF_WALLPAPER, "")
.putInt(PREF_DOMINANT, dominantColor)
.putInt(PREF_VIBRANT, vibrantColor)
.apply()
saveTheme("finn")
loadSettings()
intendedSettingsPause = true
activity.recreate()
}
fun resetToDarkTheme(activity: Activity) {
dominantColor = activity.resources.getColor(R.color.darkTheme_background_color)
vibrantColor = activity.resources.getColor(R.color.darkTheme_accent_color)
launcherPreferences.edit()
.putString(PREF_WALLPAPER, "")
.putInt(PREF_DOMINANT, dominantColor)
.putInt(PREF_VIBRANT, vibrantColor)
.apply()
saveTheme("dark")
intendedSettingsPause = true
activity.recreate()
}
fun openAppSettings(pkg :String, context:Context) {
val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
intent.data = Uri.parse("package:$pkg")
context.startActivity(intent)
}
fun openSettings(activity: Activity) {
activity.startActivity(Intent(activity, SettingsActivity::class.java))
}
fun openTutorial(activity: Activity){
activity.startActivity(Intent(activity, TutorialActivity::class.java))
}
fun openAppsList(activity: Activity){
val intent = Intent(activity, ListActivity::class.java)
intent.putExtra("intention", "view")
intendedSettingsPause = true
activity.startActivity(intent)
}
fun loadSettings() {
upApp = launcherPreferences.getString(ACTION_UP, "")!!
downApp = launcherPreferences.getString(ACTION_DOWN, "")!!
rightApp = launcherPreferences.getString(ACTION_RIGHT, "")!!
leftApp = launcherPreferences.getString(ACTION_LEFT, "")!!
volumeUpApp = launcherPreferences.getString(ACTION_VOL_UP, "")!!
volumeDownApp = launcherPreferences.getString(ACTION_VOL_DOWN, "")!!
doubleClickApp = launcherPreferences.getString(ACTION_DOUBLE_CLICK, "")!!
longClickApp = launcherPreferences.getString(ACTION_LONG_CLICK, "")!!
calendarApp = launcherPreferences.getString(ACTION_CALENDAR, "")!!
clockApp = launcherPreferences.getString(ACTION_CLOCK, "")!!
dominantColor = launcherPreferences.getInt(PREF_DOMINANT, 0)
vibrantColor = launcherPreferences.getInt(PREF_VIBRANT, 0)
}
fun resetSettings(context: Context) {
val editor = launcherPreferences.edit()
// set default theme
dominantColor = context.resources.getColor(R.color.finnmglasTheme_background_color)
vibrantColor = context.resources.getColor(R.color.finnmglasTheme_accent_color)
launcherPreferences.edit()
.putString(PREF_WALLPAPER, "")
.putInt(PREF_DOMINANT, dominantColor)
.putInt(PREF_VIBRANT, vibrantColor)
.apply()
saveTheme("finn")
// load action defaults
for (actionKey in ACTIONS)
editor.putString(actionKey, pickDefaultApp(actionKey, context))
editor.apply()
}
fun pickDefaultApp(action: String, context: Context) : String {
val arrayResource = when (action) {
ACTION_UP -> R.array.default_up
ACTION_DOWN -> R.array.default_down
ACTION_RIGHT -> R.array.default_right
ACTION_LEFT -> R.array.default_left
ACTION_VOL_UP -> R.array.default_volume_up
ACTION_VOL_DOWN -> R.array.default_volume_down
ACTION_DOUBLE_CLICK -> R.array.default_double_click
ACTION_LONG_CLICK -> R.array.default_long_click
ACTION_CLOCK -> R.array.default_clock
ACTION_CALENDAR -> R.array.default_left
else -> return "" // just prevent crashing on unknown input
}
val list = context.resources.getStringArray(arrayResource)
for (packageName in list)
if (isInstalled(packageName, context)) return packageName
return ""
}
// Used in Tutorial and Settings `ActivityOnResult`
fun saveListActivityChoice(data: Intent?) {
val value = data?.getStringExtra("value")
val forApp = data?.getStringExtra("forApp") ?: return
launcherPreferences.edit()
.putString("action_$forApp", value.toString())
.apply()
loadSettings()
}
/* Bitmaps */
fun setButtonColor(btn: Button, color: Int) {
if (Build.VERSION.SDK_INT >= 29)
btn.background.colorFilter = BlendModeColorFilter(color, BlendMode.MULTIPLY)
else if(Build.VERSION.SDK_INT >= 21) {
// tested with API 17 (Android 4.4.2 on S4 mini) -> fails
// tested with API 28 (Android 9 on S8) -> necessary
btn.background.colorFilter = PorterDuffColorFilter(color, PorterDuff.Mode.SRC_ATOP)
}
// not setting it in any other case (yet), unable to find a good solution
}
// Taken from: https://stackoverflow.com/a/33072575/12787264
fun manipulateColor(color: Int, factor: Float): Int {
val a = Color.alpha(color)
val r = (Color.red(color) * factor).roundToInt()
val g = (Color.green(color) * factor).roundToInt()
val b = (Color.blue(color) * factor).roundToInt()
return Color.argb(
a,
r.coerceAtMost(255),
g.coerceAtMost(255),
b.coerceAtMost(255)
)
}
// Taken from: https://stackoverflow.com/a/30340794/12787264
fun transformGrayscale(imageView: ImageView){
val matrix = ColorMatrix()
matrix.setSaturation(0f)
val filter = ColorMatrixColorFilter(matrix)
imageView.colorFilter = filter
}