clox-view-switcher 0.1.2 → 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -39,4 +39,10 @@ android {
39
39
  dependencies {
40
40
  implementation project(':expo-modules-core')
41
41
  implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.9.23"
42
+
43
+ // Image loading
44
+ implementation "io.coil-kt:coil:2.5.0"
45
+
46
+ // For rounded corners and shadows
47
+ implementation "androidx.cardview:cardview:1.0.0"
42
48
  }
@@ -0,0 +1,197 @@
1
+ package expo.modules.cloxviewswitcher
2
+
3
+ import android.content.Context
4
+ import android.graphics.Color
5
+ import android.graphics.Typeface
6
+ import android.graphics.drawable.GradientDrawable
7
+ import android.util.TypedValue
8
+ import android.view.Gravity
9
+ import android.view.View
10
+ import android.widget.FrameLayout
11
+ import android.widget.ImageView
12
+ import android.widget.LinearLayout
13
+ import android.widget.TextView
14
+ import coil.load
15
+ import coil.transform.RoundedCornersTransformation
16
+
17
+ data class AppItem(
18
+ val id: String,
19
+ val title: String,
20
+ val image: String,
21
+ val icon: String
22
+ )
23
+
24
+ /**
25
+ * AppCardView - Matches iOS AppCardView exactly
26
+ *
27
+ * Layout (from top to bottom):
28
+ * - Header HStack (icon + title) - 30pt height area
29
+ * - Card screenshot - 260x540
30
+ * Total frame: 260x580
31
+ *
32
+ * Header has x offset of 15
33
+ */
34
+ class AppCardView(
35
+ context: Context,
36
+ val item: AppItem,
37
+ private var backgroundColor: Int = Color.WHITE,
38
+ private var borderRadius: Float = 20f,
39
+ private var titleColor: Int = Color.WHITE,
40
+ private var titleSize: Float = 16f,
41
+ private var titleWeight: Int = Typeface.BOLD
42
+ ) : LinearLayout(context) {
43
+
44
+ // iOS dimensions (in points, need to convert to pixels)
45
+ companion object {
46
+ const val CARD_WIDTH_DP = 260f
47
+ const val CARD_HEIGHT_DP = 540f
48
+ const val TOTAL_HEIGHT_DP = 580f
49
+ const val HEADER_HEIGHT_DP = 40f // Space for header (580 - 540 = 40)
50
+ const val ICON_SIZE_DP = 30f
51
+ const val ICON_CORNER_DP = 6f
52
+ const val HEADER_OFFSET_X_DP = 15f
53
+ }
54
+
55
+ private val density = context.resources.displayMetrics.density
56
+
57
+ // Card dimensions in pixels
58
+ val cardWidthPx = (CARD_WIDTH_DP * density).toInt()
59
+ val cardHeightPx = (CARD_HEIGHT_DP * density).toInt()
60
+ val totalHeightPx = (TOTAL_HEIGHT_DP * density).toInt()
61
+
62
+ private val headerContainer: LinearLayout
63
+ private val iconImageView: ImageView
64
+ private val titleTextView: TextView
65
+ private val cardContainer: FrameLayout
66
+ private val cardImageView: ImageView
67
+
68
+ var onCardPress: ((String) -> Unit)? = null
69
+
70
+ init {
71
+ orientation = VERTICAL
72
+ layoutParams = LayoutParams(cardWidthPx, totalHeightPx)
73
+ setBackgroundColor(Color.TRANSPARENT)
74
+
75
+ // ==========================================
76
+ // HEADER (icon + title) - comes FIRST at TOP
77
+ // ==========================================
78
+ headerContainer = LinearLayout(context).apply {
79
+ orientation = HORIZONTAL
80
+ gravity = Gravity.CENTER_VERTICAL
81
+ layoutParams = LayoutParams(
82
+ LayoutParams.MATCH_PARENT,
83
+ (HEADER_HEIGHT_DP * density).toInt()
84
+ )
85
+ // Header has x offset of 15 (like iOS .offset(x: 15))
86
+ setPadding((HEADER_OFFSET_X_DP * density).toInt(), 0, 0, 0)
87
+ }
88
+
89
+ // App icon - 30x30 with corner radius 6 (only show if icon is provided)
90
+ val iconSizePx = (ICON_SIZE_DP * density).toInt()
91
+ iconImageView = ImageView(context).apply {
92
+ layoutParams = LayoutParams(iconSizePx, iconSizePx).apply {
93
+ marginEnd = (8 * density).toInt()
94
+ }
95
+ scaleType = ImageView.ScaleType.CENTER_CROP
96
+ clipToOutline = true
97
+ // No background at all
98
+ background = null
99
+ }
100
+
101
+ // Only add icon to header if icon URL is provided
102
+ if (item.icon.isNotEmpty()) {
103
+ iconImageView.load(item.icon) {
104
+ crossfade(true)
105
+ transformations(RoundedCornersTransformation(ICON_CORNER_DP * density))
106
+ }
107
+ headerContainer.addView(iconImageView)
108
+ }
109
+ // If no icon, don't add iconImageView to header at all
110
+
111
+ // App title
112
+ titleTextView = TextView(context).apply {
113
+ text = item.title
114
+ setTextColor(titleColor)
115
+ setTextSize(TypedValue.COMPLEX_UNIT_SP, titleSize)
116
+ setTypeface(typeface, titleWeight)
117
+ }
118
+ headerContainer.addView(titleTextView)
119
+
120
+ addView(headerContainer)
121
+
122
+ // ==========================================
123
+ // CARD SCREENSHOT - comes AFTER header
124
+ // ==========================================
125
+ cardContainer = FrameLayout(context).apply {
126
+ layoutParams = LayoutParams(cardWidthPx, cardHeightPx)
127
+
128
+ // Background with rounded corners
129
+ val bgDrawable = GradientDrawable().apply {
130
+ setColor(backgroundColor)
131
+ cornerRadius = borderRadius * density
132
+ }
133
+ background = bgDrawable
134
+ clipToOutline = true
135
+ elevation = 6 * density // Shadow like iOS shadow(radius: 3)
136
+ }
137
+
138
+ cardImageView = ImageView(context).apply {
139
+ layoutParams = FrameLayout.LayoutParams(
140
+ FrameLayout.LayoutParams.MATCH_PARENT,
141
+ FrameLayout.LayoutParams.MATCH_PARENT
142
+ )
143
+ scaleType = ImageView.ScaleType.CENTER_CROP
144
+
145
+ if (item.image.isNotEmpty()) {
146
+ load(item.image) {
147
+ crossfade(true)
148
+ transformations(RoundedCornersTransformation(borderRadius * density))
149
+ }
150
+ }
151
+ }
152
+ cardContainer.addView(cardImageView)
153
+
154
+ addView(cardContainer)
155
+
156
+ // Click listener on entire card
157
+ setOnClickListener {
158
+ onCardPress?.invoke(item.id)
159
+ }
160
+ }
161
+
162
+ fun setTitleOpacity(opacity: Float) {
163
+ titleTextView.alpha = opacity
164
+ // Icon stays visible, only title opacity changes (matching iOS)
165
+ }
166
+
167
+ fun updateStyles(
168
+ newBackgroundColor: Int,
169
+ newBorderRadius: Float,
170
+ newTitleColor: Int,
171
+ newTitleSize: Float,
172
+ newTitleWeight: Int
173
+ ) {
174
+ backgroundColor = newBackgroundColor
175
+ borderRadius = newBorderRadius
176
+ titleColor = newTitleColor
177
+ titleSize = newTitleSize
178
+ titleWeight = newTitleWeight
179
+
180
+ // Update card background
181
+ val bgDrawable = GradientDrawable().apply {
182
+ setColor(backgroundColor)
183
+ cornerRadius = borderRadius * density
184
+ }
185
+ cardContainer.background = bgDrawable
186
+
187
+ // Icon has transparent background, no update needed
188
+
189
+ // Update title
190
+ titleTextView.setTextColor(titleColor)
191
+ titleTextView.setTextSize(TypedValue.COMPLEX_UNIT_SP, titleSize)
192
+ titleTextView.setTypeface(titleTextView.typeface, titleWeight)
193
+ }
194
+
195
+ fun getCardWidthDp(): Float = CARD_WIDTH_DP
196
+ fun getTotalHeightDp(): Float = TOTAL_HEIGHT_DP
197
+ }
@@ -7,15 +7,60 @@ class CloxViewSwitcherModule : Module() {
7
7
  override fun definition() = ModuleDefinition {
8
8
  Name("CloxViewSwitcher")
9
9
 
10
- Events("onChange")
10
+ Events("onChange", "onItemPress", "onCardChange", "onItemRemove")
11
11
 
12
12
  Function("hello") {
13
13
  "Hello World! 👋"
14
14
  }
15
15
 
16
- // Enables the module to be used as a native view.
17
16
  View(CloxViewSwitcherView::class) {
18
- // Props will be added here
17
+ // Items prop - array of app items
18
+ Prop("items") { view: CloxViewSwitcherView, items: List<Map<String, String>> ->
19
+ view.setItems(items)
20
+ }
21
+
22
+ // Spring animation response (duration)
23
+ Prop("springResponse") { view: CloxViewSwitcherView, value: Double ->
24
+ view.setSpringResponse(value)
25
+ }
26
+
27
+ // Spring animation damping fraction
28
+ Prop("springDamping") { view: CloxViewSwitcherView, value: Double ->
29
+ view.setSpringDamping(value)
30
+ }
31
+
32
+ // Scroll speed multiplier
33
+ Prop("scrollSpeed") { view: CloxViewSwitcherView, value: Double ->
34
+ view.setScrollSpeed(value)
35
+ }
36
+
37
+ // Card background color (placeholder before images load)
38
+ Prop("cardBackgroundColor") { view: CloxViewSwitcherView, value: String ->
39
+ view.setCardBackgroundColor(value)
40
+ }
41
+
42
+ // Card border radius
43
+ Prop("cardBorderRadius") { view: CloxViewSwitcherView, value: Double ->
44
+ view.setCardBorderRadius(value)
45
+ }
46
+
47
+ // Title font color
48
+ Prop("titleFontColor") { view: CloxViewSwitcherView, value: String ->
49
+ view.setTitleFontColor(value)
50
+ }
51
+
52
+ // Title font size
53
+ Prop("titleFontSize") { view: CloxViewSwitcherView, value: Double ->
54
+ view.setTitleFontSize(value)
55
+ }
56
+
57
+ // Title font weight
58
+ Prop("titleFontWeight") { view: CloxViewSwitcherView, value: String ->
59
+ view.setTitleFontWeight(value)
60
+ }
61
+
62
+ // Events
63
+ Events("onItemPress", "onCardChange", "onItemRemove")
19
64
  }
20
65
  }
21
66
  }