authorgravatar for paul@vandyk.frPaul van Dyk <paul@vandyk.fr> 2026-08-13 22:41:17+02:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-08-13 20:41:17+00:00
logbad1143b02f7b585d1fe1335b3d9a97983ce8d8b
treed5e9ff66930547f244253f20a28babfc29ad3852
parent92d4a2e996b19239d95e32a3feb05208e0a52306
signature Signed by PGP key B5690EEEBB952194

fix(mobile): show a real settings cog in the Android sidebar header (#6520)

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>

6 files changed, 0 insertions(+), 223 deletions(-)

apps/mobile/modules/t3-native-controls/android/src/main/java/expo/modules/t3nativecontrols/T3HeaderButtonView.kt deleted-97
......@@ -1,97 +0,0 @@
1package expo.modules.t3nativecontrols
2
3import android.content.Context
4import android.graphics.Canvas
5import android.graphics.Color
6import android.graphics.Paint
7import android.view.View
8import expo.modules.kotlin.AppContext
9import expo.modules.kotlin.viewevent.EventDispatcher
10import expo.modules.kotlin.views.ExpoView
11
12class T3HeaderButtonView(context: Context, appContext: AppContext) : ExpoView(context, appContext) {
13 private val iconView = HeaderIconView(context)
14 private val onTriggered by EventDispatcher()
15
16 init {
17 isClickable = true
18 isFocusable = true
19 setOnClickListener {
20 onTriggered(emptyMap<String, Any>())
21 }
22 addView(iconView, LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT))
23 }
24
25 fun setLabel(label: String) {
26 contentDescription = label
27 }
28
29 fun setSystemImage(systemImage: String) {
30 iconView.systemImage = systemImage
31 }
32}
33
34private class HeaderIconView(context: Context) : View(context) {
35 private val paint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
36 color = Color.parseColor("#6B7280")
37 strokeCap = Paint.Cap.ROUND
38 strokeJoin = Paint.Join.ROUND
39 strokeWidth = 3f * resources.displayMetrics.density
40 style = Paint.Style.STROKE
41 }
42
43 var systemImage: String = "gearshape"
44 set(value) {
45 field = value
46 invalidate()
47 }
48
49 override fun onDraw(canvas: Canvas) {
50 super.onDraw(canvas)
51 val cx = width / 2f
52 val cy = height / 2f
53 val size = minOf(width, height).toFloat()
54 if (systemImage == "square.and.pencil") {
55 drawNewTask(canvas, cx, cy, size)
56 } else {
57 drawSettings(canvas, cx, cy, size)
58 }
59 }
60
61 private fun drawSettings(canvas: Canvas, cx: Float, cy: Float, size: Float) {
62 val radius = size * 0.12f
63 canvas.drawCircle(cx, cy, radius, paint)
64 for (index in 0 until 8) {
65 val angle = Math.PI * index / 4.0
66 val inner = size * 0.19f
67 val outer = size * 0.27f
68 val sx = cx + kotlin.math.cos(angle).toFloat() * inner
69 val sy = cy + kotlin.math.sin(angle).toFloat() * inner
70 val ex = cx + kotlin.math.cos(angle).toFloat() * outer
71 val ey = cy + kotlin.math.sin(angle).toFloat() * outer
72 canvas.drawLine(sx, sy, ex, ey, paint)
73 }
74 }
75
76 private fun drawNewTask(canvas: Canvas, cx: Float, cy: Float, size: Float) {
77 val left = cx - size * 0.2f
78 val top = cy - size * 0.16f
79 val right = cx + size * 0.14f
80 val bottom = cy + size * 0.2f
81 canvas.drawRoundRect(left, top, right, bottom, size * 0.04f, size * 0.04f, paint)
82 canvas.drawLine(
83 cx - size * 0.02f,
84 cy + size * 0.13f,
85 cx + size * 0.24f,
86 cy - size * 0.13f,
87 paint
88 )
89 canvas.drawLine(
90 cx + size * 0.17f,
91 cy - size * 0.2f,
92 cx + size * 0.24f,
93 cy - size * 0.13f,
94 paint
95 )
96 }
97}
apps/mobile/modules/t3-native-controls/android/src/main/java/expo/modules/t3nativecontrols/T3NativeControlsModule.kt-11
......@@ -32,16 +32,5 @@ class T3NativeControlsModule : Module() {
3232 ?.resolve("t3-showcase-ready")
3333 ?.writeText(scene)
3434 }
35
36 View(T3HeaderButtonView::class) {
37 Prop("label") { view: T3HeaderButtonView, label: String ->
38 view.setLabel(label)
39 }
40 Prop("systemImage") { view: T3HeaderButtonView, systemImage: String ->
41 view.setSystemImage(systemImage)
42 }
43
44 Events("onTriggered")
45 }
4635 }
4736}
apps/mobile/modules/t3-native-controls/ios/T3HeaderButtonView.swift deleted-62
......@@ -1,62 +0,0 @@
1import ExpoModulesCore
2import UIKit
3
4public final class T3HeaderButtonView: ExpoView {
5 private static let size: CGFloat = 44
6 private static let symbolSize: CGFloat = 18
7
8 private let button = UIButton(type: .system)
9 private var systemImage = "circle"
10
11 let onTriggered = EventDispatcher()
12
13 public required init(appContext: AppContext? = nil) {
14 super.init(appContext: appContext)
15
16 isAccessibilityElement = false
17 button.frame = bounds
18 button.autoresizingMask = [.flexibleWidth, .flexibleHeight]
19 button.addTarget(self, action: #selector(handlePress), for: .primaryActionTriggered)
20 addSubview(button)
21 applyConfiguration()
22 }
23
24 public override var intrinsicContentSize: CGSize {
25 CGSize(width: Self.size, height: Self.size)
26 }
27
28 public func setLabel(_ label: String) {
29 button.accessibilityLabel = label
30 }
31
32 public func setSystemImage(_ systemImage: String) {
33 guard self.systemImage != systemImage else {
34 return
35 }
36 self.systemImage = systemImage
37 applyConfiguration()
38 }
39
40 private func applyConfiguration() {
41 var configuration: UIButton.Configuration
42 if #available(iOS 26.0, *) {
43 configuration = .glass()
44 configuration.cornerStyle = .capsule
45 } else {
46 configuration = .plain()
47 }
48
49 configuration.baseForegroundColor = .label
50 configuration.contentInsets = .zero
51 configuration.image = UIImage(systemName: systemImage)
52 configuration.preferredSymbolConfigurationForImage = UIImage.SymbolConfiguration(
53 pointSize: Self.symbolSize,
54 weight: .regular
55 )
56 button.configuration = configuration
57 }
58
59 @objc private func handlePress() {
60 onTriggered()
61 }
62}
apps/mobile/modules/t3-native-controls/ios/T3NativeControlsModule.swift-11
......@@ -87,16 +87,5 @@ public final class T3NativeControlsModule: Module {
8787 let readyPath = NSHomeDirectory() + "/Library/Caches/T3ShowcaseReadyScene"
8888 try? scene.write(toFile: readyPath, atomically: true, encoding: .utf8)
8989 }
90
91 View(T3HeaderButtonView.self) {
92 Prop("label") { (view: T3HeaderButtonView, label: String) in
93 view.setLabel(label)
94 }
95 Prop("systemImage") { (view: T3HeaderButtonView, systemImage: String) in
96 view.setSystemImage(systemImage)
97 }
98
99 Events("onTriggered")
100 }
10190 }
10291}
apps/mobile/src/features/threads/sidebar-header-actions.android.tsx deleted-16
......@@ -1,16 +0,0 @@
1import { View } from "react-native";
2
3import { T3HeaderButton } from "../../native/T3HeaderButton.android";
4import type { SidebarHeaderActionsProps } from "./sidebar-header-actions";
5
6export function SidebarHeaderActions(props: SidebarHeaderActionsProps) {
7 return (
8 <View className="h-11 flex-row gap-1">
9 <T3HeaderButton
10 accessibilityLabel="Open settings"
11 icon="gearshape"
12 onPress={props.onOpenSettings}
13 />
14 </View>
15 );
16}
apps/mobile/src/native/T3HeaderButton.android.tsx deleted-26
......@@ -1,26 +0,0 @@
1import { requireNativeView } from "expo";
2import type { NativeSyntheticEvent, StyleProp, ViewProps, ViewStyle } from "react-native";
3
4interface NativeHeaderButtonProps extends ViewProps {
5 readonly label: string;
6 readonly systemImage: "gearshape" | "square.and.pencil";
7 readonly onTriggered: (event: NativeSyntheticEvent<Record<string, never>>) => void;
8}
9
10const NativeHeaderButton = requireNativeView<NativeHeaderButtonProps>("T3NativeControls");
11
12export function T3HeaderButton(props: {
13 readonly accessibilityLabel: string;
14 readonly icon: NativeHeaderButtonProps["systemImage"];
15 readonly onPress: () => void;
16 readonly style?: StyleProp<ViewStyle>;
17}) {
18 return (
19 <NativeHeaderButton
20 label={props.accessibilityLabel}
21 onTriggered={props.onPress}
22 style={[{ width: 44, height: 44 }, props.style]}
23 systemImage={props.icon}
24 />
25 );
26}