Migrate icon shape preference

This commit is contained in:
MM20 2022-01-27 22:22:10 +01:00
parent 1370901f46
commit 75529b8f42
No known key found for this signature in database
GPG Key ID: 0B61A8F2DEAFA389
50 changed files with 467 additions and 163 deletions

View File

@ -8,7 +8,6 @@ import android.view.View
import android.widget.LinearLayout import android.widget.LinearLayout
import android.widget.TextView import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.app.AppCompatDelegate
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.preference.ListPreference import androidx.preference.ListPreference
import androidx.preference.Preference import androidx.preference.Preference
@ -88,7 +87,6 @@ class PreferencesAppearanceFragment : PreferenceFragmentCompat() {
} }
val shapePreference = findPreference<Preference>("icon_shape")!! val shapePreference = findPreference<Preference>("icon_shape")!!
shapePreference.summary = getShapeName()
shapePreference.setOnPreferenceClickListener { shapePreference.setOnPreferenceClickListener {
val launcherIcon = LauncherIcon( val launcherIcon = LauncherIcon(
foreground = requireContext().getDrawable(R.mipmap.ic_launcher_foreground)!!, foreground = requireContext().getDrawable(R.mipmap.ic_launcher_foreground)!!,
@ -110,23 +108,6 @@ class PreferencesAppearanceFragment : PreferenceFragmentCompat() {
LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.WRAP_CONTENT) LinearLayout.LayoutParams.WRAP_CONTENT)
val dialog = MaterialDialog(requireContext()) 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) dialog.customView(view = iconShapeList, scrollable = true)
.title(R.string.preference_icon_shape) .title(R.string.preference_icon_shape)
@ -143,19 +124,6 @@ class PreferencesAppearanceFragment : PreferenceFragmentCompat() {
} }
} }
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() { override fun onResume() {
super.onResume() super.onResume()

View File

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

View File

Before

Width:  |  Height:  |  Size: 5.5 KiB

After

Width:  |  Height:  |  Size: 5.5 KiB

View File

Before

Width:  |  Height:  |  Size: 4.8 KiB

After

Width:  |  Height:  |  Size: 4.8 KiB

View File

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

Before

Width:  |  Height:  |  Size: 3.2 KiB

After

Width:  |  Height:  |  Size: 3.2 KiB

View File

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

View File

Before

Width:  |  Height:  |  Size: 4.0 KiB

After

Width:  |  Height:  |  Size: 4.0 KiB

View File

Before

Width:  |  Height:  |  Size: 8.7 KiB

After

Width:  |  Height:  |  Size: 8.7 KiB

View File

Before

Width:  |  Height:  |  Size: 6.9 KiB

After

Width:  |  Height:  |  Size: 6.9 KiB

View File

Before

Width:  |  Height:  |  Size: 6.4 KiB

After

Width:  |  Height:  |  Size: 6.4 KiB

View File

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 17 KiB

View File

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

View File

Before

Width:  |  Height:  |  Size: 9.1 KiB

After

Width:  |  Height:  |  Size: 9.1 KiB

View File

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 26 KiB

View File

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 16 KiB

View File

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

View File

Before

Width:  |  Height:  |  Size: 5.0 KiB

After

Width:  |  Height:  |  Size: 5.0 KiB

View File

Before

Width:  |  Height:  |  Size: 4.6 KiB

After

Width:  |  Height:  |  Size: 4.6 KiB

View File

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

View File

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

View File

Before

Width:  |  Height:  |  Size: 3.7 KiB

After

Width:  |  Height:  |  Size: 3.7 KiB

View File

Before

Width:  |  Height:  |  Size: 8.0 KiB

After

Width:  |  Height:  |  Size: 8.0 KiB

View File

Before

Width:  |  Height:  |  Size: 6.5 KiB

After

Width:  |  Height:  |  Size: 6.5 KiB

View File

Before

Width:  |  Height:  |  Size: 5.9 KiB

After

Width:  |  Height:  |  Size: 5.9 KiB

View File

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 16 KiB

View File

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

View File

Before

Width:  |  Height:  |  Size: 8.3 KiB

After

Width:  |  Height:  |  Size: 8.3 KiB

View File

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 24 KiB

View File

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

View File

@ -106,5 +106,12 @@ fun createFactorySettings(context: Context): Settings {
.setSearchBarStyle(Settings.SearchBarSettings.SearchBarStyle.Transparent) .setSearchBarStyle(Settings.SearchBarSettings.SearchBarStyle.Transparent)
.build() .build()
) )
.setIcons(
Settings.IconSettings.newBuilder()
.setLegacyIconBg(Settings.IconSettings.LegacyIconBackground.Dynamic)
.setShape(Settings.IconSettings.IconShape.PlatformDefault)
.setThemedIcons(false)
.setIconPack("")
)
.build() .build()
} }

View File

@ -133,4 +133,28 @@ message Settings {
} }
SearchBarSettings search_bar = 20; SearchBarSettings search_bar = 20;
message IconSettings {
enum IconShape {
PlatformDefault = 0;
Circle = 1;
Square = 2;
RoundedSquare = 3;
Triangle = 4;
Squircle = 5;
Hexagon = 6;
Pentagon = 7;
EasterEgg = 8;
}
IconShape shape = 1;
bool themed_icons = 2;
string icon_pack = 3;
enum LegacyIconBackground {
Dynamic = 0;
None = 1;
White = 2;
}
LegacyIconBackground legacyIconBg = 4;
}
IconSettings icons = 21;
} }

View File

