Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 5de22d2

Browse files
authoredJun 9, 2023
Add Simple Compose theme in common-ui module (simpledotorg#4714)
* Add common module * Bump Compose version * Bump Material library version * Add compose theme adapter dependency * Add compose dependencies in common module * Add common module as dependency in app module * Move theme related XML files to common module * Add `SimpleThemeAdapter` to fetch theme information from app theme.xml * Add Simple theme variants * Rename private `SimpleTheme` function to `BaseSimpleTheme` * Add lint.xml to `common` module * Add manifest in `common` module to get the theme for Compose preview tooling * Add platform text style and line height style to parsed typography in Simple theme * Rename `common` module to `common-ui` * Update CHANGELOG
1 parent 43a720b commit 5de22d2

40 files changed

+517
-26
lines changed
 

‎CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
- Update Questionnaire form radio button styling
99
- Remove firebase remote config `questionnaires_enabled` logic for loading questionnaire's api.
1010
- Remove DPH/TamilNadu build variant
11+
- Add Simple Compose theme in `common-ui` module
1112

1213
### Fixes
1314

‎app/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -420,6 +420,7 @@ dependencies {
420420
implementation(projects.mobiusBase)
421421
implementation(projects.simplePlatform)
422422
implementation(projects.simpleVisuals)
423+
implementation(projects.commonUi)
423424

424425
val composeBom = platform(libs.androidx.compose.bom)
425426
implementation(composeBom)

‎app/src/main/res/values/theme_attr.xml

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

‎common-ui/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
/build

‎common-ui/build.gradle.kts

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
@Suppress("DSL_SCOPE_VIOLATION") // TODO: Remove once KTIJ-19369 is fixed
2+
plugins {
3+
id("com.android.library")
4+
id("org.jetbrains.kotlin.android")
5+
}
6+
7+
android {
8+
val compileSdkVersion: Int by rootProject.extra
9+
val minSdkVersion: Int by rootProject.extra
10+
11+
namespace = "org.simple.clinic.common"
12+
compileSdk = compileSdkVersion
13+
14+
defaultConfig {
15+
minSdk = minSdkVersion
16+
17+
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
18+
consumerProguardFiles("consumer-rules.pro")
19+
}
20+
21+
buildTypes {
22+
release {
23+
isMinifyEnabled = false
24+
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
25+
}
26+
}
27+
compileOptions {
28+
sourceCompatibility = JavaVersion.VERSION_17
29+
targetCompatibility = JavaVersion.VERSION_17
30+
}
31+
kotlinOptions {
32+
jvmTarget = JavaVersion.VERSION_17.toString()
33+
}
34+
35+
buildFeatures {
36+
compose = true
37+
}
38+
39+
composeOptions {
40+
kotlinCompilerExtensionVersion = libs.versions.compose.compiler.get()
41+
}
42+
}
43+
44+
dependencies {
45+
implementation(libs.androidx.core.ktx)
46+
implementation(libs.androidx.appcompat)
47+
implementation(libs.material)
48+
implementation(libs.edittext.pinentry)
49+
50+
val composeBom = platform(libs.androidx.compose.bom)
51+
implementation(composeBom)
52+
implementation(libs.androidx.compose.material)
53+
implementation(libs.androidx.compose.material.iconsExtended)
54+
implementation(libs.composeThemeAdapter)
55+
implementation(libs.composeThemeAdapterCore)
56+
implementation(libs.androidx.compose.ui.tooling.preview)
57+
debugImplementation(libs.androidx.compose.ui.tooling)
58+
}

‎common-ui/consumer-rules.pro

Whitespace-only changes.

‎common-ui/lint.xml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<lint>
3+
<issue id="UnusedResources">
4+
<ignore regexp="thanks_green_500" />
5+
<ignore regexp="thanks_green_600" />
6+
<ignore regexp="thanks_teal_500" />
7+
</issue>
8+
</lint>

‎common-ui/proguard-rules.pro

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# Add project specific ProGuard rules here.
2+
# You can control the set of applied configuration files using the
3+
# proguardFiles setting in build.gradle.
4+
#
5+
# For more details, see
6+
# http://developer.android.com/guide/developing/tools/proguard.html
7+
8+
# If your project uses WebView with JS, uncomment the following
9+
# and specify the fully qualified class name to the JavaScript interface
10+
# class:
11+
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12+
# public *;
13+
#}
14+
15+
# Uncomment this to preserve the line number information for
16+
# debugging stack traces.
17+
#-keepattributes SourceFile,LineNumberTable
18+
19+
# If you keep the line number information, uncomment this to
20+
# hide the original source file name.
21+
#-renamesourcefileattribute SourceFile
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
3+
4+
<application android:theme="@style/Theme.Simple" />
5+
</manifest>
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package org.simple.clinic.common.ui.theme
2+
3+
import androidx.compose.material.Colors
4+
import androidx.compose.material.lightColors
5+
import androidx.compose.runtime.Immutable
6+
import androidx.compose.runtime.staticCompositionLocalOf
7+
import androidx.compose.ui.graphics.Color
8+
9+
@Immutable
10+
data class SimpleColors(
11+
val toolbarPrimary: Color = Color.Unspecified,
12+
val toolbarPrimaryVariant: Color = Color.Unspecified,
13+
val onToolbarPrimary: Color = Color.Unspecified,
14+
val material: Colors = lightColors()
15+
)
16+
17+
internal val LocalSimpleColors = staticCompositionLocalOf { SimpleColors() }
Lines changed: 348 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,348 @@
1+
package org.simple.clinic.common.ui.theme
2+
3+
import android.content.Context
4+
import android.content.res.TypedArray
5+
import androidx.compose.material.Colors
6+
import androidx.compose.material.MaterialTheme
7+
import androidx.compose.material.Shapes
8+
import androidx.compose.material.Typography
9+
import androidx.compose.runtime.Composable
10+
import androidx.compose.runtime.CompositionLocalProvider
11+
import androidx.compose.runtime.ReadOnlyComposable
12+
import androidx.compose.ui.platform.LocalContext
13+
import androidx.compose.ui.platform.LocalDensity
14+
import androidx.compose.ui.platform.LocalLayoutDirection
15+
import androidx.compose.ui.res.colorResource
16+
import androidx.compose.ui.text.PlatformTextStyle
17+
import androidx.compose.ui.text.style.LineHeightStyle
18+
import androidx.compose.ui.unit.Density
19+
import androidx.core.content.res.getResourceIdOrThrow
20+
import androidx.core.content.res.use
21+
import com.google.accompanist.themeadapter.core.parseColor
22+
import com.google.accompanist.themeadapter.core.parseTextAppearance
23+
import com.google.accompanist.themeadapter.material.createMdcTheme
24+
import org.simple.clinic.common.R
25+
26+
@Composable
27+
fun SimpleTheme(content: @Composable () -> Unit) {
28+
SimpleThemeAdapter(content = content)
29+
}
30+
31+
@Composable
32+
fun SimpleInverseTheme(content: @Composable () -> Unit) {
33+
SimpleThemeAdapter {
34+
val colors =
35+
SimpleTheme.colors.copy(
36+
material =
37+
SimpleTheme.colors.material.copy(
38+
primary = colorResource(id = R.color.simple_light_blue_100),
39+
onPrimary = colorResource(id = R.color.simple_light_blue_500)
40+
)
41+
)
42+
43+
BaseSimpleTheme(colors = colors, typography = SimpleTheme.typography, content = content)
44+
}
45+
}
46+
47+
@Composable
48+
fun SimpleGreenTheme(content: @Composable () -> Unit) {
49+
SimpleThemeAdapter {
50+
val colors =
51+
SimpleTheme.colors.copy(
52+
material =
53+
SimpleTheme.colors.material.copy(
54+
primary = colorResource(id = R.color.simple_green_500),
55+
onPrimary = colorResource(id = R.color.white)
56+
)
57+
)
58+
59+
BaseSimpleTheme(colors = colors, typography = SimpleTheme.typography, content = content)
60+
}
61+
}
62+
63+
@Composable
64+
fun SimpleRedTheme(content: @Composable () -> Unit) {
65+
SimpleThemeAdapter {
66+
val colors =
67+
SimpleTheme.colors.copy(
68+
material =
69+
SimpleTheme.colors.material.copy(
70+
primary = colorResource(id = R.color.simple_red_500),
71+
onPrimary = colorResource(id = R.color.white)
72+
)
73+
)
74+
75+
BaseSimpleTheme(colors = colors, typography = SimpleTheme.typography, content = content)
76+
}
77+
}
78+
79+
@Composable
80+
fun SimpleRedInverseTheme(content: @Composable () -> Unit) {
81+
SimpleThemeAdapter {
82+
val colors =
83+
SimpleTheme.colors.copy(
84+
material =
85+
SimpleTheme.colors.material.copy(
86+
primary = colorResource(id = R.color.simple_red_100),
87+
onPrimary = colorResource(id = R.color.simple_red_500)
88+
)
89+
)
90+
91+
BaseSimpleTheme(colors = colors, typography = SimpleTheme.typography, content = content)
92+
}
93+
}
94+
95+
@Composable
96+
private fun SimpleThemeAdapter(content: @Composable () -> Unit) {
97+
val context = LocalContext.current
98+
val layoutDirection = LocalLayoutDirection.current
99+
val density = LocalDensity.current
100+
101+
val materialThemeParameters = createMdcTheme(context, layoutDirection)
102+
103+
requireNotNull(materialThemeParameters.colors)
104+
requireNotNull(materialThemeParameters.typography)
105+
requireNotNull(materialThemeParameters.shapes)
106+
107+
val simpleThemeParameters =
108+
context.obtainStyledAttributes(R.styleable.SimpleThemeAttrs).use { ta ->
109+
val colors = parseSimpleColors(ta, materialThemeParameters.colors!!)
110+
val typography =
111+
parseSimpleTypography(context, ta, density, materialThemeParameters.typography!!)
112+
113+
SimpleThemeParameters(colors = colors, typography = typography)
114+
}
115+
116+
BaseSimpleTheme(
117+
content = content,
118+
colors = simpleThemeParameters.colors,
119+
typography = simpleThemeParameters.typography
120+
)
121+
}
122+
123+
@Composable
124+
private fun parseSimpleColors(ta: TypedArray, materialColors: Colors) =
125+
SimpleColors(
126+
toolbarPrimary = ta.parseColor(R.styleable.SimpleThemeAttrs_colorToolbarPrimary),
127+
toolbarPrimaryVariant = ta.parseColor(R.styleable.SimpleThemeAttrs_colorToolbarPrimaryVariant),
128+
onToolbarPrimary = ta.parseColor(R.styleable.SimpleThemeAttrs_colorOnToolbarPrimary),
129+
material = materialColors
130+
)
131+
132+
@Composable
133+
private fun parseSimpleTypography(
134+
context: Context,
135+
ta: TypedArray,
136+
density: Density,
137+
materialTypography: Typography
138+
): SimpleTypography {
139+
val typography =
140+
materialTypography.copy(
141+
h1 =
142+
materialTypography.h1.copy(
143+
platformStyle = platformTextStyle,
144+
lineHeightStyle = lineHeightStyle
145+
),
146+
h2 =
147+
materialTypography.h2.copy(
148+
platformStyle = platformTextStyle,
149+
lineHeightStyle = lineHeightStyle
150+
),
151+
h3 =
152+
materialTypography.h3.copy(
153+
platformStyle = platformTextStyle,
154+
lineHeightStyle = lineHeightStyle
155+
),
156+
h4 =
157+
materialTypography.h4.copy(
158+
platformStyle = platformTextStyle,
159+
lineHeightStyle = lineHeightStyle
160+
),
161+
h5 =
162+
materialTypography.h5.copy(
163+
platformStyle = platformTextStyle,
164+
lineHeightStyle = lineHeightStyle
165+
),
166+
h6 =
167+
materialTypography.h6.copy(
168+
platformStyle = platformTextStyle,
169+
lineHeightStyle = lineHeightStyle
170+
),
171+
subtitle1 =
172+
materialTypography.subtitle1.copy(
173+
platformStyle = platformTextStyle,
174+
lineHeightStyle = lineHeightStyle
175+
),
176+
subtitle2 =
177+
materialTypography.subtitle2.copy(
178+
platformStyle = platformTextStyle,
179+
lineHeightStyle = lineHeightStyle
180+
),
181+
body1 =
182+
materialTypography.body1.copy(
183+
platformStyle = platformTextStyle,
184+
lineHeightStyle = lineHeightStyle
185+
),
186+
body2 =
187+
materialTypography.body2.copy(
188+
platformStyle = platformTextStyle,
189+
lineHeightStyle = lineHeightStyle
190+
),
191+
button =
192+
materialTypography.button.copy(
193+
platformStyle = platformTextStyle,
194+
lineHeightStyle = lineHeightStyle
195+
),
196+
caption =
197+
materialTypography.caption.copy(
198+
platformStyle = platformTextStyle,
199+
lineHeightStyle = lineHeightStyle
200+
),
201+
overline =
202+
materialTypography.overline.copy(
203+
platformStyle = platformTextStyle,
204+
lineHeightStyle = lineHeightStyle
205+
)
206+
)
207+
208+
return SimpleTypography(
209+
h5Numeric =
210+
parseTextAppearance(
211+
context,
212+
ta.getResourceIdOrThrow(R.styleable.SimpleThemeAttrs_textAppearanceHeadline5Numeric),
213+
density,
214+
false,
215+
null
216+
)
217+
.copy(platformStyle = platformTextStyle, lineHeightStyle = lineHeightStyle),
218+
h6Numeric =
219+
parseTextAppearance(
220+
context,
221+
ta.getResourceIdOrThrow(R.styleable.SimpleThemeAttrs_textAppearanceHeadline6Numeric),
222+
density,
223+
false,
224+
null
225+
)
226+
.copy(platformStyle = platformTextStyle, lineHeightStyle = lineHeightStyle),
227+
subtitle1Medium =
228+
parseTextAppearance(
229+
context,
230+
ta.getResourceIdOrThrow(R.styleable.SimpleThemeAttrs_textAppearanceSubtitle1Medium),
231+
density,
232+
false,
233+
null
234+
)
235+
.copy(platformStyle = platformTextStyle, lineHeightStyle = lineHeightStyle),
236+
body0 =
237+
parseTextAppearance(
238+
context,
239+
ta.getResourceIdOrThrow(R.styleable.SimpleThemeAttrs_textAppearanceBody0),
240+
density,
241+
false,
242+
null
243+
)
244+
.copy(platformStyle = platformTextStyle, lineHeightStyle = lineHeightStyle),
245+
body0Medium =
246+
parseTextAppearance(
247+
context,
248+
ta.getResourceIdOrThrow(R.styleable.SimpleThemeAttrs_textAppearanceBody0Medium),
249+
density,
250+
false,
251+
null
252+
)
253+
.copy(platformStyle = platformTextStyle, lineHeightStyle = lineHeightStyle),
254+
body0Numeric =
255+
parseTextAppearance(
256+
context,
257+
ta.getResourceIdOrThrow(R.styleable.SimpleThemeAttrs_textAppearanceBody0Numeric),
258+
density,
259+
false,
260+
null
261+
)
262+
.copy(platformStyle = platformTextStyle, lineHeightStyle = lineHeightStyle),
263+
body1Numeric =
264+
parseTextAppearance(
265+
context,
266+
ta.getResourceIdOrThrow(R.styleable.SimpleThemeAttrs_textAppearanceBody1Numeric),
267+
density,
268+
false,
269+
null
270+
)
271+
.copy(platformStyle = platformTextStyle, lineHeightStyle = lineHeightStyle),
272+
body2Numeric =
273+
parseTextAppearance(
274+
context,
275+
ta.getResourceIdOrThrow(R.styleable.SimpleThemeAttrs_textAppearanceBody2Numeric),
276+
density,
277+
false,
278+
null
279+
)
280+
.copy(platformStyle = platformTextStyle, lineHeightStyle = lineHeightStyle),
281+
body2Bold =
282+
parseTextAppearance(
283+
context,
284+
ta.getResourceIdOrThrow(R.styleable.SimpleThemeAttrs_textAppearanceBody2Bold),
285+
density,
286+
false,
287+
null
288+
)
289+
.copy(platformStyle = platformTextStyle, lineHeightStyle = lineHeightStyle),
290+
buttonBig =
291+
parseTextAppearance(
292+
context,
293+
ta.getResourceIdOrThrow(R.styleable.SimpleThemeAttrs_textAppearanceButtonBig),
294+
density,
295+
false,
296+
null
297+
)
298+
.copy(platformStyle = platformTextStyle, lineHeightStyle = lineHeightStyle),
299+
tag =
300+
parseTextAppearance(
301+
context,
302+
ta.getResourceIdOrThrow(R.styleable.SimpleThemeAttrs_textAppearanceTag),
303+
density,
304+
false,
305+
null
306+
)
307+
.copy(platformStyle = platformTextStyle, lineHeightStyle = lineHeightStyle),
308+
material = typography
309+
)
310+
}
311+
312+
@Composable
313+
private fun BaseSimpleTheme(
314+
content: @Composable () -> Unit,
315+
colors: SimpleColors,
316+
typography: SimpleTypography
317+
) {
318+
MaterialTheme(colors = colors.material, typography = typography.material) {
319+
CompositionLocalProvider(
320+
LocalSimpleColors provides colors,
321+
LocalSimpleTypography provides typography
322+
) {
323+
content()
324+
}
325+
}
326+
}
327+
328+
object SimpleTheme {
329+
330+
val colors: SimpleColors
331+
@Composable @ReadOnlyComposable get() = LocalSimpleColors.current
332+
333+
val typography: SimpleTypography
334+
@Composable @ReadOnlyComposable get() = LocalSimpleTypography.current
335+
336+
val shapes: Shapes
337+
@Composable @ReadOnlyComposable get() = MaterialTheme.shapes
338+
}
339+
340+
private data class SimpleThemeParameters(
341+
val colors: SimpleColors,
342+
val typography: SimpleTypography
343+
)
344+
345+
private val platformTextStyle = PlatformTextStyle(includeFontPadding = false)
346+
347+
private val lineHeightStyle =
348+
LineHeightStyle(alignment = LineHeightStyle.Alignment.Center, trim = LineHeightStyle.Trim.None)
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package org.simple.clinic.common.ui.theme
2+
3+
import androidx.compose.material.Typography
4+
import androidx.compose.runtime.Immutable
5+
import androidx.compose.runtime.staticCompositionLocalOf
6+
import androidx.compose.ui.text.TextStyle
7+
8+
@Immutable
9+
data class SimpleTypography(
10+
val h5Numeric: TextStyle = TextStyle.Default,
11+
val h6Numeric: TextStyle = TextStyle.Default,
12+
val subtitle1Medium: TextStyle = TextStyle.Default,
13+
val body0: TextStyle = TextStyle.Default,
14+
val body0Medium: TextStyle = TextStyle.Default,
15+
val body0Numeric: TextStyle = TextStyle.Default,
16+
val body1Numeric: TextStyle = TextStyle.Default,
17+
val body2Numeric: TextStyle = TextStyle.Default,
18+
val body2Bold: TextStyle = TextStyle.Default,
19+
val buttonBig: TextStyle = TextStyle.Default,
20+
val tag: TextStyle = TextStyle.Default,
21+
val material: Typography = Typography()
22+
)
23+
24+
internal val LocalSimpleTypography = staticCompositionLocalOf { SimpleTypography() }
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<resources>
3+
<declare-styleable name="SimpleThemeAttrs">
4+
<!-- Colors -->
5+
<attr name="colorToolbarPrimary" format="color" />
6+
<attr name="colorToolbarPrimaryVariant" format="color" />
7+
<attr name="colorOnToolbarPrimary" format="color" />
8+
9+
<!-- Text Appearances -->
10+
<attr name="textAppearanceHeadline5Numeric" format="reference" />
11+
<attr name="textAppearanceHeadline6Numeric" format="reference" />
12+
<attr name="textAppearanceSubtitle1Medium" format="reference" />
13+
<attr name="textAppearanceBody0" format="reference" />
14+
<attr name="textAppearanceBody0Medium" format="reference" />
15+
<attr name="textAppearanceBody0Numeric" format="reference" />
16+
<attr name="textAppearanceBody1Numeric" format="reference" />
17+
<attr name="textAppearanceBody2Numeric" format="reference" />
18+
<attr name="textAppearanceBody2Bold" format="reference" />
19+
<attr name="textAppearanceButtonBig" format="reference" />
20+
<attr name="textAppearanceTag" format="reference" />
21+
22+
<!-- Styles -->
23+
<attr name="borderlessButtonDenseStyle" format="reference" />
24+
</declare-styleable>
25+
</resources>

‎gradle/libs.versions.toml

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,9 @@ coroutines = "1.6.4"
3232

3333
compose-compiler = "1.4.0"
3434

35-
androidx-compose-bom = "2023.01.00"
35+
androidx-compose-bom = "2023.05.01"
36+
37+
composeThemeAdapter = "0.31.3-beta"
3638

3739
[libraries]
3840
android-gradle-plugin = "com.android.tools.build:gradle:8.0.2"
@@ -123,7 +125,7 @@ logback-classic = "ch.qos.logback:logback-classic:1.2.11"
123125

124126
lottie = "com.airbnb.android:lottie:5.2.0"
125127

126-
material = "com.google.android.material:material:1.7.0"
128+
material = "com.google.android.material:material:1.9.0"
127129

128130
mixpanel-android = "com.mixpanel.android:mixpanel-android:7.2.1"
129131

@@ -202,6 +204,8 @@ androidx-compose-material = { module = "androidx.compose.material:material" }
202204
androidx-compose-material-iconsExtended = { module = "androidx.compose.material:material-icons-extended" }
203205
androidx-compose-material3 = { module = "androidx.compose.material3:material3" }
204206
androidx-compose-materialWindow = { module = "androidx.compose.material3:material3-window-size-class" }
207+
composeThemeAdapter = { module = "com.google.accompanist:accompanist-themeadapter-material", version.ref = "composeThemeAdapter" }
208+
composeThemeAdapterCore = { module = "com.google.accompanist:accompanist-themeadapter-core", version.ref = "composeThemeAdapter" }
205209

206210
[bundles]
207211
androidx-camera = ["androidx-camera-core", "androidx-camera-camera2", "androidx-camera-view", "androidx-camera-lifecycle"]

‎settings.gradle.kts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,5 +18,6 @@ include(
1818
":simple-platform",
1919
":simple-visuals",
2020
":lint",
21-
":sharedTestCode"
21+
":sharedTestCode",
22+
":common-ui"
2223
)

0 commit comments

Comments
 (0)
Please sign in to comment.