Initial commit
This commit is contained in:
84
app/src/main/java/de/mm20/launcher2/LauncherApplication.kt
Normal file
84
app/src/main/java/de/mm20/launcher2/LauncherApplication.kt
Normal file
@@ -0,0 +1,84 @@
|
||||
package de.mm20.launcher2
|
||||
|
||||
import android.app.Application
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
import android.graphics.Bitmap
|
||||
import androidx.appcompat.app.AppCompatDelegate
|
||||
import de.mm20.launcher2.debug.Debug
|
||||
import de.mm20.launcher2.icons.IconRepository
|
||||
import de.mm20.launcher2.preferences.LauncherPreferences
|
||||
import de.mm20.launcher2.preferences.Themes
|
||||
import de.mm20.launcher2.ui.legacy.helper.WallpaperBlur
|
||||
import kotlinx.coroutines.*
|
||||
import java.text.Collator
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
|
||||
class LauncherApplication : Application(), CoroutineScope {
|
||||
|
||||
override val coroutineContext: CoroutineContext
|
||||
get() = Dispatchers.Main + SupervisorJob()
|
||||
|
||||
var blurredWallpaper: Bitmap? = null
|
||||
|
||||
|
||||
private val appReceiver = object : BroadcastReceiver() {
|
||||
override fun onReceive(context: Context?, intent: Intent?) {
|
||||
IconRepository.getInstance(this@LauncherApplication).requestIconPackListUpdate()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
Debug()
|
||||
instance = this
|
||||
LauncherPreferences.initialize(this)
|
||||
IconRepository.getInstance(this).requestIconPackListUpdate()
|
||||
|
||||
registerReceiver(appReceiver, IntentFilter().apply {
|
||||
addAction(Intent.ACTION_PACKAGE_REPLACED)
|
||||
addAction(Intent.ACTION_PACKAGE_ADDED)
|
||||
addAction(Intent.ACTION_PACKAGE_REMOVED)
|
||||
addAction(Intent.ACTION_MY_PACKAGE_REPLACED)
|
||||
addAction(Intent.ACTION_PACKAGE_CHANGED)
|
||||
addDataScheme("package")
|
||||
})
|
||||
val theme = LauncherPreferences.instance.theme
|
||||
AppCompatDelegate.setDefaultNightMode(
|
||||
when (theme) {
|
||||
Themes.LIGHT -> AppCompatDelegate.MODE_NIGHT_NO // light
|
||||
Themes.DARK -> AppCompatDelegate.MODE_NIGHT_YES // dark, black
|
||||
Themes.AUTO -> AppCompatDelegate.MODE_NIGHT_AUTO // auto
|
||||
else -> AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM //system
|
||||
}
|
||||
)
|
||||
WallpaperBlur.requestBlur(this)
|
||||
@Suppress("DEPRECATION") // We need to access the wallpaper directly to blur it
|
||||
registerReceiver(WallpaperReceiver(), IntentFilter(Intent.ACTION_WALLPAPER_CHANGED))
|
||||
}
|
||||
|
||||
companion object {
|
||||
lateinit var instance: LauncherApplication
|
||||
|
||||
val collator: Collator by lazy {
|
||||
Collator.getInstance().apply { strength = Collator.SECONDARY }
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
object PermissionRequests {
|
||||
const val CALENDAR = 309
|
||||
const val LOCATION = 410
|
||||
const val ALL = 666
|
||||
}
|
||||
|
||||
class WallpaperReceiver : BroadcastReceiver() {
|
||||
override fun onReceive(context: Context, intent: Intent?) {
|
||||
WallpaperBlur.requestBlur(context)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package de.mm20.launcher2.activity;
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.content.pm.LauncherApps
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import de.mm20.launcher2.favorites.FavoritesRepository
|
||||
import de.mm20.launcher2.search.data.AppShortcut
|
||||
|
||||
class AddItemActivity : Activity() {
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
val launcherApps = getSystemService(Context.LAUNCHER_APPS_SERVICE) as LauncherApps
|
||||
val pinRequest = launcherApps.getPinItemRequest(intent) ?: return run { finish() }
|
||||
val shortcutInfo = pinRequest.shortcutInfo ?: return run { finish() }
|
||||
val shortcut = AppShortcut(this.applicationContext, shortcutInfo,
|
||||
packageManager.getApplicationInfo(shortcutInfo.`package`, 0)
|
||||
.loadLabel(packageManager).toString())
|
||||
if (pinRequest.accept()) {
|
||||
FavoritesRepository.getInstance(this).pinItem(shortcut)
|
||||
}
|
||||
}
|
||||
finish()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
package de.mm20.launcher2.activity
|
||||
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.fragment.app.Fragment
|
||||
import de.mm20.launcher2.R
|
||||
import de.mm20.launcher2.fragment.PreferencesCalendarFragment
|
||||
import de.mm20.launcher2.fragment.PreferencesMainFragment
|
||||
import de.mm20.launcher2.fragment.PreferencesServicesFragment
|
||||
import de.mm20.launcher2.fragment.PreferencesWeatherFragment
|
||||
import de.mm20.launcher2.ui.legacy.activity.LauncherActivity
|
||||
|
||||
class SettingsActivity : AppCompatActivity() {
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
if (savedInstanceState == null) {
|
||||
val fragment = getStartFragment()
|
||||
setupActionBar()
|
||||
supportFragmentManager
|
||||
.beginTransaction()
|
||||
.add(android.R.id.content, fragment)
|
||||
.commit()
|
||||
} else if (!savedInstanceState.getBoolean("theme_change")) {
|
||||
val fragment = getStartFragment()
|
||||
setupActionBar()
|
||||
supportFragmentManager
|
||||
.beginTransaction()
|
||||
.replace(android.R.id.content, fragment)
|
||||
.commit()
|
||||
}
|
||||
findViewById<View>(android.R.id.content)?.setBackgroundColor(getColor(R.color.settings_window_background))
|
||||
}
|
||||
|
||||
override fun onSaveInstanceState(outState: Bundle) {
|
||||
super.onSaveInstanceState(outState)
|
||||
outState.putBoolean("theme_change", true)
|
||||
}
|
||||
|
||||
private fun getStartFragment(): Fragment {
|
||||
return when (intent.extras?.getString(FRAGMENT, "")) {
|
||||
FRAGMENT_CALENDAR -> PreferencesCalendarFragment()
|
||||
FRAGMENT_WEATHER -> PreferencesWeatherFragment()
|
||||
FRAGMENT_SERVICES -> PreferencesServicesFragment()
|
||||
else -> PreferencesMainFragment()
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupActionBar() {
|
||||
actionBar?.setDisplayHomeAsUpEnabled(true)
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
val id = item.itemId
|
||||
if (id == android.R.id.home) {
|
||||
if (supportFragmentManager.backStackEntryCount == 0) {
|
||||
finish()
|
||||
startActivity(Intent(this, LauncherActivity::class.java))
|
||||
} else {
|
||||
supportFragmentManager.popBackStack()
|
||||
}
|
||||
return true
|
||||
}
|
||||
return super.onOptionsItemSelected(item)
|
||||
}
|
||||
|
||||
override fun onBackPressed() {
|
||||
if (supportFragmentManager.backStackEntryCount > 0) {
|
||||
supportFragmentManager.popBackStack()
|
||||
} else {
|
||||
finish()
|
||||
startActivity(Intent(this, LauncherActivity::class.java))
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val RESULT_NEED_RESTART = 0x09
|
||||
const val FRAGMENT_WEATHER: String = "weather"
|
||||
const val FRAGMENT_CALENDAR: String = "calendar"
|
||||
const val FRAGMENT_SERVICES: String = "services"
|
||||
const val FRAGMENT: String = "fragment"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package de.mm20.launcher2.content
|
||||
|
||||
import androidx.core.content.FileProvider
|
||||
|
||||
class GenericFileProvider: FileProvider()
|
||||
@@ -0,0 +1,176 @@
|
||||
package de.mm20.launcher2.fragment
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageManager
|
||||
import android.os.Bundle
|
||||
import android.widget.Toast
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.preference.Preference
|
||||
import androidx.preference.PreferenceCategory
|
||||
import androidx.preference.PreferenceFragmentCompat
|
||||
import com.afollestad.materialdialogs.MaterialDialog
|
||||
import de.mm20.launcher2.R
|
||||
import de.mm20.launcher2.crashreporter.CrashReporter
|
||||
import de.mm20.launcher2.helper.DebugInformationDumper
|
||||
|
||||
|
||||
class PreferencesAboutFragment : PreferenceFragmentCompat() {
|
||||
|
||||
private var easterEggCounter = 0
|
||||
|
||||
@SuppressLint("ResourceType")
|
||||
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||
addPreferencesFromResource(R.xml.preferences_about)
|
||||
val versionPref = findPreference<Preference>("version")!!
|
||||
try {
|
||||
val version = requireContext().packageManager.getPackageInfo(
|
||||
requireActivity().application.packageName,
|
||||
0
|
||||
).versionName
|
||||
versionPref.summary = version
|
||||
} catch (e: PackageManager.NameNotFoundException) {
|
||||
//Should never happen
|
||||
versionPref.summary = "Ich mag Bockwurst-Bananen"
|
||||
}
|
||||
|
||||
versionPref.setOnPreferenceClickListener {
|
||||
if (easterEggCounter in arrayOf(3, 4, 7)) Toast.makeText(
|
||||
context, when (easterEggCounter) {
|
||||
3 -> R.string.easter_egg_1
|
||||
4 -> R.string.easter_egg_2
|
||||
7 -> R.string.easter_egg_3
|
||||
else -> 0
|
||||
}, Toast.LENGTH_SHORT
|
||||
).show()
|
||||
if (easterEggCounter == 8) {
|
||||
easterEggCounter = 0
|
||||
requireFragmentManager().beginTransaction()
|
||||
.setCustomAnimations(
|
||||
R.anim.preference_fragment_child_enter,
|
||||
R.anim.preference_fragment_parent_exit,
|
||||
R.anim.preference_fragment_parent_enter,
|
||||
R.anim.preference_fragment_child_exit
|
||||
)
|
||||
.replace(
|
||||
android.R.id.content,
|
||||
PreferencesEasterEggFragment()
|
||||
)
|
||||
.addToBackStack(null)
|
||||
.commit()
|
||||
}
|
||||
easterEggCounter++
|
||||
false
|
||||
}
|
||||
|
||||
val licenses = findPreference<Preference>("category_licenses") as PreferenceCategory
|
||||
for (l in LICENSES) {
|
||||
val license = resources.obtainTypedArray(l)
|
||||
val preference = Preference(activity, null, 0, R.style.Preference_Material)
|
||||
preference.title = license.getString(0)
|
||||
preference.summary = license.getString(1)
|
||||
preference.onPreferenceClickListener = Preference.OnPreferenceClickListener {
|
||||
parentFragmentManager.beginTransaction()
|
||||
.setCustomAnimations(
|
||||
R.anim.preference_fragment_child_enter,
|
||||
R.anim.preference_fragment_parent_exit,
|
||||
R.anim.preference_fragment_parent_enter,
|
||||
R.anim.preference_fragment_child_exit
|
||||
)
|
||||
.replace(android.R.id.content,
|
||||
PreferencesLicenseFragment().apply { library = l })
|
||||
.addToBackStack(null)
|
||||
.commit()
|
||||
true
|
||||
}
|
||||
license.recycle()
|
||||
licenses.addPreference(preference)
|
||||
}
|
||||
findPreference<Preference>("crash_reporter")?.setOnPreferenceClickListener {
|
||||
startActivity(CrashReporter.getLaunchIntent())
|
||||
true
|
||||
}
|
||||
findPreference<Preference>("export_debug")?.setOnPreferenceClickListener {
|
||||
Toast.makeText(
|
||||
activity,
|
||||
getString(
|
||||
R.string.debug_export_information_file,
|
||||
DebugInformationDumper().dump(requireContext())
|
||||
),
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
true
|
||||
}
|
||||
findPreference<Preference>("export_databases")?.setOnPreferenceClickListener {
|
||||
MaterialDialog(requireContext()).show {
|
||||
message(res = R.string.debug_export_databases_warning)
|
||||
positiveButton(res = R.string.dialog_continue, click = {
|
||||
Toast.makeText(
|
||||
activity,
|
||||
getString(
|
||||
R.string.debug_export_information_file,
|
||||
DebugInformationDumper().exportDatabases(requireContext())
|
||||
),
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
it.dismiss()
|
||||
})
|
||||
negativeButton(res = android.R.string.cancel, click = {
|
||||
it.cancel()
|
||||
})
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
findPreference<Preference>("license")?.setOnPreferenceClickListener {
|
||||
parentFragmentManager.beginTransaction()
|
||||
.setCustomAnimations(
|
||||
R.anim.preference_fragment_child_enter,
|
||||
R.anim.preference_fragment_parent_exit,
|
||||
R.anim.preference_fragment_parent_enter,
|
||||
R.anim.preference_fragment_child_exit
|
||||
)
|
||||
.replace(android.R.id.content,
|
||||
PreferencesLicenseFragment().apply { library = R.array.license_mm20launcher2 })
|
||||
.addToBackStack(null)
|
||||
.commit()
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
(activity as AppCompatActivity).supportActionBar?.setTitle(R.string.preference_screen_about)
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
private val LICENSES = intArrayOf(
|
||||
R.array.license_accompanist,
|
||||
R.array.license_android_jetpack,
|
||||
R.array.license_suncalc,
|
||||
R.array.license_crashreporter,
|
||||
R.array.license_draglinearlayout,
|
||||
R.array.license_glide,
|
||||
R.array.license_glide_transformations,
|
||||
R.array.license_google_apiclient,
|
||||
R.array.license_google_auth,
|
||||
R.array.license_groupie,
|
||||
R.array.license_gson,
|
||||
R.array.license_jsoup,
|
||||
R.array.license_kotlin_stdlib,
|
||||
R.array.license_lottie,
|
||||
R.array.license_mdicons,
|
||||
R.array.license_material_components,
|
||||
R.array.license_materialdialogs,
|
||||
R.array.license_msal,
|
||||
R.array.license_msgraph,
|
||||
R.array.license_mxparser,
|
||||
R.array.license_okhttp,
|
||||
R.array.license_retrofit,
|
||||
R.array.license_textdrawable,
|
||||
R.array.license_viewpropertyobjectanimator
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,182 @@
|
||||
package de.mm20.launcher2.fragment
|
||||
|
||||
import android.Manifest
|
||||
import android.app.WallpaperManager
|
||||
import android.content.Intent
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.TextView
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.appcompat.app.AppCompatDelegate
|
||||
import androidx.core.app.ActivityCompat
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.preference.ListPreference
|
||||
import androidx.preference.Preference
|
||||
import androidx.preference.PreferenceCategory
|
||||
import androidx.preference.PreferenceFragmentCompat
|
||||
import com.afollestad.materialdialogs.MaterialDialog
|
||||
import com.afollestad.materialdialogs.customview.customView
|
||||
import de.mm20.launcher2.R
|
||||
import de.mm20.launcher2.icons.IconPackManager
|
||||
import de.mm20.launcher2.icons.IconRepository
|
||||
import de.mm20.launcher2.icons.LauncherIcon
|
||||
import de.mm20.launcher2.ktx.checkPermission
|
||||
import de.mm20.launcher2.preferences.IconShape
|
||||
import de.mm20.launcher2.preferences.LauncherPreferences
|
||||
import de.mm20.launcher2.preferences.Themes
|
||||
import de.mm20.launcher2.ui.legacy.view.LauncherIconView
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class PreferencesAppearanceFragment : PreferenceFragmentCompat() {
|
||||
|
||||
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||
addPreferencesFromResource(R.xml.preferences_appearance)
|
||||
findPreference<Preference>("theme")?.setOnPreferenceChangeListener { _, newValue ->
|
||||
val theme = Themes.byValue(newValue as String)
|
||||
@Suppress("DEPRECATION") // Still using MODE_NIGHT_AUTO
|
||||
AppCompatDelegate.setDefaultNightMode(when (theme) {
|
||||
Themes.LIGHT -> AppCompatDelegate.MODE_NIGHT_NO
|
||||
Themes.DARK -> AppCompatDelegate.MODE_NIGHT_YES
|
||||
Themes.AUTO -> AppCompatDelegate.MODE_NIGHT_AUTO
|
||||
else -> AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM
|
||||
})
|
||||
requireActivity().recreate()
|
||||
true
|
||||
}
|
||||
if (WallpaperManager.getInstance(activity).wallpaperInfo != null) {
|
||||
findPreference<Preference>("blur_cards")?.apply {
|
||||
setSummary(R.string.preference_blur_cards_summary_lwp)
|
||||
isEnabled = false
|
||||
}
|
||||
}
|
||||
findPreference<Preference>("blur_cards")?.setOnPreferenceChangeListener { _, newValue ->
|
||||
val newVal = newValue as? Boolean ?: return@setOnPreferenceChangeListener true
|
||||
if (newVal && requireActivity().checkPermission(Manifest.permission.READ_EXTERNAL_STORAGE)) {
|
||||
ActivityCompat.requestPermissions(requireActivity(), arrayOf(Manifest.permission.READ_EXTERNAL_STORAGE), 0)
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
findPreference<Preference>("wallpaper")?.setOnPreferenceClickListener {
|
||||
requireContext().startActivity(Intent.createChooser(Intent(Intent.ACTION_SET_WALLPAPER), null))
|
||||
true
|
||||
}
|
||||
findPreference<Preference>("cards")?.setOnPreferenceClickListener {
|
||||
requireFragmentManager().beginTransaction()
|
||||
.setCustomAnimations(R.anim.preference_fragment_child_enter, R.anim.preference_fragment_parent_exit,
|
||||
R.anim.preference_fragment_parent_enter, R.anim.preference_fragment_child_exit)
|
||||
.replace(android.R.id.content, PreferencesCardFragment())
|
||||
.addToBackStack(null)
|
||||
.commit()
|
||||
true
|
||||
}
|
||||
|
||||
val manager = IconPackManager.getInstance(requireContext())
|
||||
lifecycleScope.launch {
|
||||
val packs = manager.getInstalledIconPacks()
|
||||
findPreference<ListPreference>("icon_pack")?.apply {
|
||||
entries = packs.map { it.name }.toMutableList().apply { add(0, "System") }.toTypedArray()
|
||||
entryValues = (-1 until packs.size).map { it.toString() }.toTypedArray()
|
||||
if (packs.isEmpty()) {
|
||||
isEnabled = false
|
||||
setSummary(R.string.preference_icon_pack_summary_empty)
|
||||
} else {
|
||||
isEnabled = true
|
||||
summary = "%s"
|
||||
value = packs.indexOfFirst { it.packageName == manager.selectedIconPack }.toString()
|
||||
}
|
||||
setOnPreferenceChangeListener { _, newValue ->
|
||||
val index = (newValue as String).toInt()
|
||||
IconRepository.getInstance(requireContext()).clearCache()
|
||||
if (index == -1) manager.selectIconPack("")
|
||||
else {
|
||||
manager.selectIconPack(packs[index].packageName)
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
findPreference<Preference>("legacy_icon_bg")?.setOnPreferenceChangeListener { _, _ ->
|
||||
IconRepository.getInstance(requireContext()).clearCache()
|
||||
true
|
||||
}
|
||||
|
||||
val shapePreference = findPreference<Preference>("icon_shape")!!
|
||||
shapePreference.summary = getShapeName()
|
||||
shapePreference.setOnPreferenceClickListener {
|
||||
val launcherIcon = LauncherIcon(
|
||||
foreground = requireContext().getDrawable(R.mipmap.ic_launcher_foreground)!!,
|
||||
background = requireContext().getDrawable(R.mipmap.ic_launcher_background)
|
||||
)
|
||||
val iconShapeList = LinearLayout(requireContext())
|
||||
iconShapeList.orientation = LinearLayout.VERTICAL
|
||||
val shapes = arrayOf(
|
||||
IconShape.PLATFORM_DEFAULT to R.string.preference_icon_shape_platform,
|
||||
IconShape.CIRCLE to R.string.preference_icon_shape_circle,
|
||||
IconShape.ROUNDED_SQUARE to R.string.preference_icon_shape_rounded_square,
|
||||
IconShape.SQUARE to R.string.preference_icon_shape_square,
|
||||
IconShape.SQUIRCLE to R.string.preference_icon_shape_squircle,
|
||||
IconShape.HEXAGON to R.string.preference_icon_shape_hexagon,
|
||||
IconShape.TRIANGLE to R.string.preference_icon_shape_triangle,
|
||||
IconShape.PENTAGON to R.string.preference_icon_shape_pentagon
|
||||
)
|
||||
val layoutParams = LinearLayout.LayoutParams(
|
||||
LinearLayout.LayoutParams.MATCH_PARENT,
|
||||
LinearLayout.LayoutParams.WRAP_CONTENT)
|
||||
val dialog = MaterialDialog(requireContext())
|
||||
shapes.forEachIndexed { i, shape ->
|
||||
val view = View.inflate(requireContext(), R.layout.preference_icon_shape_row, null)
|
||||
view.findViewById<LauncherIconView>(R.id.icon).also { iconView ->
|
||||
iconView.icon = launcherIcon
|
||||
iconView.shape = shape.first
|
||||
}
|
||||
view.findViewById<TextView>(R.id.label).also { labelView ->
|
||||
labelView.setText(shape.second)
|
||||
}
|
||||
view.layoutParams = layoutParams
|
||||
iconShapeList.addView(view)
|
||||
view.setOnClickListener {
|
||||
LauncherPreferences.instance.iconShape = shape.first
|
||||
shapePreference.summary = getShapeName()
|
||||
dialog.dismiss()
|
||||
}
|
||||
}
|
||||
|
||||
dialog.customView(view = iconShapeList, scrollable = true)
|
||||
.title(R.string.preference_icon_shape)
|
||||
.negativeButton(android.R.string.cancel) {
|
||||
dialog.cancel()
|
||||
}
|
||||
.show()
|
||||
true
|
||||
}
|
||||
|
||||
val systemBarsCategory = findPreference<PreferenceCategory>("system_bars")!!
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
|
||||
systemBarsCategory.removePreference(findPreference("light_nav_bar"))
|
||||
}
|
||||
}
|
||||
|
||||
private fun getShapeName(): String {
|
||||
return requireContext().getString(when (LauncherIconView.getDefaultShape(requireContext())) {
|
||||
IconShape.TRIANGLE -> R.string.preference_icon_shape_triangle
|
||||
IconShape.HEXAGON -> R.string.preference_icon_shape_hexagon
|
||||
IconShape.ROUNDED_SQUARE -> R.string.preference_icon_shape_rounded_square
|
||||
IconShape.SQUIRCLE -> R.string.preference_icon_shape_squircle
|
||||
IconShape.SQUARE -> R.string.preference_icon_shape_square
|
||||
IconShape.PENTAGON -> R.string.preference_icon_shape_pentagon
|
||||
IconShape.PLATFORM_DEFAULT -> R.string.preference_icon_shape_platform
|
||||
else -> R.string.preference_icon_shape_circle
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
(activity as AppCompatActivity).supportActionBar
|
||||
?.setTitle(R.string.preference_screen_appearance)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package de.mm20.launcher2.fragment
|
||||
|
||||
import android.os.Bundle
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.preference.Preference
|
||||
import androidx.preference.PreferenceFragmentCompat
|
||||
import de.mm20.launcher2.R
|
||||
import de.mm20.launcher2.badges.BadgeProvider
|
||||
import de.mm20.launcher2.notifications.NotificationService
|
||||
|
||||
class PreferencesBadgesFragment : PreferenceFragmentCompat() {
|
||||
|
||||
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||
addPreferencesFromResource(R.xml.preferences_badges)
|
||||
findPreference<Preference>("notification_badges")?.setOnPreferenceChangeListener { _, newValue ->
|
||||
if (newValue as Boolean) {
|
||||
de.mm20.launcher2.notifications.NotificationService.getInstance()?.generateBadges()
|
||||
} else {
|
||||
BadgeProvider.getInstance(requireContext()).removeNotificationBadges()
|
||||
}
|
||||
true
|
||||
}
|
||||
findPreference<Preference>("suspended_badges")?.setOnPreferenceChangeListener { _, newValue ->
|
||||
if (newValue as Boolean) {
|
||||
BadgeProvider.getInstance(requireContext()).addSuspendBadges()
|
||||
} else {
|
||||
BadgeProvider.getInstance(requireContext()).removeSuspendBadges()
|
||||
}
|
||||
true
|
||||
}
|
||||
findPreference<Preference>("cloud_badges")?.setOnPreferenceChangeListener { _, newValue ->
|
||||
if (newValue as Boolean) {
|
||||
BadgeProvider.getInstance(requireContext()).addCloudBadges()
|
||||
} else {
|
||||
BadgeProvider.getInstance(requireContext()).removeCloudBadges()
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
(activity as AppCompatActivity).supportActionBar
|
||||
?.setTitle(R.string.preference_screen_badges)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
package de.mm20.launcher2.fragment
|
||||
|
||||
import android.Manifest
|
||||
import android.content.res.ColorStateList
|
||||
import android.os.Bundle
|
||||
import android.widget.CheckBox
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.ScrollView
|
||||
import android.widget.TextView
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.app.ActivityCompat
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.view.setPadding
|
||||
import androidx.preference.Preference
|
||||
import androidx.preference.PreferenceFragmentCompat
|
||||
import com.afollestad.materialdialogs.MaterialDialog
|
||||
import com.afollestad.materialdialogs.bottomsheets.BottomSheet
|
||||
import com.afollestad.materialdialogs.customview.customView
|
||||
import de.mm20.launcher2.R
|
||||
import de.mm20.launcher2.ktx.checkPermission
|
||||
import de.mm20.launcher2.ktx.dp
|
||||
import de.mm20.launcher2.preferences.LauncherPreferences
|
||||
import de.mm20.launcher2.search.data.CalendarEvent
|
||||
import de.mm20.launcher2.search.data.UserCalendar
|
||||
|
||||
class PreferencesCalendarFragment : PreferenceFragmentCompat() {
|
||||
|
||||
private var hasCalendarPermission = false
|
||||
private val calendars = mutableListOf<UserCalendar>()
|
||||
|
||||
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||
addPreferencesFromResource(R.xml.preferences_calendar)
|
||||
init()
|
||||
}
|
||||
|
||||
|
||||
fun init(requestPermission: Boolean = true) {
|
||||
val context = context ?: return
|
||||
hasCalendarPermission = context.checkPermission(Manifest.permission.READ_CALENDAR)
|
||||
if (hasCalendarPermission) {
|
||||
calendars.clear()
|
||||
calendars.addAll(CalendarEvent.getCalendars(context))
|
||||
val unselectedCalendars = LauncherPreferences.instance.unselectedCalendars.toMutableList()
|
||||
findPreference<Preference>("calendar_calendars")?.apply {
|
||||
var count = calendars.size - unselectedCalendars.size
|
||||
summary = resources.getQuantityString(R.plurals.preference_calendar_calendars_summary, count, count)
|
||||
isEnabled = true
|
||||
setOnPreferenceClickListener {
|
||||
val sheetView = LinearLayout(activity)
|
||||
sheetView.setPadding((8 * context.dp).toInt())
|
||||
sheetView.orientation = LinearLayout.VERTICAL
|
||||
sheetView.setBackgroundColor(ContextCompat.getColor(context, R.color.bottom_sheet))
|
||||
var owner = ""
|
||||
val padding = (8 * context.dp).toInt()
|
||||
for (c in calendars) {
|
||||
if (owner != c.owner) {
|
||||
owner = c.owner
|
||||
val text = TextView(activity)
|
||||
text.setTextColor(ContextCompat.getColor(context, R.color.text_color_secondary_normal))
|
||||
text.setPadding(padding, 2 * padding, padding, padding)
|
||||
text.text = owner
|
||||
sheetView.addView(text)
|
||||
}
|
||||
val checkbox = CheckBox(activity)
|
||||
checkbox.text = c.name
|
||||
checkbox.buttonTintList = ColorStateList.valueOf(CalendarEvent.getDisplayColor(context, c.color))
|
||||
checkbox.setPadding(padding)
|
||||
checkbox.isChecked = !unselectedCalendars.contains(c.id)
|
||||
checkbox.setOnCheckedChangeListener { _, checked ->
|
||||
if (checked) {
|
||||
unselectedCalendars.remove(c.id)
|
||||
} else {
|
||||
unselectedCalendars.add(c.id)
|
||||
}
|
||||
LauncherPreferences.instance.unselectedCalendars = unselectedCalendars
|
||||
count = calendars.size - unselectedCalendars.size
|
||||
summary = resources.getQuantityString(R.plurals.preference_calendar_calendars_summary, count, count)
|
||||
}
|
||||
sheetView.addView(checkbox)
|
||||
}
|
||||
val scrollView = ScrollView(context)
|
||||
scrollView.isNestedScrollingEnabled = true
|
||||
scrollView.addView(sheetView)
|
||||
MaterialDialog(context, BottomSheet()).show {
|
||||
customView(view = scrollView)
|
||||
title(R.string.preference_calendar_calendars)
|
||||
.negativeButton(R.string.close) {
|
||||
dismiss()
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (requestPermission) {
|
||||
ActivityCompat.requestPermissions(
|
||||
requireActivity(),
|
||||
arrayOf(Manifest.permission.READ_CALENDAR, Manifest.permission.WRITE_CALENDAR),
|
||||
0)
|
||||
}
|
||||
findPreference<Preference>("calendar_calendars")?.apply {
|
||||
isEnabled = false
|
||||
setSummary(R.string.preference_permission_denied)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
(activity as AppCompatActivity).supportActionBar?.setTitle(R.string.preference_screen_calendar)
|
||||
hasCalendarPermission = requireActivity().checkPermission(Manifest.permission.READ_CALENDAR)
|
||||
&& requireActivity().checkPermission(Manifest.permission.WRITE_CALENDAR)
|
||||
}
|
||||
|
||||
|
||||
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
|
||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
|
||||
init(false)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,199 @@
|
||||
package de.mm20.launcher2.fragment
|
||||
|
||||
import android.animation.Animator
|
||||
import android.animation.ObjectAnimator
|
||||
import android.app.WallpaperManager
|
||||
import android.graphics.*
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import android.view.ViewOutlineProvider
|
||||
import androidx.core.content.res.ResourcesCompat
|
||||
import androidx.core.view.doOnNextLayout
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.preference.Preference
|
||||
import androidx.preference.PreferenceFragmentCompat
|
||||
import de.mm20.launcher2.LauncherApplication
|
||||
import de.mm20.launcher2.R
|
||||
import de.mm20.launcher2.ktx.castTo
|
||||
import de.mm20.launcher2.ktx.dp
|
||||
import de.mm20.launcher2.ktx.translate
|
||||
import de.mm20.launcher2.preferences.CardBackground
|
||||
import de.mm20.launcher2.preferences.LauncherPreferences
|
||||
import de.mm20.launcher2.ui.legacy.helper.WallpaperBlur
|
||||
import kotlinx.android.synthetic.main.fragment_card_settings.*
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.io.File
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
class PreferencesCardFragment : Fragment(R.layout.fragment_card_settings) {
|
||||
|
||||
val preferences = LauncherPreferences.instance
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
previewCard.strokeOpacity = 0xFF
|
||||
previewCardBlur.clipToOutline = true
|
||||
|
||||
previewCardBlur.outlineProvider = object : ViewOutlineProvider() {
|
||||
override fun getOutline(view: View, outline: Outline?) {
|
||||
val radius = preferences.cardRadius
|
||||
outline?.setRoundRect(0, 0, view.width, view.height, radius * dp)
|
||||
}
|
||||
}
|
||||
|
||||
val context = requireContext()
|
||||
|
||||
|
||||
val previewCardBlur = previewCardBlur
|
||||
val previewCard = previewCard
|
||||
val prefFragment = PreferenesCardInnerFragment()
|
||||
|
||||
prefFragment.onPreferencesReady = {
|
||||
findPreference<Preference>("card_radius")?.let {
|
||||
it.summary = preferences.cardRadius.toString()
|
||||
it.setOnPreferenceChangeListener { pref, newValue ->
|
||||
val value = newValue as Int
|
||||
previewCard.radius = value * dp
|
||||
previewCardBlur.invalidateOutline()
|
||||
pref.summary = value.toString()
|
||||
true
|
||||
}
|
||||
}
|
||||
findPreference<Preference>("card_opacity")?.let {
|
||||
it.summary = preferences.cardOpacity.toString()
|
||||
it.setOnPreferenceChangeListener { pref, newValue ->
|
||||
val value = newValue as Int
|
||||
previewCard.backgroundOpacity = value
|
||||
previewCard.cardElevation = if (value == 0xFF) resources.getDimension(R.dimen.card_elevation) else 0f
|
||||
pref.summary = value.toString()
|
||||
true
|
||||
}
|
||||
}
|
||||
findPreference<Preference>("card_stroke_width")?.let {
|
||||
it.summary = preferences.cardRadius.toString()
|
||||
it.setOnPreferenceChangeListener { pref, newValue ->
|
||||
val value = newValue as Int
|
||||
previewCard.strokeWidth = (value * dp).roundToInt()
|
||||
pref.summary = value.toString()
|
||||
true
|
||||
}
|
||||
}
|
||||
findPreference<Preference>("blur_cards")?.let {
|
||||
if (WallpaperManager.getInstance(requireContext()).wallpaperInfo != null) {
|
||||
it.isEnabled = false
|
||||
it.setSummary(R.string.preference_blur_cards_summary_lwp)
|
||||
previewCardBlur.visibility = View.INVISIBLE
|
||||
} else {
|
||||
previewCardBlur.visibility = if (preferences.blurCards) {
|
||||
View.VISIBLE
|
||||
} else {
|
||||
View.INVISIBLE
|
||||
}
|
||||
it.setOnPreferenceChangeListener { pref, newValue ->
|
||||
previewCardBlur.visibility = if (newValue as Boolean) {
|
||||
View.VISIBLE
|
||||
} else {
|
||||
View.INVISIBLE
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
findPreference<Preference>("card_background")?.let {
|
||||
it.setOnPreferenceChangeListener { preference, newValue ->
|
||||
val background = CardBackground.byValue(newValue as String)
|
||||
var color = when (background) {
|
||||
CardBackground.BLACK -> context.getColor(R.color.cardview_background_black)
|
||||
else -> context.getColor(R.color.cardview_background)
|
||||
}
|
||||
color = color and ((previewCard.backgroundOpacity shl 24) or 0xFFFFFF)
|
||||
previewCard.setCardBackgroundColor(color)
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
childFragmentManager.beginTransaction()
|
||||
.replace(R.id.preferencesView, prefFragment)
|
||||
.commit()
|
||||
|
||||
|
||||
}
|
||||
|
||||
private var blurBitmap: Bitmap? = null
|
||||
|
||||
private var animator: Animator? = null
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
val content = activity?.findViewById<View>(android.R.id.content) ?: return
|
||||
animator = ObjectAnimator.ofArgb(content, "backgroundColor", ResourcesCompat.getColor(resources, R.color.settings_window_background, null), Color.TRANSPARENT)
|
||||
.apply {
|
||||
duration = 200
|
||||
startDelay = resources.getInteger(android.R.integer.config_shortAnimTime).toLong()
|
||||
start()
|
||||
}
|
||||
|
||||
if (preferences.blurCards && preferences.cardOpacity < 0xFF) {
|
||||
lifecycleScope.launch {
|
||||
val wallpaper = withContext(Dispatchers.IO) {
|
||||
WallpaperBlur.getCachedBitmap(requireContext())
|
||||
}
|
||||
LauncherApplication.instance.blurredWallpaper = wallpaper
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
content.doOnNextLayout {
|
||||
WallpaperManager.getInstance(requireContext()).setWallpaperOffsets(it.windowToken, 0.5f, 0.5f)
|
||||
}
|
||||
|
||||
val activity = requireActivity()
|
||||
|
||||
lifecycleScope.launch {
|
||||
val viewPosition = intArrayOf(0, 0)
|
||||
val rect = Rect(0, 0, previewCardBlur.width, previewCardBlur.height)
|
||||
val screen = Point()
|
||||
activity.windowManager.defaultDisplay.getRealSize(screen)
|
||||
previewCardBlur.getLocationOnScreen(viewPosition)
|
||||
val file = File(requireContext().cacheDir, "wallpaper")
|
||||
if (!file.exists()) return@launch
|
||||
blurBitmap = withContext(Dispatchers.IO) {
|
||||
val wallpaperWidth: Int
|
||||
val wallpaperHeight: Int
|
||||
val decoder = BitmapRegionDecoder
|
||||
.newInstance(file.absolutePath, false)
|
||||
wallpaperHeight = decoder.height
|
||||
wallpaperWidth = decoder.width
|
||||
|
||||
if (wallpaperWidth >= screen.x && wallpaperHeight >= screen.y) {
|
||||
val translateX = (wallpaperWidth - previewCardBlur.width) / 2f
|
||||
val translateY = (wallpaperHeight - screen.y) / 2f + viewPosition[1]
|
||||
rect.translate(translateX.roundToInt(),
|
||||
translateY.roundToInt())
|
||||
}
|
||||
|
||||
decoder.decodeRegion(rect, null)
|
||||
}
|
||||
previewCardBlur.setImageBitmap(blurBitmap)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onStop() {
|
||||
super.onStop()
|
||||
if (animator?.isRunning == true) animator?.end()
|
||||
activity?.findViewById<View>(android.R.id.content)?.setBackgroundColor(ResourcesCompat.getColor(resources, R.color.settings_window_background, null))
|
||||
}
|
||||
}
|
||||
|
||||
class PreferenesCardInnerFragment : PreferenceFragmentCompat() {
|
||||
var onPreferencesReady: (PreferenesCardInnerFragment.() -> Unit)? = null
|
||||
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||
addPreferencesFromResource(R.xml.preferences_cards)
|
||||
onPreferencesReady?.invoke(this)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package de.mm20.launcher2.fragment
|
||||
|
||||
import android.animation.ObjectAnimator
|
||||
import android.animation.ValueAnimator
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.FrameLayout
|
||||
import android.widget.Toast
|
||||
import android.widget.ToggleButton
|
||||
import androidx.fragment.app.Fragment
|
||||
import de.mm20.launcher2.R
|
||||
import de.mm20.launcher2.preferences.LauncherPreferences
|
||||
|
||||
class PreferencesEasterEggFragment : Fragment() {
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||
val view = inflater.inflate(R.layout.fragment_easteregg, null, false)
|
||||
val root = view.findViewById<FrameLayout>(R.id.easterEggRoot)
|
||||
val toggle = view.findViewById<ToggleButton>(R.id.magicModeToggle)
|
||||
toggle.isChecked = LauncherPreferences.instance.easterEggEnabled
|
||||
toggle.setOnCheckedChangeListener { _, isChecked ->
|
||||
LauncherPreferences.instance.easterEggEnabled = isChecked
|
||||
Toast.makeText(requireContext(), if (isChecked) R.string.easter_egg_activated else R.string.easter_egg_deactivated, Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
return view
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
package de.mm20.launcher2.fragment
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
import androidx.browser.customtabs.CustomTabColorSchemeParams
|
||||
import androidx.browser.customtabs.CustomTabsIntent
|
||||
import androidx.fragment.app.Fragment
|
||||
import com.bumptech.glide.Glide
|
||||
import de.mm20.launcher2.R
|
||||
|
||||
class PreferencesLicenseFragment : Fragment() {
|
||||
var library: Int = 0
|
||||
|
||||
@SuppressLint("ResourceType")
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
|
||||
savedInstanceState: Bundle?): View? {
|
||||
val view = inflater.inflate(R.layout.fragment_license, null, false)
|
||||
val license = resources.obtainTypedArray(library)
|
||||
(activity as AppCompatActivity).supportActionBar?.title = license.getString(0)
|
||||
val icon = view.findViewById<ImageView>(R.id.icon)
|
||||
val iconUri = license.getString(2)
|
||||
if (iconUri == null) icon.visibility = View.GONE
|
||||
else {
|
||||
Glide
|
||||
.with(icon)
|
||||
.load(iconUri)
|
||||
.into(icon)
|
||||
icon.visibility = View.VISIBLE
|
||||
}
|
||||
val url = license.getString(6)
|
||||
val website = view.findViewById<TextView>(R.id.website)
|
||||
website.setOnClickListener {
|
||||
val intent = CustomTabsIntent.Builder()
|
||||
.setDefaultColorSchemeParams(CustomTabColorSchemeParams
|
||||
.Builder()
|
||||
.setToolbarColor(-0x9f8275)
|
||||
.build())
|
||||
.setShowTitle(true)
|
||||
.build()
|
||||
intent.launchUrl(activity as AppCompatActivity, Uri.parse(url))
|
||||
}
|
||||
val description = view.findViewById<TextView>(R.id.description)
|
||||
description.text = license.getString(1)
|
||||
val licenseTitle = view.findViewById<TextView>(R.id.licenseTitle)
|
||||
val licenseText = view.findViewById<TextView>(R.id.licenseText)
|
||||
val licenseCopyright = view.findViewById<TextView>(R.id.licenseCopyright)
|
||||
licenseTitle.text = license.getString(3)
|
||||
licenseCopyright.text = license.getString(4)
|
||||
licenseText.text = resources.openRawResource(license.getResourceId(5, 0)).reader().readText()
|
||||
license.recycle()
|
||||
return view
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
package de.mm20.launcher2.fragment
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Color
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.preference.Preference
|
||||
import androidx.preference.PreferenceFragmentCompat
|
||||
import de.mm20.launcher2.R
|
||||
|
||||
class PreferencesMainFragment : PreferenceFragmentCompat() {
|
||||
|
||||
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||
addPreferencesFromResource(R.xml.preferences_main)
|
||||
findPreference<Preference>("screen_appearance")?.setOnPreferenceClickListener {
|
||||
setSettingsScreen(PreferencesAppearanceFragment())
|
||||
true
|
||||
}
|
||||
findPreference<Preference>("screen_about")?.setOnPreferenceClickListener {
|
||||
setSettingsScreen(PreferencesAboutFragment())
|
||||
true
|
||||
}
|
||||
findPreference<Preference>("screen_weather")?.setOnPreferenceClickListener {
|
||||
setSettingsScreen(PreferencesWeatherFragment())
|
||||
true
|
||||
}
|
||||
findPreference<Preference>("screen_services")?.setOnPreferenceClickListener {
|
||||
setSettingsScreen(PreferencesServicesFragment())
|
||||
true
|
||||
}
|
||||
findPreference<Preference>("screen_search")?.setOnPreferenceClickListener {
|
||||
setSettingsScreen(PreferencesSearchFragment())
|
||||
true
|
||||
}
|
||||
findPreference<Preference>("screen_calendar")?.setOnPreferenceClickListener {
|
||||
setSettingsScreen(PreferencesCalendarFragment())
|
||||
true
|
||||
}
|
||||
findPreference<Preference>("screen_badges")?.setOnPreferenceClickListener {
|
||||
setSettingsScreen(PreferencesBadgesFragment())
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
private fun setSettingsScreen(fragment: Fragment) {
|
||||
parentFragmentManager.beginTransaction()
|
||||
.setCustomAnimations(R.anim.preference_fragment_child_enter, R.anim.preference_fragment_parent_exit,
|
||||
R.anim.preference_fragment_parent_enter, R.anim.preference_fragment_child_exit)
|
||||
.replace(android.R.id.content, fragment)
|
||||
.addToBackStack(null)
|
||||
.commit()
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
(activity as AppCompatActivity).supportActionBar?.setTitle(R.string.title_activity_settings)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,176 @@
|
||||
package de.mm20.launcher2.fragment
|
||||
|
||||
import android.Manifest
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.app.ActivityCompat
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.lifecycle.repeatOnLifecycle
|
||||
import androidx.preference.Preference
|
||||
import androidx.preference.PreferenceFragmentCompat
|
||||
import de.mm20.launcher2.R
|
||||
import de.mm20.launcher2.gservices.GoogleApiHelper
|
||||
import de.mm20.launcher2.ktx.checkPermission
|
||||
import de.mm20.launcher2.msservices.MicrosoftGraphApiHelper
|
||||
import de.mm20.launcher2.nextcloud.NextcloudApiHelper
|
||||
import de.mm20.launcher2.owncloud.OwncloudClient
|
||||
import de.mm20.launcher2.preferences.LauncherPreferences
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class PreferencesSearchFragment : PreferenceFragmentCompat() {
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
lifecycleScope.launch {
|
||||
viewLifecycleOwner.lifecycle.repeatOnLifecycle(Lifecycle.State.RESUMED) {
|
||||
updateGoogleDrive()
|
||||
updateOneDrive()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||
addPreferencesFromResource(R.xml.preferences_search)
|
||||
findPreference<Preference>("search_activities")?.summary =
|
||||
getString(
|
||||
R.string.preference_search_activities_summary,
|
||||
requireActivity().componentName.flattenToShortString()
|
||||
)
|
||||
findPreference<Preference>("search_files")?.setOnPreferenceChangeListener { _, newValue ->
|
||||
if (newValue == true &&
|
||||
requireContext().checkPermission(Manifest.permission.READ_EXTERNAL_STORAGE)
|
||||
) {
|
||||
ActivityCompat.requestPermissions(
|
||||
requireActivity(),
|
||||
arrayOf(Manifest.permission.READ_EXTERNAL_STORAGE),
|
||||
0
|
||||
)
|
||||
}
|
||||
true
|
||||
}
|
||||
findPreference<Preference>("search_edit_websearch")?.setOnPreferenceClickListener {
|
||||
setSettingsScreen(PreferencesWebSearchesFragment())
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun updateGoogleDrive() {
|
||||
val googleApiHelper = GoogleApiHelper.getInstance(context ?: return)
|
||||
val account = googleApiHelper.getAccount()
|
||||
val pref = findPreference<Preference>("search_gdrive")!!
|
||||
if (account == null) {
|
||||
pref.apply {
|
||||
setSummary(R.string.preference_summary_not_logged_in)
|
||||
}
|
||||
} else {
|
||||
pref.apply {
|
||||
summary = context.getString(R.string.preference_search_gdrive_summary, account.name)
|
||||
}
|
||||
}
|
||||
val isSignedIn = account != null
|
||||
pref.setOnPreferenceChangeListener { _, value ->
|
||||
val newVal = value as Boolean
|
||||
if (newVal && !isSignedIn) {
|
||||
googleLogin()
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun updateOneDrive() {
|
||||
val oneDrivePref = findPreference<Preference>("search_onedrive")!!
|
||||
val user = MicrosoftGraphApiHelper.getInstance(requireContext()).getUser()
|
||||
if (user == null) {
|
||||
oneDrivePref.setSummary(R.string.preference_summary_not_logged_in)
|
||||
oneDrivePref.setOnPreferenceChangeListener { _, value ->
|
||||
if (value as Boolean) {
|
||||
lifecycleScope.launch launch2@{
|
||||
MicrosoftGraphApiHelper.getInstance(requireContext())
|
||||
.login(requireActivity())
|
||||
updateOneDrive()
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
} else {
|
||||
oneDrivePref.summary =
|
||||
context?.getString(R.string.preference_search_onedrive_summary, user.name)
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateNextcloud() {
|
||||
val nextcloudPref = findPreference<Preference>("search_nextcloud")!!
|
||||
val client = NextcloudApiHelper(context ?: return)
|
||||
lifecycleScope.launch {
|
||||
val user = client.getLoggedInUser()
|
||||
if (user == null) {
|
||||
nextcloudPref.setSummary(R.string.preference_summary_not_logged_in)
|
||||
LauncherPreferences.instance.searchNextcloud = false
|
||||
nextcloudPref.setOnPreferenceChangeListener { _, value ->
|
||||
if (value as Boolean) {
|
||||
lifecycleScope.launch launch2@{
|
||||
updateNextcloud()
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
} else {
|
||||
nextcloudPref.summary = context?.getString(
|
||||
R.string.preference_search_nextcloud_summary,
|
||||
user.displayName
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateOwncloud() {
|
||||
val owncloudPref = findPreference<Preference>("search_owncloud")!!
|
||||
lifecycleScope.launch {
|
||||
val client = OwncloudClient(context ?: return@launch)
|
||||
val user = client.getLoggedInUser()
|
||||
if (user == null) {
|
||||
owncloudPref.setSummary(R.string.preference_summary_not_logged_in)
|
||||
LauncherPreferences.instance.searchOwncloud = false
|
||||
owncloudPref.setOnPreferenceChangeListener { _, value ->
|
||||
if (value as Boolean) {
|
||||
lifecycleScope.launch launch2@{
|
||||
client.login(requireActivity(), 0)
|
||||
updateOwncloud()
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
} else {
|
||||
owncloudPref.summary = context?.getString(
|
||||
R.string.preference_search_nextcloud_summary,
|
||||
user.displayName,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun googleLogin() {
|
||||
GoogleApiHelper.getInstance(requireContext()).login(requireActivity())
|
||||
}
|
||||
|
||||
private fun setSettingsScreen(fragment: Fragment) {
|
||||
parentFragmentManager.beginTransaction()
|
||||
.setCustomAnimations(
|
||||
R.anim.preference_fragment_child_enter, R.anim.preference_fragment_parent_exit,
|
||||
R.anim.preference_fragment_parent_enter, R.anim.preference_fragment_child_exit
|
||||
)
|
||||
.replace(android.R.id.content, fragment)
|
||||
.addToBackStack(null)
|
||||
.commit()
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
(activity as AppCompatActivity).supportActionBar?.setTitle(R.string.preference_screen_search)
|
||||
updateNextcloud()
|
||||
updateOwncloud()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,174 @@
|
||||
package de.mm20.launcher2.fragment
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.lifecycle.repeatOnLifecycle
|
||||
import androidx.preference.Preference
|
||||
import androidx.preference.PreferenceFragmentCompat
|
||||
import de.mm20.launcher2.R
|
||||
import de.mm20.launcher2.gservices.GoogleApiHelper
|
||||
import de.mm20.launcher2.msservices.MicrosoftGraphApiHelper
|
||||
import de.mm20.launcher2.nextcloud.NextcloudApiHelper
|
||||
import de.mm20.launcher2.owncloud.OwncloudClient
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class PreferencesServicesFragment : PreferenceFragmentCompat() {
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
lifecycleScope.launch {
|
||||
viewLifecycleOwner.lifecycle.repeatOnLifecycle(Lifecycle.State.RESUMED) {
|
||||
updateGooglePreferences()
|
||||
updateMicrosoftPreferences()
|
||||
updateNextcloudPreferences()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||
addPreferencesFromResource(R.xml.preferences_services)
|
||||
}
|
||||
|
||||
private suspend fun updateGooglePreferences() {
|
||||
val pref = findPreference<Preference>("google_signin")!!
|
||||
val googleApiHelper = GoogleApiHelper.getInstance(requireContext())
|
||||
if (!googleApiHelper.isAvailable()) {
|
||||
pref.isEnabled = false
|
||||
pref.summary = context?.getString(R.string.feature_not_available, context?.getString(R.string.app_name))
|
||||
return
|
||||
}
|
||||
val account = googleApiHelper.getAccount()
|
||||
if (account == null) {
|
||||
pref.apply {
|
||||
setTitle(R.string.preference_google_signin)
|
||||
setSummary(R.string.preference_google_signin_summary)
|
||||
setOnPreferenceClickListener {
|
||||
googleApiHelper.login(requireActivity())
|
||||
true
|
||||
}
|
||||
}
|
||||
} else {
|
||||
pref.apply {
|
||||
title = context.getString(R.string.preference_signin_logout)
|
||||
summary = context.getString(R.string.preference_signin_user, account.name)
|
||||
setOnPreferenceClickListener {
|
||||
googleApiHelper.logout()
|
||||
lifecycleScope.launch {
|
||||
updateGooglePreferences()
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun updateMicrosoftPreferences() {
|
||||
val pref = findPreference<Preference>("ms_signin")!!
|
||||
val msApiHelper = MicrosoftGraphApiHelper.getInstance(requireContext())
|
||||
if (!msApiHelper.isAvailable()) {
|
||||
pref.isEnabled = false
|
||||
pref.summary = context?.getString(R.string.feature_not_available, context?.getString(R.string.app_name))
|
||||
return
|
||||
}
|
||||
val user = MicrosoftGraphApiHelper.getInstance(requireContext()).getUser()
|
||||
if (user == null) {
|
||||
pref.setTitle(R.string.preference_ms_signin)
|
||||
pref.setSummary(R.string.preference_ms_signin_summary)
|
||||
pref.setOnPreferenceClickListener {
|
||||
lifecycleScope.launch {
|
||||
msApiHelper.login(requireActivity())
|
||||
updateMicrosoftPreferences()
|
||||
}
|
||||
true
|
||||
}
|
||||
} else {
|
||||
pref.setTitle(R.string.preference_signin_logout)
|
||||
pref.summary = context?.getString(R.string.preference_signin_user, user.name)
|
||||
pref.setOnPreferenceClickListener {
|
||||
lifecycleScope.launch {
|
||||
msApiHelper.logout()
|
||||
updateMicrosoftPreferences()
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun updateNextcloudPreferences() {
|
||||
val nextcloud = NextcloudApiHelper(requireContext())
|
||||
val user = nextcloud.getLoggedInUser()
|
||||
if (user == null) {
|
||||
findPreference<Preference>("nextcloud_signin")?.let {
|
||||
it.setOnPreferenceClickListener {
|
||||
nextcloud.login(requireActivity())
|
||||
true
|
||||
}
|
||||
it.setTitle(R.string.preference_nextcloud_signin)
|
||||
it.setSummary(R.string.preference_nextcloud_signin_summary)
|
||||
}
|
||||
} else {
|
||||
findPreference<Preference>("nextcloud_signin")?.let {
|
||||
it.setOnPreferenceClickListener {
|
||||
lifecycleScope.launch {
|
||||
nextcloud.logout()
|
||||
updateNextcloudPreferences()
|
||||
}
|
||||
true
|
||||
}
|
||||
it.setTitle(R.string.preference_signin_logout)
|
||||
it.summary = context?.getString(
|
||||
R.string.preference_signin_user_nextcloud,
|
||||
user.displayName
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateOwncloudPreferences() {
|
||||
val client = OwncloudClient(context ?: return)
|
||||
lifecycleScope.launch {
|
||||
val user = client.getLoggedInUser()
|
||||
if (user == null) {
|
||||
findPreference<Preference>("owncloud_signin")?.let {
|
||||
it.setOnPreferenceClickListener {
|
||||
OwncloudClient(requireContext()).login(
|
||||
requireActivity(),
|
||||
REQUEST_OWNCLOUD_LOGIN
|
||||
)
|
||||
true
|
||||
}
|
||||
it.setTitle(R.string.preference_owncloud_signin)
|
||||
it.setSummary(R.string.preference_owncloud_signin_summary)
|
||||
}
|
||||
} else {
|
||||
findPreference<Preference>("owncloud_signin")?.let {
|
||||
it.setOnPreferenceClickListener {
|
||||
OwncloudClient(requireContext()).logout()
|
||||
updateOwncloudPreferences()
|
||||
true
|
||||
}
|
||||
it.setTitle(R.string.preference_signin_logout)
|
||||
it.summary = context?.getString(
|
||||
R.string.preference_signin_user_nextcloud,
|
||||
user.displayName,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
(activity as AppCompatActivity).supportActionBar?.setTitle(R.string.preference_screen_services)
|
||||
updateOwncloudPreferences()
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val REQUEST_OWNCLOUD_LOGIN = 581
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,166 @@
|
||||
package de.mm20.launcher2.fragment
|
||||
|
||||
import android.os.Bundle
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.preference.Preference
|
||||
import androidx.preference.PreferenceFragmentCompat
|
||||
import androidx.preference.SwitchPreference
|
||||
import com.afollestad.materialdialogs.MaterialDialog
|
||||
import com.afollestad.materialdialogs.list.listItems
|
||||
import com.afollestad.materialdialogs.list.listItemsSingleChoice
|
||||
import de.mm20.launcher2.R
|
||||
import de.mm20.launcher2.preferences.LauncherPreferences
|
||||
import de.mm20.launcher2.preferences.WeatherProviders
|
||||
import de.mm20.launcher2.weather.WeatherProvider
|
||||
import de.mm20.launcher2.weather.WeatherViewModel
|
||||
import de.mm20.launcher2.weather.here.HereProvider
|
||||
import de.mm20.launcher2.weather.metno.MetNoProvider
|
||||
import de.mm20.launcher2.weather.openweathermap.OpenWeatherMapProvider
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
class PreferencesWeatherFragment : PreferenceFragmentCompat() {
|
||||
|
||||
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||
addPreferencesFromResource(R.xml.preferences_weather)
|
||||
findPreference<Preference>("location")?.setOnPreferenceChangeListener { _, newValue ->
|
||||
lifecycleScope.launch {
|
||||
val locations = withContext(Dispatchers.IO) {
|
||||
WeatherProvider.getInstance(requireContext())
|
||||
?.lookupLocation(newValue as String)
|
||||
} ?: return@launch
|
||||
onLookupCompleted(locations)
|
||||
}
|
||||
false
|
||||
}
|
||||
/*findPreference<Preference>("weather_provider")?.setOnPreferenceChangeListener { pref, newValue ->
|
||||
val newProvider = WeatherProviders.byValue(newValue as String)
|
||||
LauncherPreferences.instance.weatherProvider = newProvider
|
||||
WeatherProvider.getInstance(requireContext())?.resetLastUpdate()
|
||||
ViewModelProvider(this).get(WeatherViewModel::class.java).requestUpdate(requireContext())
|
||||
updateProviderPreferences()
|
||||
true
|
||||
}*/
|
||||
val providerPref = findPreference<Preference>("weather_provider")!!
|
||||
val context = requireContext()
|
||||
|
||||
val providers = mutableListOf<Pair<WeatherProviders, String>>()
|
||||
OpenWeatherMapProvider(context).takeIf { it.isAvailable() }?.let {
|
||||
providers.add(WeatherProviders.OPENWEATHERMAP to it.name)
|
||||
}
|
||||
HereProvider(context).takeIf { it.isAvailable() }?.let {
|
||||
providers.add(WeatherProviders.HERE to it.name)
|
||||
}
|
||||
MetNoProvider(context).takeIf { it.isAvailable() }?.let {
|
||||
providers.add(WeatherProviders.MET_NO to it.name)
|
||||
}
|
||||
|
||||
if (providers.isEmpty()) {
|
||||
providerPref.summary = context.getString(
|
||||
R.string.feature_not_available,
|
||||
context.getString(R.string.app_name)
|
||||
)
|
||||
providerPref.isEnabled = false
|
||||
} else {
|
||||
providerPref.setOnPreferenceClickListener {
|
||||
MaterialDialog(context).show {
|
||||
title(R.string.preference_weather_provider)
|
||||
listItemsSingleChoice(
|
||||
items = providers.map { it.second },
|
||||
initialSelection = providers.indexOfFirst { it.first == LauncherPreferences.instance.weatherProvider }
|
||||
) { dialog, index, text ->
|
||||
LauncherPreferences.instance.weatherProvider = providers[index].first
|
||||
WeatherProvider.getInstance(requireContext())?.resetLastUpdate()
|
||||
ViewModelProvider(this@PreferencesWeatherFragment)
|
||||
.get(WeatherViewModel::class.java)
|
||||
.requestUpdate(requireContext())
|
||||
updateProviderPreferences()
|
||||
dialog.dismiss()
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
findPreference<Preference>("auto_location")?.setOnPreferenceChangeListener { _, newValue ->
|
||||
val autoLocation = newValue as Boolean
|
||||
val provider = WeatherProvider.getInstance(requireContext())
|
||||
provider?.autoLocation = autoLocation
|
||||
provider?.resetLastUpdate()
|
||||
provider?.setLocation(null, "")
|
||||
ViewModelProvider(this).get(WeatherViewModel::class.java)
|
||||
.requestUpdate(requireContext())
|
||||
updateProviderPreferences()
|
||||
true
|
||||
}
|
||||
updateProviderPreferences()
|
||||
}
|
||||
|
||||
private fun updateProviderPreferences() {
|
||||
val provider = WeatherProvider.getInstance(requireContext())
|
||||
val autoLocationPref = findPreference<SwitchPreference>("auto_location")!!
|
||||
val locationPref = findPreference<Preference>("location")!!
|
||||
val unitsPref = findPreference<Preference>("imperial_units")!!
|
||||
val providerPref = findPreference<Preference>("weather_provider")!!
|
||||
|
||||
|
||||
|
||||
locationPref.parent?.isVisible = provider != null
|
||||
unitsPref.isVisible = provider != null
|
||||
|
||||
provider ?: return
|
||||
|
||||
providerPref.summary = provider.name
|
||||
|
||||
if (provider.supportsAutoLocation) {
|
||||
autoLocationPref.setSummary(R.string.preference_automatic_location_summary)
|
||||
autoLocationPref.isChecked = provider.autoLocation
|
||||
if (!provider.supportsManualLocation) {
|
||||
autoLocationPref.isEnabled = false
|
||||
autoLocationPref.isChecked = true
|
||||
locationPref.isEnabled = false
|
||||
locationPref.setSummary(R.string.preference_location_disabled_summary)
|
||||
} else {
|
||||
autoLocationPref.isEnabled = true
|
||||
autoLocationPref.isChecked = provider.autoLocation
|
||||
locationPref.isEnabled = true
|
||||
locationPref.summary = provider.getLastLocation()
|
||||
}
|
||||
} else {
|
||||
autoLocationPref.isEnabled = false
|
||||
autoLocationPref.setSummary(R.string.preference_automatic_location_disabled_summary)
|
||||
autoLocationPref.isChecked = false
|
||||
}
|
||||
}
|
||||
|
||||
private fun onLookupCompleted(results: List<Pair<Any?, String>>) {
|
||||
MaterialDialog(requireContext())
|
||||
.listItems(
|
||||
items = results.map { it.second },
|
||||
waitForPositiveButton = false
|
||||
) { dialog, index, _ ->
|
||||
val provider = WeatherProvider.getInstance(requireContext())
|
||||
?: return@listItems dialog.dismiss()
|
||||
provider.resetLastUpdate()
|
||||
provider.setLocation(results[index].first, results[index].second)
|
||||
findPreference<Preference>("location")?.summary = results[index].second
|
||||
ViewModelProvider(this).get(WeatherViewModel::class.java)
|
||||
.requestUpdate(requireContext())
|
||||
dialog.dismiss()
|
||||
}
|
||||
.negativeButton {
|
||||
it.cancel()
|
||||
}
|
||||
.show()
|
||||
}
|
||||
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
|
||||
(activity as AppCompatActivity).supportActionBar?.setTitle(R.string.preference_screen_weather)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,229 @@
|
||||
package de.mm20.launcher2.fragment
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.ActivityNotFoundException
|
||||
import android.content.Intent
|
||||
import android.content.res.ColorStateList
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.BitmapFactory
|
||||
import android.graphics.PorterDuff
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.graphics.drawable.GradientDrawable
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.widget.EditText
|
||||
import android.widget.ImageView
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.graphics.scale
|
||||
import androidx.lifecycle.Observer
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.preference.Preference
|
||||
import androidx.preference.PreferenceFragmentCompat
|
||||
import com.afollestad.materialdialogs.MaterialDialog
|
||||
import com.afollestad.materialdialogs.bottomsheets.BottomSheet
|
||||
import com.afollestad.materialdialogs.color.colorChooser
|
||||
import com.afollestad.materialdialogs.customview.customView
|
||||
import com.bumptech.glide.Glide
|
||||
import com.bumptech.glide.request.target.SimpleTarget
|
||||
import com.bumptech.glide.request.transition.Transition
|
||||
import de.mm20.launcher2.R
|
||||
import de.mm20.launcher2.ktx.dp
|
||||
import de.mm20.launcher2.search.SearchViewModel
|
||||
import de.mm20.launcher2.search.WebsearchViewModel
|
||||
import de.mm20.launcher2.search.data.Websearch
|
||||
import java.io.File
|
||||
import java.io.FileOutputStream
|
||||
import java.lang.ref.WeakReference
|
||||
|
||||
class PreferencesWebSearchesFragment : PreferenceFragmentCompat() {
|
||||
|
||||
private lateinit var rootView: View
|
||||
|
||||
private var sheetIcon: WeakReference<ImageView>? = null
|
||||
|
||||
private val viewModel by lazy {
|
||||
ViewModelProvider(context as AppCompatActivity)[WebsearchViewModel::class.java]
|
||||
}
|
||||
|
||||
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||
preferenceScreen = preferenceManager.createPreferenceScreen(activity)
|
||||
val searches = viewModel.allWebsearches
|
||||
searches.observe(context as AppCompatActivity, Observer {
|
||||
updatePreferenceScreen(it)
|
||||
})
|
||||
}
|
||||
|
||||
private fun updatePreferenceScreen(searches: List<Websearch>) {
|
||||
preferenceScreen.removeAll()
|
||||
|
||||
for (search in searches) {
|
||||
val pref = Preference(context)
|
||||
pref.title = search.label
|
||||
if (search.icon == null) {
|
||||
val drawable = resources.getDrawable(R.drawable.ic_search, requireActivity().theme).mutate()
|
||||
drawable.setTintMode(PorterDuff.Mode.SRC_ATOP)
|
||||
drawable.setTint(search.color)
|
||||
pref.icon = drawable
|
||||
} else {
|
||||
Glide.with(requireContext())
|
||||
.asDrawable()
|
||||
.load(search.icon)
|
||||
.into(object : SimpleTarget<Drawable>() {
|
||||
override fun onResourceReady(resource: Drawable, transition: Transition<in Drawable>?) {
|
||||
pref.icon = resource
|
||||
}
|
||||
})
|
||||
}
|
||||
pref.setOnPreferenceClickListener {
|
||||
editSearch(search)
|
||||
true
|
||||
}
|
||||
preferenceScreen.addPreference(pref)
|
||||
}
|
||||
|
||||
|
||||
val newPref = Preference(activity)
|
||||
newPref.setTitle(R.string.preference_websearch_new)
|
||||
newPref.setIcon(R.drawable.ic_preference_websearch_new)
|
||||
newPref.setOnPreferenceClickListener {
|
||||
editSearch(null)
|
||||
true
|
||||
}
|
||||
preferenceScreen.addPreference(newPref)
|
||||
}
|
||||
|
||||
private fun editSearch(search: Websearch?) {
|
||||
|
||||
val websearch = search ?: Websearch("", "", 0xFF555555.toInt(), null, null)
|
||||
|
||||
val dialogView = LayoutInflater.from(activity).inflate(R.layout.dialog_websearch, null)
|
||||
val nameEdit = dialogView.findViewById<EditText>(R.id.websearchName)
|
||||
nameEdit.setText(websearch.label)
|
||||
val urlEdit = dialogView.findViewById<EditText>(R.id.websearchUrl)
|
||||
urlEdit.setText(websearch.urlTemplate)
|
||||
val iconView = dialogView.findViewById<ImageView>(R.id.websearchIcon)
|
||||
iconView.apply {
|
||||
if (websearch.icon == null) {
|
||||
setImageResource(R.drawable.ic_search)
|
||||
imageTintList = ColorStateList.valueOf(websearch.color)
|
||||
} else {
|
||||
Glide.with(this)
|
||||
.load(websearch.icon)
|
||||
.into(this)
|
||||
}
|
||||
sheetIcon = WeakReference(this)
|
||||
}
|
||||
|
||||
val sheet = MaterialDialog(requireContext(), BottomSheet())
|
||||
.cornerRadius(8f)
|
||||
.customView(view = dialogView)
|
||||
|
||||
val radius = 8 * dialogView.dp
|
||||
dialogView.background = GradientDrawable().apply {
|
||||
cornerRadii = floatArrayOf(
|
||||
radius, radius, // top left
|
||||
radius, radius, // top right
|
||||
0f, 0f, // bottom left
|
||||
0f, 0f // bottom right
|
||||
)
|
||||
}
|
||||
|
||||
var newColor = websearch.color
|
||||
var newIcon: String? = websearch.icon
|
||||
|
||||
|
||||
sheet.noAutoDismiss()
|
||||
.positiveButton(android.R.string.ok) {
|
||||
val newUrl = urlEdit.text.toString()
|
||||
val newName = nameEdit.text.toString()
|
||||
if (!newUrl.contains("\${1}")) {
|
||||
urlEdit.error = getString(R.string.websearch_dialog_url_error)
|
||||
return@positiveButton
|
||||
}
|
||||
File(requireContext().cacheDir, "websearch-tmp").takeIf { it.exists() }?.let {
|
||||
websearch.icon?.let { File(it).takeIf { it.exists() }?.delete() }
|
||||
val newFile = File(requireContext().filesDir, "websearch-${System.currentTimeMillis()}")
|
||||
it.copyTo(newFile, true)
|
||||
it.delete()
|
||||
newIcon = newFile.absolutePath
|
||||
}
|
||||
if (newIcon == null) {
|
||||
websearch.icon?.let { File(it).takeIf { it.exists() }?.delete() }
|
||||
}
|
||||
websearch.urlTemplate = newUrl
|
||||
websearch.label = newName
|
||||
websearch.icon = newIcon
|
||||
websearch.color = newColor
|
||||
viewModel.insertWebsearch(websearch)
|
||||
sheet.dismiss()
|
||||
}
|
||||
|
||||
sheet.negativeButton(android.R.string.cancel) {
|
||||
sheet.cancel()
|
||||
}
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
sheet.neutralButton(R.string.menu_delete) {
|
||||
sheet.dismiss()
|
||||
websearch.icon?.let { File(it).takeIf { it.exists() }?.delete() }
|
||||
viewModel.deleteWebsearch(websearch)
|
||||
}
|
||||
|
||||
sheet.setOnCancelListener {
|
||||
File(requireContext().cacheDir, "websearch-tmp").takeIf { it.exists() }?.delete()
|
||||
}
|
||||
|
||||
dialogView.findViewById<View>(R.id.websearchIcon).setOnClickListener {
|
||||
MaterialDialog(requireContext()).show {
|
||||
@Suppress("DEPRECATION")
|
||||
neutralButton(R.string.custom_icon) {
|
||||
val intent = Intent(Intent.ACTION_GET_CONTENT)
|
||||
intent.type = "image/*"
|
||||
try {
|
||||
startActivityForResult(intent, 24)
|
||||
} catch (e: ActivityNotFoundException) {
|
||||
}
|
||||
dismiss()
|
||||
}
|
||||
title(R.string.websearch_dialog_choose_icon_color)
|
||||
colorChooser(
|
||||
colors = context.resources.getIntArray(R.array.color_chooser_presets),
|
||||
allowCustomArgb = true,
|
||||
showAlphaSelector = false
|
||||
) { _, color ->
|
||||
iconView.setImageResource(R.drawable.ic_search)
|
||||
iconView.imageTintList = ColorStateList.valueOf(color)
|
||||
newColor = color
|
||||
newIcon = null
|
||||
File(requireContext().cacheDir, "websearch-tmp").takeIf { it.exists() }?.delete()
|
||||
dismiss()
|
||||
}
|
||||
}
|
||||
}
|
||||
sheet.show()
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
(activity as AppCompatActivity).supportActionBar
|
||||
?.setTitle(R.string.preference_search_edit_websearch)
|
||||
}
|
||||
|
||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||
super.onActivityResult(requestCode, resultCode, data)
|
||||
val dataUri = data?.data
|
||||
if (requestCode == 24 && resultCode == Activity.RESULT_OK && dataUri != null) {
|
||||
val stream = requireActivity().contentResolver.openInputStream(dataUri)
|
||||
val icon = BitmapFactory.decodeStream(stream)
|
||||
val scaledIcon = icon.scale((32 * requireContext().dp).toInt(), (32 * requireContext().dp).toInt())
|
||||
val out = FileOutputStream(File(requireContext().cacheDir, "websearch-tmp"))
|
||||
scaledIcon.compress(Bitmap.CompressFormat.PNG, 100, out)
|
||||
out.close()
|
||||
sheetIcon?.get()?.apply {
|
||||
imageTintList = null
|
||||
setImageBitmap(scaledIcon)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package de.mm20.launcher2.helper
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.io.File
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
|
||||
class DebugInformationDumper {
|
||||
|
||||
fun dump(context: Context): String {
|
||||
val df = SimpleDateFormat("yyyy-MM-dd-HHmmss")
|
||||
val file = File(context.getExternalFilesDir(null), "kvaesitso-log-${df.format(Date(System.currentTimeMillis()))}")
|
||||
val fos = file.outputStream().writer()
|
||||
fos.write("Device: ${Build.DEVICE}\n")
|
||||
fos.write("SDK version: ${Build.VERSION.SDK_INT}\n")
|
||||
fos.write("====================================\n")
|
||||
Thread {
|
||||
val input = Runtime.getRuntime().exec("/system/bin/sh -c logcat").inputStream.bufferedReader()
|
||||
var line = input.readLine()
|
||||
while (line != null) {
|
||||
line = input.readLine()
|
||||
fos.write("$line\n")
|
||||
}
|
||||
fos.close()
|
||||
}.start()
|
||||
return file.absolutePath
|
||||
}
|
||||
|
||||
fun exportDatabases(context: Context): String {
|
||||
val df = SimpleDateFormat("yyyy-MM-dd-HHmmss")
|
||||
val exportFile = File(context.getExternalFilesDir(null), "room-${df.format(Date(System.currentTimeMillis()))}.db")
|
||||
GlobalScope.launch {
|
||||
withContext(Dispatchers.IO) {
|
||||
context.getDatabasePath("room").copyTo(exportFile)
|
||||
}
|
||||
}
|
||||
return exportFile.absolutePath
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
package de.mm20.launcher2.ui.preferences
|
||||
|
||||
import android.animation.Animator
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.TextView
|
||||
import androidx.core.view.postDelayed
|
||||
import androidx.preference.Preference
|
||||
import com.afollestad.materialdialogs.MaterialDialog
|
||||
import com.afollestad.materialdialogs.customview.customView
|
||||
import com.airbnb.lottie.LottieAnimationView
|
||||
import de.mm20.launcher2.R
|
||||
import de.mm20.launcher2.preferences.AppStartAnimation
|
||||
import de.mm20.launcher2.preferences.LauncherPreferences
|
||||
|
||||
class AppStartAnimPreference @JvmOverloads constructor(
|
||||
context: Context,
|
||||
attrs: AttributeSet? = null,
|
||||
defStyleAttr: Int = R.attr.preferenceStyle) : Preference(context, attrs, defStyleAttr) {
|
||||
|
||||
init {
|
||||
summary = getNameForAnimation(LauncherPreferences.instance.appStartAnim)
|
||||
|
||||
setOnPreferenceClickListener {
|
||||
val anims = mutableListOf(
|
||||
AppStartAnimation.M to R.raw.app_start_anim_m,
|
||||
AppStartAnimation.SLIDE_BOTTOM to R.raw.app_start_anim_slide_bottom,
|
||||
AppStartAnimation.FADE to R.raw.app_start_anim_fade
|
||||
)
|
||||
|
||||
val dialog = MaterialDialog(context)
|
||||
val layoutParams = ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)
|
||||
|
||||
val list = LinearLayout(context)
|
||||
|
||||
list.orientation = LinearLayout.VERTICAL
|
||||
|
||||
anims.forEachIndexed { _, anim ->
|
||||
val view = View.inflate(context, R.layout.preference_start_anim_item, null)
|
||||
view.findViewById<LottieAnimationView>(R.id.icon).also { iconView ->
|
||||
iconView.setAnimation(anim.second)
|
||||
iconView.addAnimatorListener(object : Animator.AnimatorListener {
|
||||
override fun onAnimationRepeat(animation: Animator?) {
|
||||
}
|
||||
|
||||
override fun onAnimationEnd(animation: Animator) {
|
||||
iconView.postDelayed(500) {
|
||||
iconView.frame = 0
|
||||
}
|
||||
iconView.postDelayed(1300) {
|
||||
iconView.playAnimation()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onAnimationCancel(animation: Animator?) {
|
||||
}
|
||||
|
||||
override fun onAnimationStart(animation: Animator?) {
|
||||
}
|
||||
|
||||
})
|
||||
iconView.postDelayed(300) {
|
||||
iconView.playAnimation()
|
||||
}
|
||||
}
|
||||
view.findViewById<TextView>(R.id.label).also { labelView ->
|
||||
labelView.setText(getNameForAnimation(anim.first))
|
||||
}
|
||||
view.layoutParams = layoutParams
|
||||
list.addView(view)
|
||||
view.setOnClickListener {
|
||||
LauncherPreferences.instance.appStartAnim = anim.first
|
||||
summary = getNameForAnimation(anim.first)
|
||||
dialog.dismiss()
|
||||
}
|
||||
}
|
||||
|
||||
dialog.customView(view = list, scrollable = true)
|
||||
.title(R.string.preference_app_start_animation)
|
||||
.negativeButton(android.R.string.cancel) {
|
||||
dialog.cancel()
|
||||
}
|
||||
.show()
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
private fun getNameForAnimation(anim: AppStartAnimation): String {
|
||||
return when (anim) {
|
||||
AppStartAnimation.FADE -> context.getString(R.string.preference_app_start_animation_fade)
|
||||
AppStartAnimation.SLIDE_BOTTOM -> context.getString(R.string.preference_app_start_animation_slide_bottom)
|
||||
AppStartAnimation.M -> context.getString(R.string.preference_app_start_animation_m)
|
||||
else -> context.getString(R.string.preference_app_start_animation_default)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
package de.mm20.launcher2.ui.view
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Bundle
|
||||
import android.util.AttributeSet
|
||||
import android.view.View
|
||||
import android.widget.FrameLayout
|
||||
import androidx.annotation.XmlRes
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.preference.Preference
|
||||
import androidx.preference.PreferenceFragmentCompat
|
||||
import de.mm20.launcher2.R
|
||||
import de.mm20.launcher2.ktx.castTo
|
||||
|
||||
class PreferencesView @JvmOverloads constructor(
|
||||
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
|
||||
) : FrameLayout(context, attrs, defStyleAttr) {
|
||||
|
||||
private val fragment = PreferenceViewFragment()
|
||||
|
||||
init {
|
||||
if (id == View.NO_ID) id = View.generateViewId()
|
||||
context.castTo<AppCompatActivity>().supportFragmentManager.beginTransaction()
|
||||
.add(id, fragment)
|
||||
.commit()
|
||||
attrs?.let {
|
||||
val ta = context.theme.obtainStyledAttributes(it, R.styleable.PreferencesView, 0, defStyleAttr)
|
||||
val preferenceScreen = ta.getResourceId(R.styleable.SearchGridView_columnCount, 0)
|
||||
setPreferenceResource(preferenceScreen)
|
||||
ta.recycle()
|
||||
}
|
||||
}
|
||||
|
||||
fun setPreferenceResource(@XmlRes resId: Int) {
|
||||
if (resId == 0) return
|
||||
fragment.setPreferenceResource(resId)
|
||||
}
|
||||
|
||||
fun <T : Preference> findPreference(key: String): T? {
|
||||
return fragment.findPreference<T>(key)
|
||||
}
|
||||
|
||||
var onPreferencesReady: (() -> Unit)? = null
|
||||
set(value) {
|
||||
field = value
|
||||
fragment.onPreferencesReady = value
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class PreferenceViewFragment : PreferenceFragmentCompat() {
|
||||
|
||||
private var isInitialized = false
|
||||
var onPreferencesReady: (() -> Unit)? = null
|
||||
|
||||
@XmlRes
|
||||
private var preferenceResource = 0
|
||||
|
||||
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||
if (preferenceResource != 0) addPreferencesFromResource(preferenceResource)
|
||||
isInitialized = true
|
||||
onPreferencesReady?.invoke()
|
||||
onPreferencesReady = null
|
||||
}
|
||||
|
||||
internal fun setPreferenceResource(@XmlRes resId: Int) {
|
||||
preferenceResource = resId
|
||||
if (isInitialized && resId != 0) {
|
||||
addPreferencesFromResource(resId)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user