@ -40,7 +40,6 @@ import de.mm20.launcher2.icons.IconRepository
import de.mm20.launcher2.ktx.* import de.mm20.launcher2.ktx.*
import de.mm20.launcher2.legacy.helper.ActivityStarter import de.mm20.launcher2.legacy.helper.ActivityStarter
import de.mm20.launcher2.notifications.NotificationRepository import de.mm20.launcher2.notifications.NotificationRepository
import de.mm20.launcher2.notifications.NotificationService
import de.mm20.launcher2.search.data.AppInstallation import de.mm20.launcher2.search.data.AppInstallation
import de.mm20.launcher2.search.data.Application import de.mm20.launcher2.search.data.Application
import de.mm20.launcher2.search.data.LauncherApp import de.mm20.launcher2.search.data.LauncherApp
@ -79,8 +78,8 @@ class ApplicationDetailRepresentation : Representation, KoinComponent {
setOnLongClickListener(null) setOnLongClickListener(null)
findViewById<TextView>(R.id.appName).text = application.label findViewById<TextView>(R.id.appName).text = application.label
val iconView = findViewById<LauncherIconView>(R.id.icon).apply { val iconView = findViewById<LauncherIconView>(R.id.icon).apply {
shape = LauncherIconView.getDefaultShape(context)
icon = iconRepository.getIconIfCached(application) icon = iconRepository.getIconIfCached(application)
shape = LauncherIconView.currentShape
} }
val notificationView = findViewById<ChipGroup>(R.id.notifications) val notificationView = findViewById<ChipGroup>(R.id.notifications)
@ -107,6 +106,11 @@ class ApplicationDetailRepresentation : Representation, KoinComponent {
updateNotifications(notificationView, it) updateNotifications(notificationView, it)
} }
} }
launch {
LauncherIconView.getDefaultShape().collectLatest {
iconView.shape = it
}
}
} }
} }
findViewById<SwipeCardView>(R.id.appCard).also { findViewById<SwipeCardView>(R.id.appCard).also {

View File

@ -9,7 +9,6 @@ import de.mm20.launcher2.badges.BadgeRepository
import de.mm20.launcher2.icons.IconRepository import de.mm20.launcher2.icons.IconRepository
import de.mm20.launcher2.ktx.dp import de.mm20.launcher2.ktx.dp
import de.mm20.launcher2.ktx.lifecycleOwner import de.mm20.launcher2.ktx.lifecycleOwner
import de.mm20.launcher2.ktx.lifecycleScope
import de.mm20.launcher2.legacy.helper.ActivityStarter import de.mm20.launcher2.legacy.helper.ActivityStarter
import de.mm20.launcher2.search.data.Searchable import de.mm20.launcher2.search.data.Searchable
import de.mm20.launcher2.ui.R import de.mm20.launcher2.ui.R
@ -46,7 +45,6 @@ class BasicGridRepresentation : Representation, KoinComponent {
.alpha(1f) .alpha(1f)
.start()*/ .start()*/
findViewById<LauncherIconView>(R.id.icon).apply { findViewById<LauncherIconView>(R.id.icon).apply {
shape = LauncherIconView.getDefaultShape(context)
setOnClickListener { setOnClickListener {
if (!ActivityStarter.start( if (!ActivityStarter.start(
context, context,
@ -58,6 +56,7 @@ class BasicGridRepresentation : Representation, KoinComponent {
} }
} }
icon = iconRepository.getIconIfCached(searchable) icon = iconRepository.getIconIfCached(searchable)
shape = LauncherIconView.currentShape
job = rootView.scope.launch { job = rootView.scope.launch {
rootView.lifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) { rootView.lifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
@ -72,6 +71,11 @@ class BasicGridRepresentation : Representation, KoinComponent {
badge = it badge = it
} }
} }
launch {
LauncherIconView.getDefaultShape().collectLatest {
shape = it
}
}
} }
} }
setOnLongClickListener { setOnLongClickListener {

View File

@ -20,7 +20,6 @@ import de.mm20.launcher2.badges.BadgeRepository
import de.mm20.launcher2.icons.IconRepository import de.mm20.launcher2.icons.IconRepository
import de.mm20.launcher2.ktx.dp import de.mm20.launcher2.ktx.dp
import de.mm20.launcher2.ktx.lifecycleOwner import de.mm20.launcher2.ktx.lifecycleOwner
import de.mm20.launcher2.ktx.lifecycleScope
import de.mm20.launcher2.ktx.setStartCompoundDrawable import de.mm20.launcher2.ktx.setStartCompoundDrawable
import de.mm20.launcher2.legacy.helper.ActivityStarter import de.mm20.launcher2.legacy.helper.ActivityStarter
import de.mm20.launcher2.search.data.Contact import de.mm20.launcher2.search.data.Contact
@ -54,8 +53,8 @@ class ContactDetailRepresentation : Representation, KoinComponent {
scene.setEnterAction { scene.setEnterAction {
with(rootView) { with(rootView) {
findViewById<LauncherIconView>(R.id.icon).apply { findViewById<LauncherIconView>(R.id.icon).apply {
shape = LauncherIconView.getDefaultShape(context)
icon = iconRepository.getIconIfCached(contact) icon = iconRepository.getIconIfCached(contact)
shape = LauncherIconView.currentShape
job = rootView.scope.launch { job = rootView.scope.launch {
rootView.lifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) { rootView.lifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
launch { launch {
@ -69,6 +68,11 @@ class ContactDetailRepresentation : Representation, KoinComponent {
badge = it badge = it
} }
} }
launch {
LauncherIconView.getDefaultShape().collectLatest {
shape = it
}
}
} }
} }
} }

View File

@ -10,7 +10,6 @@ import de.mm20.launcher2.ui.R
import de.mm20.launcher2.icons.IconRepository import de.mm20.launcher2.icons.IconRepository
import de.mm20.launcher2.ktx.dp import de.mm20.launcher2.ktx.dp
import de.mm20.launcher2.ktx.lifecycleOwner import de.mm20.launcher2.ktx.lifecycleOwner
import de.mm20.launcher2.ktx.lifecycleScope
import de.mm20.launcher2.search.data.Contact import de.mm20.launcher2.search.data.Contact
import de.mm20.launcher2.search.data.Searchable import de.mm20.launcher2.search.data.Searchable
import de.mm20.launcher2.ui.legacy.searchable.SearchableView import de.mm20.launcher2.ui.legacy.searchable.SearchableView
@ -38,8 +37,8 @@ class ContactListRepresentation : Representation, KoinComponent {
scene.setEnterAction { scene.setEnterAction {
with(rootView) { with(rootView) {
findViewById<LauncherIconView>(R.id.icon).apply { findViewById<LauncherIconView>(R.id.icon).apply {
shape = LauncherIconView.getDefaultShape(context)
icon = iconRepository.getIconIfCached(contact) icon = iconRepository.getIconIfCached(contact)
shape = LauncherIconView.currentShape
job = rootView.scope.launch { job = rootView.scope.launch {
rootView.lifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) { rootView.lifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
launch { launch {
@ -53,6 +52,11 @@ class ContactListRepresentation : Representation, KoinComponent {
badge = it badge = it
} }
} }
launch {
LauncherIconView.getDefaultShape().collectLatest {
shape = it
}
}
} }
} }
} }

View File

@ -48,8 +48,8 @@ class FileDetailRepresentation : Representation, KoinComponent {
findViewById<TextView>(R.id.fileLabel).text = file.label findViewById<TextView>(R.id.fileLabel).text = file.label
findViewById<TextView>(R.id.fileInfo).text = getInfo(context, file) findViewById<TextView>(R.id.fileInfo).text = getInfo(context, file)
findViewById<LauncherIconView>(R.id.icon).apply { findViewById<LauncherIconView>(R.id.icon).apply {
shape = LauncherIconView.getDefaultShape(context)
icon = iconRepository.getIconIfCached(file) icon = iconRepository.getIconIfCached(file)
shape = LauncherIconView.currentShape
job = rootView.scope.launch { job = rootView.scope.launch {
rootView.lifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) { rootView.lifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
launch { launch {
@ -63,6 +63,11 @@ class FileDetailRepresentation : Representation, KoinComponent {
badge = it badge = it
} }
} }
launch {
LauncherIconView.getDefaultShape().collectLatest {
shape = it
}
}
} }
} }
} }

View File

@ -44,8 +44,8 @@ class FileListRepresentation : Representation, KoinComponent {
findViewById<TextView>(R.id.fileLabel).text = file.label findViewById<TextView>(R.id.fileLabel).text = file.label
findViewById<TextView>(R.id.fileInfo).text = file.getFileType(context) findViewById<TextView>(R.id.fileInfo).text = file.getFileType(context)
findViewById<LauncherIconView>(R.id.icon).apply { findViewById<LauncherIconView>(R.id.icon).apply {
shape = LauncherIconView.getDefaultShape(context)
icon = iconRepository.getIconIfCached(file) icon = iconRepository.getIconIfCached(file)
shape = LauncherIconView.currentShape
job = rootView.scope.launch { job = rootView.scope.launch {
rootView.lifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) { rootView.lifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
launch { launch {
@ -59,6 +59,11 @@ class FileListRepresentation : Representation, KoinComponent {
badge = it badge = it
} }
} }
launch {
LauncherIconView.getDefaultShape().collectLatest {
shape = it
}
}
} }
} }
} }

View File

@ -47,8 +47,8 @@ class AppShortcutDetailRepresentation : Representation, KoinComponent {
setOnLongClickListener(null) setOnLongClickListener(null)
findViewById<TextView>(R.id.appName).text = appShortcut.label findViewById<TextView>(R.id.appName).text = appShortcut.label
findViewById<LauncherIconView>(R.id.icon).apply { findViewById<LauncherIconView>(R.id.icon).apply {
shape = LauncherIconView.getDefaultShape(context)
icon = iconRepository.getIconIfCached(appShortcut) icon = iconRepository.getIconIfCached(appShortcut)
shape = LauncherIconView.currentShape
job = rootView.scope.launch { job = rootView.scope.launch {
rootView.lifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) { rootView.lifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
launch { launch {
@ -62,6 +62,11 @@ class AppShortcutDetailRepresentation : Representation, KoinComponent {
badge = it badge = it
} }
} }
launch {
LauncherIconView.getDefaultShape().collectLatest {
shape = it
}
}
} }
} }
} }

View File

@ -11,7 +11,6 @@ import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle import androidx.lifecycle.repeatOnLifecycle
import androidx.transition.Scene import androidx.transition.Scene
import coil.load import coil.load
import coil.size.Scale
import de.mm20.launcher2.icons.IconRepository import de.mm20.launcher2.icons.IconRepository
import de.mm20.launcher2.ktx.dp import de.mm20.launcher2.ktx.dp
import de.mm20.launcher2.ktx.lifecycleOwner import de.mm20.launcher2.ktx.lifecycleOwner
@ -74,14 +73,21 @@ class WebsiteDetailRepresentation : Representation, KoinComponent {
label.transitionName = null label.transitionName = null
websiteFavIcon.transitionName = "icon" websiteFavIcon.transitionName = "icon"
websiteFavIcon.apply { websiteFavIcon.apply {
shape = LauncherIconView.getDefaultShape(context)
icon = iconRepository.getIconIfCached(website) icon = iconRepository.getIconIfCached(website)
shape = LauncherIconView.currentShape
job = rootView.scope.launch { job = rootView.scope.launch {
rootView.lifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) { rootView.lifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
iconRepository.getIcon(website, (84 * rootView.dp).toInt()) launch {
.collectLatest { iconRepository.getIcon(website, (84 * rootView.dp).toInt())
icon = it .collectLatest {
icon = it
}
}
launch {
LauncherIconView.getDefaultShape().collectLatest {
shape = it
} }
}
} }
} }
} }

View File

@ -77,8 +77,8 @@ class WebsiteListRepresentation : Representation, KoinComponent {
label.transitionName = null label.transitionName = null
websiteFavIcon.transitionName = "icon" websiteFavIcon.transitionName = "icon"
websiteFavIcon.apply { websiteFavIcon.apply {
shape = LauncherIconView.getDefaultShape(context)
icon = iconRepository.getIconIfCached(website) icon = iconRepository.getIconIfCached(website)
shape = LauncherIconView.currentShape
job = rootView.scope.launch { job = rootView.scope.launch {
rootView.lifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) { rootView.lifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
launch { launch {
@ -92,6 +92,11 @@ class WebsiteListRepresentation : Representation, KoinComponent {
badge = it badge = it
} }
} }
launch {
LauncherIconView.getDefaultShape().collectLatest {
shape = it
}
}
} }
} }
} }

