clox-view-switcher 0.1.1 → 0.1.3

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,200 @@
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
+
97
+ // Transparent background - no placeholder color
98
+ setBackgroundColor(Color.TRANSPARENT)
99
+ clipToOutline = true
100
+
101
+ if (item.icon.isNotEmpty()) {
102
+ visibility = View.VISIBLE
103
+ load(item.icon) {
104
+ crossfade(true)
105
+ transformations(RoundedCornersTransformation(ICON_CORNER_DP * density))
106
+ }
107
+ } else {
108
+ // Hide icon container if no icon provided
109
+ visibility = View.GONE
110
+ }
111
+ }
112
+ headerContainer.addView(iconImageView)
113
+
114
+ // App title
115
+ titleTextView = TextView(context).apply {
116
+ text = item.title
117
+ setTextColor(titleColor)
118
+ setTextSize(TypedValue.COMPLEX_UNIT_SP, titleSize)
119
+ setTypeface(typeface, titleWeight)
120
+ }
121
+ headerContainer.addView(titleTextView)
122
+
123
+ addView(headerContainer)
124
+
125
+ // ==========================================
126
+ // CARD SCREENSHOT - comes AFTER header
127
+ // ==========================================
128
+ cardContainer = FrameLayout(context).apply {
129
+ layoutParams = LayoutParams(cardWidthPx, cardHeightPx)
130
+
131
+ // Background with rounded corners
132
+ val bgDrawable = GradientDrawable().apply {
133
+ setColor(backgroundColor)
134
+ cornerRadius = borderRadius * density
135
+ }
136
+ background = bgDrawable
137
+ clipToOutline = true
138
+ elevation = 6 * density // Shadow like iOS shadow(radius: 3)
139
+ }
140
+
141
+ cardImageView = ImageView(context).apply {
142
+ layoutParams = FrameLayout.LayoutParams(
143
+ FrameLayout.LayoutParams.MATCH_PARENT,
144
+ FrameLayout.LayoutParams.MATCH_PARENT
145
+ )
146
+ scaleType = ImageView.ScaleType.CENTER_CROP
147
+
148
+ if (item.image.isNotEmpty()) {
149
+ load(item.image) {
150
+ crossfade(true)
151
+ transformations(RoundedCornersTransformation(borderRadius * density))
152
+ }
153
+ }
154
+ }
155
+ cardContainer.addView(cardImageView)
156
+
157
+ addView(cardContainer)
158
+
159
+ // Click listener on entire card
160
+ setOnClickListener {
161
+ onCardPress?.invoke(item.id)
162
+ }
163
+ }
164
+
165
+ fun setTitleOpacity(opacity: Float) {
166
+ titleTextView.alpha = opacity
167
+ // Icon stays visible, only title opacity changes (matching iOS)
168
+ }
169
+
170
+ fun updateStyles(
171
+ newBackgroundColor: Int,
172
+ newBorderRadius: Float,
173
+ newTitleColor: Int,
174
+ newTitleSize: Float,
175
+ newTitleWeight: Int
176
+ ) {
177
+ backgroundColor = newBackgroundColor
178
+ borderRadius = newBorderRadius
179
+ titleColor = newTitleColor
180
+ titleSize = newTitleSize
181
+ titleWeight = newTitleWeight
182
+
183
+ // Update card background
184
+ val bgDrawable = GradientDrawable().apply {
185
+ setColor(backgroundColor)
186
+ cornerRadius = borderRadius * density
187
+ }
188
+ cardContainer.background = bgDrawable
189
+
190
+ // Icon has transparent background, no update needed
191
+
192
+ // Update title
193
+ titleTextView.setTextColor(titleColor)
194
+ titleTextView.setTextSize(TypedValue.COMPLEX_UNIT_SP, titleSize)
195
+ titleTextView.setTypeface(titleTextView.typeface, titleWeight)
196
+ }
197
+
198
+ fun getCardWidthDp(): Float = CARD_WIDTH_DP
199
+ fun getTotalHeightDp(): Float = TOTAL_HEIGHT_DP
200
+ }
@@ -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
  }