Skip to content

Commit d35ddef

Browse files
refactor(settings): Replace XML switch with Compose implementation
The custom `SettingsSwitch` has been refactored from an XML drawable-based implementation to a pure Jetpack Compose `CustomSwitch`. This change removes the dependency on four XML drawable files (`selector_switch.xml`, `shape_switch_thumb.xml`, `shape_switch_track_off.xml`, `shape_switch_track_on.xml`), simplifying the component's structure and improving maintainability. The new `CustomSwitch` composable recreates the switch's appearance and behavior, including the track and an animated thumb. The previous "✔"/"✖" text indicator has been removed in favor of the more conventional and visually cleaner switch design.
1 parent 981f8d9 commit d35ddef

File tree

5 files changed

+85
-79
lines changed

5 files changed

+85
-79
lines changed

app/src/main/java/com/github/droidworksstudio/mlauncher/ui/compose/SettingsComposable.kt

Lines changed: 85 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -5,19 +5,26 @@ import android.text.method.LinkMovementMethod
55
import android.util.TypedValue
66
import android.view.View
77
import androidx.annotation.DrawableRes
8+
import androidx.compose.animation.core.animateDpAsState
89
import androidx.compose.foundation.Image
10+
import androidx.compose.foundation.background
11+
import androidx.compose.foundation.border
912
import androidx.compose.foundation.clickable
1013
import androidx.compose.foundation.layout.Arrangement
14+
import androidx.compose.foundation.layout.Box
1115
import androidx.compose.foundation.layout.Column
1216
import androidx.compose.foundation.layout.Row
1317
import androidx.compose.foundation.layout.Spacer
1418
import androidx.compose.foundation.layout.fillMaxWidth
1519
import androidx.compose.foundation.layout.height
20+
import androidx.compose.foundation.layout.offset
1621
import androidx.compose.foundation.layout.padding
1722
import androidx.compose.foundation.layout.size
1823
import androidx.compose.foundation.layout.width
1924
import androidx.compose.foundation.layout.wrapContentHeight
2025
import androidx.compose.foundation.layout.wrapContentSize
26+
import androidx.compose.foundation.shape.CircleShape
27+
import androidx.compose.foundation.shape.RoundedCornerShape
2128
import androidx.compose.runtime.Composable
2229
import androidx.compose.runtime.getValue
2330
import androidx.compose.runtime.mutableIntStateOf
@@ -28,6 +35,8 @@ import androidx.compose.runtime.rememberCoroutineScope
2835
import androidx.compose.runtime.setValue
2936
import androidx.compose.ui.Alignment
3037
import androidx.compose.ui.Modifier
38+
import androidx.compose.ui.draw.clip
39+
import androidx.compose.ui.graphics.Brush
3140
import androidx.compose.ui.graphics.Color
3241
import androidx.compose.ui.graphics.ColorFilter
3342
import androidx.compose.ui.graphics.toArgb
@@ -41,8 +50,6 @@ import androidx.compose.ui.viewinterop.AndroidView
4150
import com.github.creativecodecat.components.views.FontAppCompatTextView
4251
import com.github.droidworksstudio.mlauncher.services.HapticFeedbackService
4352
import com.github.droidworksstudio.mlauncher.style.SettingsTheme
44-
import com.github.droidworksstudio.mlauncher.style.textDisabled
45-
import com.github.droidworksstudio.mlauncher.style.textEnabled
4653
import kotlinx.coroutines.Job
4754
import kotlinx.coroutines.delay
4855
import kotlinx.coroutines.launch
@@ -431,68 +438,111 @@ object SettingsComposable {
431438
@Composable
432439
fun SettingsSwitch(
433440
text: String,
434-
fontSize: TextUnit = TextUnit.Unspecified,
441+
fontSize: TextUnit = 14.sp,
435442
titleColor: Color = SettingsTheme.typography.title.color,
436443
defaultState: Boolean = false,
437444
onCheckedChange: (Boolean) -> Unit
438445
) {
439446
var isChecked by remember { mutableStateOf(defaultState) }
440-
441-
val resolvedFontSizeSp = if (fontSize != TextUnit.Unspecified) fontSize.value else 14f
447+
// Extract font size and color from theme safely in composable scope
448+
val resolvedFontSizeSp = if (fontSize != TextUnit.Unspecified) fontSize.value else 16f
442449
val context = LocalContext.current
443450

444-
Column(
451+
Row(
445452
modifier = Modifier
446453
.fillMaxWidth()
447-
.padding(vertical = 4.dp, horizontal = 16.dp)
448454
.clickable {
449455
isChecked = !isChecked
450456
onCheckedChange(isChecked)
451457

452-
// Haptic feedback
453458
HapticFeedbackService.trigger(
454-
context = context,
455-
effectType = if (isChecked)
459+
context,
460+
if (isChecked)
456461
HapticFeedbackService.EffectType.ON
457462
else
458463
HapticFeedbackService.EffectType.OFF
459464
)
460-
},
461-
verticalArrangement = Arrangement.Center,
462-
horizontalAlignment = Alignment.Start
465+
}
466+
.padding(horizontal = 16.dp, vertical = 12.dp),
467+
verticalAlignment = Alignment.CenterVertically
463468
) {
464-
// Title text
469+
470+
// Label
465471
AndroidView(
466-
factory = { ctx ->
467-
FontAppCompatTextView(ctx).apply {
468-
textAlignment = View.TEXT_ALIGNMENT_VIEW_START
472+
factory = { context ->
473+
FontAppCompatTextView(context).apply {
474+
this.text = text
469475
setTextSize(TypedValue.COMPLEX_UNIT_SP, resolvedFontSizeSp)
476+
setTextColor(titleColor.toArgb())
477+
textAlignment = View.TEXT_ALIGNMENT_VIEW_START
470478
}
471479
},
472-
modifier = Modifier.wrapContentHeight(),
473-
update = { textView ->
474-
textView.text = text
475-
textView.setTextColor(titleColor.toArgb())
476-
}
480+
modifier = Modifier
481+
.weight(1f)
482+
.wrapContentHeight()
477483
)
478484

479-
// “Enabled/Disabled” text
480-
AndroidView(
481-
factory = { ctx ->
482-
FontAppCompatTextView(ctx).apply {
483-
setTextSize(TypedValue.COMPLEX_UNIT_SP, (resolvedFontSizeSp / 1.3).toFloat())
484-
textAlignment = View.TEXT_ALIGNMENT_VIEW_START
485-
}
486-
},
487-
modifier = Modifier.wrapContentHeight(),
488-
update = { textView ->
489-
textView.text = if (isChecked) "" else ""
490-
textView.setTextColor(if (isChecked) textEnabled.toArgb() else textDisabled.toArgb())
491-
}
485+
// Custom switch
486+
CustomSwitch(
487+
checked = isChecked
488+
)
489+
}
490+
}
491+
492+
493+
@Composable
494+
fun CustomSwitch(
495+
checked: Boolean,
496+
modifier: Modifier = Modifier
497+
) {
498+
val thumbSize = 14.dp
499+
val trackWidth = 32.dp
500+
val trackHeight = 16.dp
501+
502+
val thumbOffset by animateDpAsState(
503+
targetValue = if (checked) trackWidth - thumbSize - 2.dp else 2.dp,
504+
label = "thumbOffset"
505+
)
506+
507+
Box(
508+
modifier = modifier
509+
.size(width = trackWidth, height = trackHeight)
510+
.clip(RoundedCornerShape(trackHeight / 2))
511+
.background(
512+
if (checked)
513+
Brush.verticalGradient(
514+
colors = listOf(
515+
Color(0xFF4CAF73),
516+
Color(0xFF2E8B57)
517+
)
518+
)
519+
else
520+
Brush.verticalGradient(
521+
colors = listOf(
522+
Color(0xFFD6D6D6),
523+
Color(0xFFD6D6D6)
524+
)
525+
)
526+
527+
)
528+
) {
529+
Box(
530+
modifier = Modifier
531+
.offset(x = thumbOffset)
532+
.size(thumbSize)
533+
.align(Alignment.CenterStart)
534+
.clip(CircleShape)
535+
.background(Color.White)
536+
.border(
537+
width = 1.dp,
538+
color = Color.Black.copy(alpha = 0.15f),
539+
shape = CircleShape
540+
)
492541
)
493542
}
494543
}
495544

545+
496546
@Composable
497547
fun SettingsSelect(
498548
title: String,

app/src/main/res/drawable/selector_switch.xml

Lines changed: 0 additions & 5 deletions
This file was deleted.

app/src/main/res/drawable/shape_switch_thumb.xml

Lines changed: 0 additions & 13 deletions
This file was deleted.

app/src/main/res/drawable/shape_switch_track_off.xml

Lines changed: 0 additions & 13 deletions
This file was deleted.

app/src/main/res/drawable/shape_switch_track_on.xml

Lines changed: 0 additions & 13 deletions
This file was deleted.

0 commit comments

Comments
 (0)