View File

@ -7,37 +7,25 @@ import android.graphics.*
import android.graphics.drawable.AdaptiveIconDrawable import android.graphics.drawable.AdaptiveIconDrawable
import android.os.Build import android.os.Build
import android.util.AttributeSet import android.util.AttributeSet
import android.util.Log
import android.view.HapticFeedbackConstants import android.view.HapticFeedbackConstants
import android.view.MotionEvent import android.view.MotionEvent
import android.view.View import android.view.View
import android.view.ViewConfiguration import android.view.ViewConfiguration
import androidx.annotation.RequiresApi import androidx.annotation.RequiresApi
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LiveData
import androidx.lifecycle.Observer
import androidx.lifecycle.repeatOnLifecycle
import com.bartoszlipinski.viewpropertyobjectanimator.ViewPropertyObjectAnimator import com.bartoszlipinski.viewpropertyobjectanimator.ViewPropertyObjectAnimator
import de.mm20.launcher2.badges.Badge import de.mm20.launcher2.badges.Badge
import de.mm20.launcher2.badges.BadgeRepository import de.mm20.launcher2.icons.LauncherIcon
import de.mm20.launcher2.ktx.dp import de.mm20.launcher2.ktx.dp
import de.mm20.launcher2.ktx.toRectF import de.mm20.launcher2.ktx.toRectF
import de.mm20.launcher2.icons.LauncherIcon import de.mm20.launcher2.preferences.LauncherDataStore
import de.mm20.launcher2.ktx.lifecycleOwner import de.mm20.launcher2.preferences.Settings.IconSettings.IconShape
import de.mm20.launcher2.ktx.lifecycleScope
import de.mm20.launcher2.preferences.IconShape
import de.mm20.launcher2.preferences.LauncherPreferences
import de.mm20.launcher2.ui.R import de.mm20.launcher2.ui.R
import de.mm20.launcher2.ui.legacy.helper.BitmapHolder import de.mm20.launcher2.ui.legacy.helper.BitmapHolder
import kotlinx.coroutines.* import kotlinx.coroutines.flow.*
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.collectLatest
import org.koin.core.component.KoinComponent import org.koin.core.component.KoinComponent
import org.koin.core.component.inject import org.koin.core.component.get
import java.lang.Math.pow import java.lang.Math.pow
import java.lang.Runnable
import kotlin.math.abs import kotlin.math.abs
import kotlin.math.hypot import kotlin.math.hypot
import kotlin.math.roundToInt import kotlin.math.roundToInt
@ -45,17 +33,19 @@ import kotlin.math.roundToInt
class LauncherIconView : View, KoinComponent { class LauncherIconView : View, KoinComponent {
constructor(context: Context) : super(context) constructor(context: Context) : super(context)
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
constructor(context: Context, attrs: AttributeSet?, defStyleRes: Int) : super(context, attrs, defStyleRes) constructor(context: Context, attrs: AttributeSet?, defStyleRes: Int) : super(
context,
attrs,
defStyleRes
)
var shape: IconShape var shape: IconShape
set(value) { set(value) {
if (value == IconShape.PLATFORM_DEFAULT) { if (value == IconShape.PlatformDefault) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { platformShape = getSystemShape()
platformShape = getSystemShape() transformMatrix = Matrix()
transformMatrix = Matrix() platformShapeBounds = RectF()
platformShapeBounds = RectF() field = value
field = value
} else field = IconShape.CIRCLE
} else { } else {
platformShape = null platformShape = null
transformMatrix = null transformMatrix = null
@ -108,7 +98,7 @@ class LauncherIconView : View, KoinComponent {
} }
init { init {
shape = IconShape.CIRCLE shape = IconShape.Circle
setLayerType(LAYER_TYPE_SOFTWARE, null) setLayerType(LAYER_TYPE_SOFTWARE, null)
} }
@ -200,34 +190,45 @@ class LauncherIconView : View, KoinComponent {
if (bg != null) { if (bg != null) {
bg.bounds = bmpDrawRect bg.bounds = bmpDrawRect
when (shape) { when (shape) {
IconShape.PLATFORM_DEFAULT -> { IconShape.PlatformDefault -> {
path.rewind() path.rewind()
val matrix = transformMatrix!! val matrix = transformMatrix!!
val bounds = platformShapeBounds!! val bounds = platformShapeBounds!!
val shape = platformShape!! val shape = platformShape!!
shape.computeBounds(bounds, true) shape.computeBounds(bounds, true)
matrix.setRectToRect(bounds, badgeRect.also { drawRect.toRectF(it) }, Matrix.ScaleToFit.CENTER) matrix.setRectToRect(
bounds,
badgeRect.also { drawRect.toRectF(it) },
Matrix.ScaleToFit.CENTER
)
path.rewind() path.rewind()
shape.transform(matrix, path) shape.transform(matrix, path)
canvas.drawPath(path, maskPaint) canvas.drawPath(path, maskPaint)
} }
IconShape.CIRCLE -> { IconShape.Circle -> {
canvas.drawOval(drawRect.left.toFloat(), drawRect.top.toFloat(), drawRect.right.toFloat(), drawRect.bottom.toFloat(), maskPaint) canvas.drawOval(
} drawRect.left.toFloat(),
IconShape.SQUARE -> { drawRect.top.toFloat(),
canvas.drawRect(drawRect, maskPaint) drawRect.right.toFloat(),
} drawRect.bottom.toFloat(),
IconShape.ROUNDED_SQUARE -> { maskPaint
canvas.drawRoundRect(drawRect.left.toFloat(),
drawRect.top.toFloat(),
drawRect.right.toFloat(),
drawRect.bottom.toFloat(),
width * 0.125f,
height * 0.125f,
maskPaint
) )
} }
IconShape.TRIANGLE -> { IconShape.Square -> {
canvas.drawRect(drawRect, maskPaint)
}
IconShape.RoundedSquare -> {
canvas.drawRoundRect(
drawRect.left.toFloat(),
drawRect.top.toFloat(),
drawRect.right.toFloat(),
drawRect.bottom.toFloat(),
width * 0.125f,
height * 0.125f,
maskPaint
)
}
IconShape.Triangle -> {
path.rewind() path.rewind()
var cx = drawRect.left.toFloat() var cx = drawRect.left.toFloat()
var cy = drawRect.top + drawRect.height().toFloat() * 0.86f var cy = drawRect.top + drawRect.height().toFloat() * 0.86f
@ -248,63 +249,157 @@ class LauncherIconView : View, KoinComponent {
canvas.drawArc(cx - r, cy - r, cx + r, cy + r, 60f, 60f, true, maskPaint) canvas.drawArc(cx - r, cy - r, cx + r, cy + r, 60f, 60f, true, maskPaint)
} }
IconShape.SQUIRCLE -> { IconShape.Squircle -> {
path.rewind() path.rewind()
val radius = drawRect.width() / 2 val radius = drawRect.width() / 2
val radiusToPow = pow(radius.toDouble(), 3.0) val radiusToPow = pow(radius.toDouble(), 3.0)
path.moveTo(-radius.toFloat(), 0f) path.moveTo(-radius.toFloat(), 0f)
for (x in -radius..radius) for (x in -radius..radius)
path.lineTo(x.toFloat(), Math.cbrt(radiusToPow - Math.abs(x * x * x)).toFloat()) path.lineTo(
x.toFloat(),
Math.cbrt(radiusToPow - Math.abs(x * x * x)).toFloat()
)
for (x in radius downTo -radius) for (x in radius downTo -radius)
path.lineTo(x.toFloat(), (-Math.cbrt(radiusToPow - Math.abs(x * x * x))).toFloat()) path.lineTo(
x.toFloat(),
(-Math.cbrt(radiusToPow - Math.abs(x * x * x))).toFloat()
)
path.close() path.close()
canvas.save() canvas.save()
canvas.translate(width / 2f, height / 2f) canvas.translate(width / 2f, height / 2f)
canvas.drawPath(path, maskPaint) canvas.drawPath(path, maskPaint)
canvas.restore() canvas.restore()
} }
IconShape.HEXAGON -> { IconShape.Hexagon -> {
path.rewind() path.rewind()
path.moveTo(drawRect.left + drawRect.width() * 0.25f, drawRect.top + drawRect.height() * 0.933f) path.moveTo(
path.lineTo(drawRect.left + drawRect.width() * 0.75f, drawRect.top + drawRect.height() * 0.933f) drawRect.left + drawRect.width() * 0.25f,
path.lineTo(drawRect.left + drawRect.width() * 1.0f, drawRect.top + drawRect.height() * 0.5f) drawRect.top + drawRect.height() * 0.933f
path.lineTo(drawRect.left + drawRect.width() * 0.75f, drawRect.top + drawRect.height() * 0.067f) )
path.lineTo(drawRect.left + drawRect.width() * 0.25f, drawRect.top + drawRect.height() * 0.067f) path.lineTo(
drawRect.left + drawRect.width() * 0.75f,
drawRect.top + drawRect.height() * 0.933f
)
path.lineTo(
drawRect.left + drawRect.width() * 1.0f,
drawRect.top + drawRect.height() * 0.5f
)
path.lineTo(
drawRect.left + drawRect.width() * 0.75f,
drawRect.top + drawRect.height() * 0.067f
)
path.lineTo(
drawRect.left + drawRect.width() * 0.25f,
drawRect.top + drawRect.height() * 0.067f
)
path.lineTo(drawRect.left.toFloat(), drawRect.top + drawRect.height() * 0.5f) path.lineTo(drawRect.left.toFloat(), drawRect.top + drawRect.height() * 0.5f)
path.close() path.close()
canvas.drawPath(path, maskPaint) canvas.drawPath(path, maskPaint)
} }
IconShape.HEART -> { IconShape.EasterEgg -> {
path.rewind() path.rewind()
path.moveTo(0.49999999f * drawRect.width() + drawRect.left, 1f * drawRect.height() + drawRect.top) path.moveTo(
path.lineTo(0.42749999f * drawRect.width() + drawRect.left, 0.9339999999999999f * drawRect.height() + drawRect.top) 0.49999999f * drawRect.width() + drawRect.left,
path.cubicTo(0.16999998f * drawRect.width() + drawRect.left, 0.7005004f * drawRect.height() + drawRect.top, 0f + drawRect.left, 0.5460004f * drawRect.height() + drawRect.top, 0f + drawRect.left, 0.3575003f * drawRect.height() + drawRect.top) 1f * drawRect.height() + drawRect.top
path.cubicTo(0f + drawRect.left, 0.2030004f * drawRect.height() + drawRect.top, 0.12100002f * drawRect.width() + drawRect.left, 0.0825004f * drawRect.height() + drawRect.top, 0.275f * drawRect.width() + drawRect.left, 0.0825004f * drawRect.height() + drawRect.top) )
path.cubicTo(0.362f * drawRect.width() + drawRect.left, 0.0825004f * drawRect.height() + drawRect.top, 0.4455f * drawRect.width() + drawRect.left, 0.123f * drawRect.height() + drawRect.top, 0.5f * drawRect.width() + drawRect.left, 0.1865003f * drawRect.height() + drawRect.top) path.lineTo(
path.cubicTo(0.55449999f * drawRect.width() + drawRect.left, 0.123f * drawRect.height() + drawRect.top, 0.638f * drawRect.width() + drawRect.left, 0.0825f * drawRect.height() + drawRect.top, 0.725f * drawRect.width() + drawRect.left, 0.0825f * drawRect.height() + drawRect.top) 0.42749999f * drawRect.width() + drawRect.left,
path.cubicTo(0.87900006f * drawRect.width() + drawRect.left, 0.0825004f * drawRect.height() + drawRect.top, 1f * drawRect.width() + drawRect.left, 0.2030004f * drawRect.height() + drawRect.top, 1f * drawRect.width() + drawRect.left, 0.3575003f * drawRect.height() + drawRect.top) 0.9339999999999999f * drawRect.height() + drawRect.top
path.cubicTo(1f * drawRect.width() + drawRect.left, 0.5460004f * drawRect.height() + drawRect.top, 0.82999999f * drawRect.width() + drawRect.left, 0.7005004f * drawRect.height() + drawRect.top, 0.57250001f * drawRect.width() + drawRect.left, 0.9340004f * drawRect.height() + drawRect.top) )
path.cubicTo(
0.16999998f * drawRect.width() + drawRect.left,
0.7005004f * drawRect.height() + drawRect.top,
0f + drawRect.left,
0.5460004f * drawRect.height() + drawRect.top,
0f + drawRect.left,
0.3575003f * drawRect.height() + drawRect.top
)
path.cubicTo(
0f + drawRect.left,
0.2030004f * drawRect.height() + drawRect.top,
0.12100002f * drawRect.width() + drawRect.left,
0.0825004f * drawRect.height() + drawRect.top,
0.275f * drawRect.width() + drawRect.left,
0.0825004f * drawRect.height() + drawRect.top
)
path.cubicTo(
0.362f * drawRect.width() + drawRect.left,
0.0825004f * drawRect.height() + drawRect.top,
0.4455f * drawRect.width() + drawRect.left,
0.123f * drawRect.height() + drawRect.top,
0.5f * drawRect.width() + drawRect.left,
0.1865003f * drawRect.height() + drawRect.top
)
path.cubicTo(
0.55449999f * drawRect.width() + drawRect.left,
0.123f * drawRect.height() + drawRect.top,
0.638f * drawRect.width() + drawRect.left,
0.0825f * drawRect.height() + drawRect.top,
0.725f * drawRect.width() + drawRect.left,
0.0825f * drawRect.height() + drawRect.top
)
path.cubicTo(
0.87900006f * drawRect.width() + drawRect.left,
0.0825004f * drawRect.height() + drawRect.top,
1f * drawRect.width() + drawRect.left,
0.2030004f * drawRect.height() + drawRect.top,
1f * drawRect.width() + drawRect.left,
0.3575003f * drawRect.height() + drawRect.top
)
path.cubicTo(
1f * drawRect.width() + drawRect.left,
0.5460004f * drawRect.height() + drawRect.top,
0.82999999f * drawRect.width() + drawRect.left,
0.7005004f * drawRect.height() + drawRect.top,
0.57250001f * drawRect.width() + drawRect.left,
0.9340004f * drawRect.height() + drawRect.top
)
path.close() path.close()
canvas.drawPath(path, maskPaint) canvas.drawPath(path, maskPaint)
} }
IconShape.PENTAGON -> { IconShape.Pentagon -> {
path.rewind() path.rewind()
path.moveTo(0.49997027f * drawRect.width() + drawRect.left, 0.0060308f * drawRect.height() + drawRect.top) path.moveTo(
path.lineTo(0.99994053f * drawRect.width() + drawRect.left, 0.36928048f * drawRect.height() + drawRect.top) 0.49997027f * drawRect.width() + drawRect.left,
path.lineTo(0.80896887f * drawRect.width() + drawRect.left, 0.95703078f * drawRect.height() + drawRect.top) 0.0060308f * drawRect.height() + drawRect.top
path.lineTo(0.19097162f * drawRect.width() + drawRect.left, 0.95703076f * drawRect.height() + drawRect.top) )
path.lineTo(drawRect.left.toFloat(), 0.36928045f * drawRect.height() + drawRect.top) path.lineTo(
0.99994053f * drawRect.width() + drawRect.left,
0.36928048f * drawRect.height() + drawRect.top
)
path.lineTo(
0.80896887f * drawRect.width() + drawRect.left,
0.95703078f * drawRect.height() + drawRect.top
)
path.lineTo(
0.19097162f * drawRect.width() + drawRect.left,
0.95703076f * drawRect.height() + drawRect.top
)
path.lineTo(
drawRect.left.toFloat(),
0.36928045f * drawRect.height() + drawRect.top
)
path.close() path.close()
canvas.drawPath(path, maskPaint) canvas.drawPath(path, maskPaint)
} }
} }
c.save() c.save()
c.scale(backgroundScale, backgroundScale, bmpDrawRect.centerX().toFloat(), bmpDrawRect.centerY().toFloat()) c.scale(
backgroundScale,
backgroundScale,
bmpDrawRect.centerX().toFloat(),
bmpDrawRect.centerY().toFloat()
)
bg.draw(c) bg.draw(c)
c.restore() c.restore()
} }
c.save() c.save()
c.scale(foregroundScale, foregroundScale, bmpDrawRect.centerX().toFloat(), bmpDrawRect.centerY().toFloat()) c.scale(
foregroundScale,
foregroundScale,
bmpDrawRect.centerX().toFloat(),
bmpDrawRect.centerY().toFloat()
)
fg.draw(c) fg.draw(c)
c.restore() c.restore()
if (bg != null) { if (bg != null) {
@ -314,26 +409,33 @@ class LauncherIconView : View, KoinComponent {
} }
if (bg != null) { if (bg != null) {
when (shape) { when (shape) {
IconShape.CIRCLE -> { IconShape.Circle -> {
canvas.drawOval(drawRect.left.toFloat(), drawRect.top.toFloat(), drawRect.right.toFloat(), drawRect.bottom.toFloat(), shadowPaint) canvas.drawOval(
} drawRect.left.toFloat(),
IconShape.SQUARE -> { drawRect.top.toFloat(),
canvas.drawRect(drawRect, shadowPaint) drawRect.right.toFloat(),
} drawRect.bottom.toFloat(),
IconShape.ROUNDED_SQUARE -> { shadowPaint
canvas.drawRoundRect(drawRect.left.toFloat(),
drawRect.top.toFloat(),
drawRect.right.toFloat(),
drawRect.bottom.toFloat(),
width * 0.125f,
height * 0.125f,
shadowPaint
) )
} }
IconShape.TRIANGLE, IconShape.HEXAGON, IconShape.HEART, IconShape.PENTAGON, IconShape.PLATFORM_DEFAULT -> { IconShape.Square -> {
canvas.drawRect(drawRect, shadowPaint)
}
IconShape.RoundedSquare -> {
canvas.drawRoundRect(
drawRect.left.toFloat(),
drawRect.top.toFloat(),
drawRect.right.toFloat(),
drawRect.bottom.toFloat(),
width * 0.125f,
height * 0.125f,
shadowPaint
)
}
IconShape.Triangle, IconShape.Hexagon, IconShape.EasterEgg, IconShape.Pentagon, IconShape.PlatformDefault -> {
canvas.drawPath(path, shadowPaint) canvas.drawPath(path, shadowPaint)
} }
IconShape.SQUIRCLE -> { IconShape.Squircle -> {
canvas.save() canvas.save()
canvas.translate(width / 2f, height / 2f) canvas.translate(width / 2f, height / 2f)
canvas.drawPath(path, shadowPaint) canvas.drawPath(path, shadowPaint)
@ -361,12 +463,18 @@ class LauncherIconView : View, KoinComponent {
canvas.drawArc(badgeRect, 270f, it * 360, true, badgeProgressPaint) canvas.drawArc(badgeRect, 270f, it * 360, true, badgeProgressPaint)
} }
badgeIcon?.let { badgeIcon?.let {
it.setBounds((drawRect.right - badgeSize * 0.9f).toInt(), it.setBounds(
(drawRect.bottom - badgeSize * 0.9f).toInt(), (drawRect.right - badgeSize * 0.9f).toInt(),
(drawRect.right - badgeSize * 0.1f).toInt(), (drawRect.bottom - badgeSize * 0.9f).toInt(),
(drawRect.bottom - badgeSize * 0.1f).toInt() (drawRect.right - badgeSize * 0.1f).toInt(),
(drawRect.bottom - badgeSize * 0.1f).toInt()
)
it.setBounds(
badgeRect.left.roundToInt(),
badgeRect.top.roundToInt(),
badgeRect.right.roundToInt(),
badgeRect.bottom.roundToInt()
) )
it.setBounds(badgeRect.left.roundToInt(), badgeRect.top.roundToInt(), badgeRect.right.roundToInt(), badgeRect.bottom.roundToInt())
it.draw(canvas) it.draw(canvas)
return return
} }
@ -375,7 +483,12 @@ class LauncherIconView : View, KoinComponent {
val textSize = (1f - 0.1f - text.length * 0.1f) * badgeSize val textSize = (1f - 0.1f - text.length * 0.1f) * badgeSize
badgeTextPaint.textSize = textSize badgeTextPaint.textSize = textSize
badgeTextPaint.getTextBounds(text, 0, text.length, textBounds) badgeTextPaint.getTextBounds(text, 0, text.length, textBounds)
canvas.drawText(it.toString(), badgeRect.centerX(), badgeRect.centerY() - textBounds.exactCenterY(), badgeTextPaint) canvas.drawText(
it.toString(),
badgeRect.centerX(),
badgeRect.centerY() - textBounds.exactCenterY(),
badgeTextPaint
)
} }
} }
@ -398,7 +511,10 @@ class LauncherIconView : View, KoinComponent {
downX = ev.rawX downX = ev.rawX
downY = ev.rawY downY = ev.rawY
longClicked = false longClicked = false
handler?.postDelayed(longClickRunnable, ViewConfiguration.getLongPressTimeout().toLong()) handler?.postDelayed(
longClickRunnable,
ViewConfiguration.getLongPressTimeout().toLong()
)
return true return true
} }
MotionEvent.ACTION_MOVE -> { MotionEvent.ACTION_MOVE -> {
@ -428,9 +544,9 @@ class LauncherIconView : View, KoinComponent {
private fun animateTouchUp() { private fun animateTouchUp() {
AnimatorSet().also { AnimatorSet().also {
it.playTogether( it.playTogether(
ViewPropertyObjectAnimator.animate(this).translationZ(0f).get(), ViewPropertyObjectAnimator.animate(this).translationZ(0f).get(),
ObjectAnimator.ofFloat(this, "foregroundScale", icon?.foregroundScale ?: 1f), ObjectAnimator.ofFloat(this, "foregroundScale", icon?.foregroundScale ?: 1f),
ObjectAnimator.ofFloat(this, "backgroundScale", icon?.backgroundScale ?: 1f) ObjectAnimator.ofFloat(this, "backgroundScale", icon?.backgroundScale ?: 1f)
) )
it.duration = 300 it.duration = 300
it.start() it.start()
@ -440,11 +556,15 @@ class LauncherIconView : View, KoinComponent {
private fun animateTouchDown() { private fun animateTouchDown() {
AnimatorSet().also { AnimatorSet().also {
it.playTogether( it.playTogether(
ViewPropertyObjectAnimator.animate(this).translationZ(2 * dp).get(), ViewPropertyObjectAnimator.animate(this).translationZ(2 * dp).get(),
ObjectAnimator.ofFloat(this, "foregroundScale", (icon?.foregroundScale ObjectAnimator.ofFloat(
?: 1f) * 0.8f), this, "foregroundScale", (icon?.foregroundScale
ObjectAnimator.ofFloat(this, "backgroundScale", (icon?.backgroundScale ?: 1f) * 0.8f
?: 1f) * 1.2f) ),
ObjectAnimator.ofFloat(
this, "backgroundScale", (icon?.backgroundScale
?: 1f) * 1.2f
)
) )
it.duration = 250 it.duration = 250
it.start() it.start()
@ -452,12 +572,16 @@ class LauncherIconView : View, KoinComponent {
} }
companion object { companion object: KoinComponent {
fun getDefaultShape(context: Context): IconShape { var currentShape: IconShape = IconShape.PlatformDefault
if (LauncherPreferences.instance.easterEggEnabled) return IconShape.HEART
return LauncherPreferences.instance.iconShape.let { fun getDefaultShape(): Flow<IconShape> = channelFlow {
return@let if (it != IconShape.HEART) it else IconShape.PLATFORM_DEFAULT send(currentShape)
val dataStore: LauncherDataStore = get()
dataStore.data.map { it.icons.shape }.distinctUntilChanged().collectLatest {
currentShape = it
send(it)
} }
} }
} }

View File

@ -1,31 +1,45 @@
package de.mm20.launcher2.ui.settings.appearance package de.mm20.launcher2.ui.settings.appearance
import android.content.Context
import android.graphics.drawable.ColorDrawable
import android.view.ViewGroup
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.content.res.AppCompatResources
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
import androidx.compose.material3.AlertDialog import androidx.compose.foundation.lazy.GridCells
import androidx.compose.material3.MaterialTheme import androidx.compose.foundation.lazy.LazyVerticalGrid
import androidx.compose.material3.Text import androidx.compose.foundation.lazy.items
import androidx.compose.material3.TextButton import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.*
import androidx.compose.runtime.* import androidx.compose.runtime.*
import androidx.compose.runtime.livedata.observeAsState import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.viewinterop.AndroidView
import androidx.compose.ui.window.Dialog
import androidx.lifecycle.viewmodel.compose.viewModel import androidx.lifecycle.viewmodel.compose.viewModel
import com.google.accompanist.pager.ExperimentalPagerApi import com.google.accompanist.pager.ExperimentalPagerApi
import com.google.accompanist.pager.HorizontalPager import com.google.accompanist.pager.HorizontalPager
import com.google.accompanist.pager.HorizontalPagerIndicator import com.google.accompanist.pager.HorizontalPagerIndicator
import com.google.accompanist.pager.rememberPagerState import com.google.accompanist.pager.rememberPagerState
import de.mm20.launcher2.icons.LauncherIcon
import de.mm20.launcher2.ktx.dp
import de.mm20.launcher2.preferences.IconShape
import de.mm20.launcher2.preferences.Settings.AppearanceSettings.ColorScheme import de.mm20.launcher2.preferences.Settings.AppearanceSettings.ColorScheme
import de.mm20.launcher2.preferences.Settings.AppearanceSettings.Theme import de.mm20.launcher2.preferences.Settings.AppearanceSettings.Theme
import de.mm20.launcher2.preferences.Settings.IconSettings
import de.mm20.launcher2.preferences.Settings.SearchBarSettings import de.mm20.launcher2.preferences.Settings.SearchBarSettings
import de.mm20.launcher2.ui.R import de.mm20.launcher2.ui.R
import de.mm20.launcher2.ui.component.preferences.* import de.mm20.launcher2.ui.component.preferences.*
import de.mm20.launcher2.ui.launcher.search.SearchBar import de.mm20.launcher2.ui.launcher.search.SearchBar
import de.mm20.launcher2.ui.launcher.search.SearchBarLevel import de.mm20.launcher2.ui.launcher.search.SearchBarLevel
import de.mm20.launcher2.ui.legacy.view.LauncherIconView
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.isActive import kotlinx.coroutines.isActive
@ -96,6 +110,17 @@ fun AppearanceSettingsScreen() {
} }
) )
} }
PreferenceCategory(stringResource(R.string.preference_category_icons)) {
val iconShape by viewModel.iconShape.observeAsState()
IconShapePreference(
title = stringResource(R.string.preference_icon_shape),
summary = getShapeName(iconShape),
value = iconShape,
onValueChanged = {
viewModel.setIconShape(it)
}
)
}
PreferenceCategory(stringResource(R.string.preference_category_searchbar)) { PreferenceCategory(stringResource(R.string.preference_category_searchbar)) {
val searchBarStyle by viewModel.searchBarStyle.observeAsState() val searchBarStyle by viewModel.searchBarStyle.observeAsState()
SearchBarStylePreference( SearchBarStylePreference(
@ -123,14 +148,15 @@ fun SearchBarStylePreference(
Preference(title = title, summary = summary, onClick = { showDialog = true }) Preference(title = title, summary = summary, onClick = { showDialog = true })
if (showDialog && value != null) { if (showDialog && value != null) {
val styles = remember { val styles = remember {
SearchBarSettings.SearchBarStyle.values().filter { it != SearchBarSettings.SearchBarStyle.UNRECOGNIZED } SearchBarSettings.SearchBarStyle.values()
.filter { it != SearchBarSettings.SearchBarStyle.UNRECOGNIZED }
} }
val pagerState = rememberPagerState(styles.indexOf(value)) val pagerState = rememberPagerState(styles.indexOf(value))
var level by remember { mutableStateOf(SearchBarLevel.Resting) } var level by remember { mutableStateOf(SearchBarLevel.Resting) }
var previewSearchValue by remember { mutableStateOf("") } var previewSearchValue by remember { mutableStateOf("") }
LaunchedEffect(null) { LaunchedEffect(null) {
while(isActive) { while (isActive) {
delay(2000) delay(2000)
level = SearchBarLevel.Active level = SearchBarLevel.Active
delay(1000) delay(1000)
@ -181,11 +207,108 @@ fun SearchBarStylePreference(
.padding(bottom = 16.dp) .padding(bottom = 16.dp)
.background(MaterialTheme.colorScheme.secondary) .background(MaterialTheme.colorScheme.secondary)
) { ) {
SearchBar(level = level, style = styles[it], websearches = emptyList(), value = previewSearchValue, onValueChange = {}) SearchBar(
level = level,
style = styles[it],
websearches = emptyList(),
value = previewSearchValue,
onValueChange = {})
} }
HorizontalPagerIndicator(pagerState = pagerState) HorizontalPagerIndicator(pagerState = pagerState)
} }
} }
) )
} }
}
@OptIn(ExperimentalFoundationApi::class)
@Composable
fun IconShapePreference(
title: String,
summary: String? = null,
value: IconSettings.IconShape?,
onValueChanged: (IconSettings.IconShape) -> Unit
) {
var showDialog by remember { mutableStateOf(false) }
Preference(title = title, summary = summary, onClick = { showDialog = true })
if (showDialog && value != null) {
val shapes = remember {
IconSettings.IconShape.values()
.filter { it != IconSettings.IconShape.UNRECOGNIZED && it != IconSettings.IconShape.EasterEgg }
}
Dialog(onDismissRequest = { showDialog = false }) {
Surface(
tonalElevation = 16.dp,
shadowElevation = 16.dp,
shape = RoundedCornerShape(16.dp),
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 16.dp),
) {
Column(
modifier = Modifier.fillMaxWidth()
) {
Text(
text = title,
style = MaterialTheme.typography.titleLarge,
modifier = Modifier.padding(
start = 24.dp, end = 24.dp, top = 16.dp, bottom = 8.dp
)
)
LazyVerticalGrid(
cells = GridCells.Adaptive(96.dp),
modifier = Modifier
.fillMaxWidth()
.padding(bottom = 16.dp, start = 16.dp, end = 16.dp)
) {
items(shapes) {
Column(modifier = Modifier
.padding(8.dp),
horizontalAlignment = Alignment.CenterHorizontally) {
AndroidView(factory = { context ->
LauncherIconView(context).apply {
shape = it
icon = LauncherIcon(
foreground = AppCompatResources.getDrawable(context, R.mipmap.ic_launcher_foreground)!!,
background = ColorDrawable(context.getColor(R.color.ic_launcher_background))
)
setOnClickListener { _ ->
onValueChanged(it)
showDialog = false
}
layoutParams = ViewGroup.LayoutParams(
(48 * context.dp).toInt(),
(48 * context.dp).toInt(),
)
}
})
Text(
getShapeName(it) ?: "",
textAlign = TextAlign.Center,
style = MaterialTheme.typography.labelMedium,
modifier = Modifier.padding(top = 4.dp))
}
}
}
}
}
}
}
}
@Composable
private fun getShapeName(shape: IconSettings.IconShape?): String? {
return stringResource(when (shape) {
IconSettings.IconShape.Triangle -> R.string.preference_icon_shape_triangle
IconSettings.IconShape.Hexagon -> R.string.preference_icon_shape_hexagon
IconSettings.IconShape.RoundedSquare -> R.string.preference_icon_shape_rounded_square
IconSettings.IconShape.Squircle -> R.string.preference_icon_shape_squircle
IconSettings.IconShape.Square -> R.string.preference_icon_shape_square
IconSettings.IconShape.Pentagon -> R.string.preference_icon_shape_pentagon
IconSettings.IconShape.PlatformDefault -> R.string.preference_icon_shape_platform
IconSettings.IconShape.Circle -> R.string.preference_icon_shape_circle
else -> return null
})
} }

View File

@ -6,6 +6,7 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.asLiveData import androidx.lifecycle.asLiveData
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import de.mm20.launcher2.preferences.LauncherDataStore import de.mm20.launcher2.preferences.LauncherDataStore
import de.mm20.launcher2.preferences.Settings
import de.mm20.launcher2.preferences.Settings.AppearanceSettings.ColorScheme import de.mm20.launcher2.preferences.Settings.AppearanceSettings.ColorScheme
import de.mm20.launcher2.preferences.Settings.AppearanceSettings.Theme import de.mm20.launcher2.preferences.Settings.AppearanceSettings.Theme
import de.mm20.launcher2.preferences.Settings.SearchBarSettings import de.mm20.launcher2.preferences.Settings.SearchBarSettings
@ -70,8 +71,23 @@ class AppearanceSettingsScreenVM : ViewModel(), KoinComponent {
viewModelScope.launch { viewModelScope.launch {
dataStore.updateData { dataStore.updateData {
it.toBuilder() it.toBuilder()
.setSearchBar(it.searchBar.toBuilder() .setSearchBar(
.setSearchBarStyle(searchBarStyle) it.searchBar.toBuilder()
.setSearchBarStyle(searchBarStyle)
)
.build()
}
}
}
val iconShape = dataStore.data.map { it.icons.shape }.asLiveData()
fun setIconShape(iconShape: Settings.IconSettings.IconShape) {
viewModelScope.launch {
dataStore.updateData {
it.toBuilder()
.setIcons(
it.icons.toBuilder()
.setShape(iconShape)
) )
.build() .build()
} }