Skip to content

Commit

Permalink
Merge pull request #106 from azrael8576/chore/lint
Browse files Browse the repository at this point in the history
Enforce `resourcePrefix` on Android library modules
  • Loading branch information
azrael8576 authored Feb 15, 2024
2 parents 2a3f479 + 996a836 commit 872e4ef
Show file tree
Hide file tree
Showing 33 changed files with 355 additions and 246 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,12 @@ internal fun homeEndToEndRobot(
internal open class HomeEndToEndRobot(
private val composeTestRule: AndroidComposeTestRule<ActivityScenarioRule<MainActivity>, MainActivity>,
) {
private fun AndroidComposeTestRule<*, *>.stringResource(@StringRes resId: Int) =
ReadOnlyProperty<Any?, String> { _, _ -> activity.getString(resId) }
private fun AndroidComposeTestRule<*, *>.stringResource(
@StringRes resId: Int,
) = ReadOnlyProperty<Any?, String> { _, _ -> activity.getString(resId) }

// The strings used for matching in these tests
private val importantNoteDescription by composeTestRule.stringResource(FeatureHomeR.string.important_note)
private val importantNoteDescription by composeTestRule.stringResource(FeatureHomeR.string.feature_home_important_note)

private val importantNote by lazy {
composeTestRule.onNode(hasContentDescription(importantNoteDescription))
Expand Down
4 changes: 4 additions & 0 deletions build-logic/convention/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,10 @@ gradlePlugin {
id = "pq.android.application.flavors"
implementationClass = "AndroidApplicationFlavorsConventionPlugin"
}
register("androidLint") {
id = "pq.android.lint"
implementationClass = "AndroidLintConventionPlugin"
}
register("jvmLibrary") {
id = "pq.jvm.library"
implementationClass = "JvmLibraryConventionPlugin"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import com.android.build.api.dsl.ApplicationExtension
import com.wei.picquest.configureGradleManagedDevices
import com.android.build.api.variant.ApplicationAndroidComponentsExtension
import com.wei.picquest.configureGradleManagedDevices
import com.wei.picquest.configureKotlinAndroid
import com.wei.picquest.configurePrintApksTask
import org.gradle.api.Plugin
Expand All @@ -13,6 +13,7 @@ class AndroidApplicationConventionPlugin : Plugin<Project> {
with(pluginManager) {
apply("com.android.application")
apply("org.jetbrains.kotlin.android")
apply("pq.android.lint")
}

extensions.configure<ApplicationExtension> {
Expand All @@ -25,5 +26,4 @@ class AndroidApplicationConventionPlugin : Plugin<Project> {
}
}
}

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,19 @@ class AndroidLibraryConventionPlugin : Plugin<Project> {
with(pluginManager) {
apply("com.android.library")
apply("org.jetbrains.kotlin.android")
apply("pq.android.lint")
}

extensions.configure<LibraryExtension> {
configureKotlinAndroid(this)
defaultConfig.targetSdk = 34
configureFlavors(this)
configureGradleManagedDevices(this)
// The resource prefix is derived from the module name,
// so resources inside ":core:module1" must be prefixed with "core_module1_"
resourcePrefix =
path.split("""\W""".toRegex()).drop(1).distinct().joinToString(separator = "_")
.lowercase() + "_"
}
extensions.configure<LibraryAndroidComponentsExtension> {
configurePrintApksTask(this)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import com.android.build.api.dsl.ApplicationExtension
import com.android.build.api.dsl.LibraryExtension
import com.android.build.api.dsl.Lint
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.kotlin.dsl.configure

class AndroidLintConventionPlugin : Plugin<Project> {
override fun apply(target: Project) {
with(target) {
when {
pluginManager.hasPlugin("com.android.application") ->
configure<ApplicationExtension> { lint(Lint::configure) }

pluginManager.hasPlugin("com.android.library") ->
configure<LibraryExtension> { lint(Lint::configure) }

else -> {
pluginManager.apply("com.android.lint")
configure<Lint>(Lint::configure)
}
}
}
}
}

private fun Lint.configure() {
xmlReport = true
checkDependencies = true
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ class JvmLibraryConventionPlugin : Plugin<Project> {
with(target) {
with(pluginManager) {
apply("org.jetbrains.kotlin.jvm")
apply("pq.android.lint")
}
configureKotlinJvm()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ import kotlin.properties.ReadOnlyProperty
* https://developer.android.com/jetpack/compose/testing-cheatsheet
*/
class UiTextTest {

/**
* 通常我們使用 createComposeRule(),作為 composeTestRule
*
Expand All @@ -31,13 +30,14 @@ class UiTextTest {
@get:Rule
val composeTestRule = createAndroidComposeRule<ComponentActivity>()

private fun AndroidComposeTestRule<*, *>.stringResource(@StringRes resId: Int) =
ReadOnlyProperty<Any?, String> { _, _ -> activity.getString(resId) }
private fun AndroidComposeTestRule<*, *>.stringResource(
@StringRes resId: Int,
) = ReadOnlyProperty<Any?, String> { _, _ -> activity.getString(resId) }

// The strings used for matching in these tests
private val testString by composeTestRule.stringResource(R.string.generic_hello)
private val formattedStringSingle by composeTestRule.stringResource(R.string.greeting_with_name)
private val formattedStringMultiple by composeTestRule.stringResource(R.string.greeting_with_name_and_weather)
private val testString by composeTestRule.stringResource(R.string.core_common_generic_hello)
private val formattedStringSingle by composeTestRule.stringResource(R.string.core_common_greeting_with_name)
private val formattedStringMultiple by composeTestRule.stringResource(R.string.core_common_greeting_with_name_and_weather)

@Composable
fun TestUiTextContent(uiText: UiText): String {
Expand All @@ -64,7 +64,7 @@ class UiTextTest {
*/
@Test
fun stringResource_returnsExpectedValue_withoutArgs() {
val uiText = UiText.StringResource(R.string.generic_hello)
val uiText = UiText.StringResource(R.string.core_common_generic_hello)

composeTestRule.setContent {
TestUiTextContent(uiText)
Expand All @@ -79,10 +79,11 @@ class UiTextTest {
@Test
fun stringResource_returnsExpectedValue_withSingleArg() {
val argName = "Alice"
val uiText = UiText.StringResource(
R.string.greeting_with_name,
listOf(UiText.StringResource.Args.DynamicString(argName)),
)
val uiText =
UiText.StringResource(
R.string.core_common_greeting_with_name,
listOf(UiText.StringResource.Args.DynamicString(argName)),
)

composeTestRule.setContent {
TestUiTextContent(uiText)
Expand All @@ -102,13 +103,14 @@ class UiTextTest {
fun stringResource_returnsExpectedValue_withMultipleArgs() {
val argName = "Alice"
val argWeather = "sunny"
val uiText = UiText.StringResource(
R.string.greeting_with_name_and_weather,
listOf(
UiText.StringResource.Args.DynamicString(argName),
UiText.StringResource.Args.DynamicString(argWeather),
),
)
val uiText =
UiText.StringResource(
R.string.core_common_greeting_with_name_and_weather,
listOf(
UiText.StringResource.Args.DynamicString(argName),
UiText.StringResource.Args.DynamicString(argWeather),
),
)

composeTestRule.setContent {
TestUiTextContent(uiText)
Expand All @@ -129,13 +131,14 @@ class UiTextTest {
fun stringResource_returnsExpectedValue_withNestedUiTextArg() {
val argName = UiText.DynamicString("Alice")
val argWeather = "sunny"
val uiText = UiText.StringResource(
R.string.greeting_with_name_and_weather,
listOf(
UiText.StringResource.Args.UiTextArg(argName),
UiText.StringResource.Args.DynamicString(argWeather),
),
)
val uiText =
UiText.StringResource(
R.string.core_common_greeting_with_name_and_weather,
listOf(
UiText.StringResource.Args.UiTextArg(argName),
UiText.StringResource.Args.DynamicString(argWeather),
),
)

composeTestRule.setContent {
TestUiTextContent(uiText)
Expand Down
6 changes: 3 additions & 3 deletions core/common/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="generic_hello" translatable="false">Hello</string>
<string name="greeting_with_name" translatable="false">Hello, %s!</string>
<string name="greeting_with_name_and_weather" translatable="false">Hello, %1$s! It\'s a %2$s day today.</string>
<string name="core_common_generic_hello" translatable="false">Hello</string>
<string name="core_common_greeting_with_name" translatable="false">Hello, %s!</string>
<string name="core_common_greeting_with_name_and_weather" translatable="false">Hello, %1$s! It\'s a %2$s day today.</string>
</resources>
Original file line number Diff line number Diff line change
@@ -1,42 +1,63 @@
package com.wei.picquest.core.datastore

import android.util.Log
import androidx.datastore.core.DataStore
import com.wei.picquest.core.model.data.DarkThemeConfig
import com.wei.picquest.core.model.data.LanguageConfig
import com.wei.picquest.core.model.data.ThemeBrand
import com.wei.picquest.core.model.data.UserData
import kotlinx.coroutines.flow.map
import timber.log.Timber
import java.io.IOException
import javax.inject.Inject

class PqPreferencesDataSource @Inject constructor(
class PqPreferencesDataSource
@Inject
constructor(
private val userPreferences: DataStore<UserPreferences>,
) {
val userData = userPreferences.data.map {
UserData(
isFirstTimeUser = it.isFirstTimeUser,
tokenString = it.tokenString,
userName = it.userName,
useDynamicColor = it.useDynamicColor,
themeBrand = when (it.themeBrand) {
null, ThemeBrandProto.THEME_BRAND_UNSPECIFIED, ThemeBrandProto.UNRECOGNIZED, ThemeBrandProto.THEME_BRAND_DEFAULT -> ThemeBrand.DEFAULT
ThemeBrandProto.THEME_BRAND_ANDROID -> ThemeBrand.ANDROID
},
darkThemeConfig = when (it.darkThemeConfig) {
null, DarkThemeConfigProto.DARK_THEME_CONFIG_UNSPECIFIED, DarkThemeConfigProto.UNRECOGNIZED, DarkThemeConfigProto.DARK_THEME_CONFIG_FOLLOW_SYSTEM -> DarkThemeConfig.FOLLOW_SYSTEM
DarkThemeConfigProto.DARK_THEME_CONFIG_LIGHT -> DarkThemeConfig.LIGHT
DarkThemeConfigProto.DARK_THEME_CONFIG_DARK -> DarkThemeConfig.DARK
},
languageConfig = when (it.languageConfig) {
null, LanguageConfigProto.LANGUAGE_CONFIG_UNSPECIFIED, LanguageConfigProto.UNRECOGNIZED, LanguageConfigProto.LANGUAGE_CONFIG_FOLLOW_SYSTEM -> LanguageConfig.FOLLOW_SYSTEM
LanguageConfigProto.LANGUAGE_CONFIG_ENGLISH -> LanguageConfig.ENGLISH
LanguageConfigProto.LANGUAGE_CONFIG_CHINESE -> LanguageConfig.CHINESE
},
recentSearchPhotoQueries = it.recentSearchPhotoQueriesList,
recentSearchVideoQueries = it.recentSearchVideoQueriesList,
)
}
val userData =
userPreferences.data.map {
UserData(
isFirstTimeUser = it.isFirstTimeUser,
tokenString = it.tokenString,
userName = it.userName,
useDynamicColor = it.useDynamicColor,
themeBrand =
when (it.themeBrand) {
null,
ThemeBrandProto.THEME_BRAND_UNSPECIFIED,
ThemeBrandProto.UNRECOGNIZED,
ThemeBrandProto.THEME_BRAND_DEFAULT,
-> ThemeBrand.DEFAULT

ThemeBrandProto.THEME_BRAND_ANDROID -> ThemeBrand.ANDROID
},
darkThemeConfig =
when (it.darkThemeConfig) {
null,
DarkThemeConfigProto.DARK_THEME_CONFIG_UNSPECIFIED,
DarkThemeConfigProto.UNRECOGNIZED,
DarkThemeConfigProto.DARK_THEME_CONFIG_FOLLOW_SYSTEM,
-> DarkThemeConfig.FOLLOW_SYSTEM

DarkThemeConfigProto.DARK_THEME_CONFIG_LIGHT -> DarkThemeConfig.LIGHT
DarkThemeConfigProto.DARK_THEME_CONFIG_DARK -> DarkThemeConfig.DARK
},
languageConfig =
when (it.languageConfig) {
null,
LanguageConfigProto.LANGUAGE_CONFIG_UNSPECIFIED,
LanguageConfigProto.UNRECOGNIZED,
LanguageConfigProto.LANGUAGE_CONFIG_FOLLOW_SYSTEM,
-> LanguageConfig.FOLLOW_SYSTEM

LanguageConfigProto.LANGUAGE_CONFIG_ENGLISH -> LanguageConfig.ENGLISH
LanguageConfigProto.LANGUAGE_CONFIG_CHINESE -> LanguageConfig.CHINESE
},
recentSearchPhotoQueries = it.recentSearchPhotoQueriesList,
recentSearchVideoQueries = it.recentSearchVideoQueriesList,
)
}

suspend fun setTokenString(tokenString: String) {
try {
Expand All @@ -46,26 +67,28 @@ class PqPreferencesDataSource @Inject constructor(
}
}
} catch (ioException: IOException) {
Log.e("PqPreferences", "Failed to update user preferences", ioException)
Timber.tag("PqPreferences").e(ioException, "Failed to update user preferences")
}
}

suspend fun addRecentSearchPhotoQuery(newQuery: String) {
try {
userPreferences.updateData { preferences ->
val updatedQueries = preferences.recentSearchPhotoQueriesList.toMutableList().apply {
// 移除既有相同的查詢(如果存在),然後添加到列表末尾
remove(newQuery)
add(newQuery)
if (size > 10) removeAt(0)
}
val updatedQueries =
preferences.recentSearchPhotoQueriesList.toMutableList().apply {
// 移除既有相同的查詢(如果存在),然後添加到列表末尾
remove(newQuery)
add(newQuery)
if (size > 10) removeAt(0)
}
preferences.toBuilder()
.clearRecentSearchPhotoQueries()
.addAllRecentSearchPhotoQueries(updatedQueries)
.build()
}
} catch (ioException: IOException) {
Log.e("PqPreferencesDataSource", "Failed to update recent search photo queries", ioException)
Timber.tag("PqPreferencesDataSource")
.e(ioException, "Failed to update recent search photo queries")
}
}

Expand All @@ -77,26 +100,29 @@ class PqPreferencesDataSource @Inject constructor(
.build()
}
} catch (ioException: IOException) {
Log.e("PqPreferencesDataSource", "Failed to clear recent search photo queries", ioException)
Timber.tag("PqPreferencesDataSource")
.e(ioException, "Failed to clear recent search photo queries")
}
}

suspend fun addRecentSearchVideoQuery(newQuery: String) {
try {
userPreferences.updateData { preferences ->
val updatedQueries = preferences.recentSearchVideoQueriesList.toMutableList().apply {
// 移除既有相同的查詢(如果存在),然後添加到列表末尾
remove(newQuery)
add(newQuery)
if (size > 10) removeAt(0)
}
val updatedQueries =
preferences.recentSearchVideoQueriesList.toMutableList().apply {
// 移除既有相同的查詢(如果存在),然後添加到列表末尾
remove(newQuery)
add(newQuery)
if (size > 10) removeAt(0)
}
preferences.toBuilder()
.clearRecentSearchVideoQueries()
.addAllRecentSearchVideoQueries(updatedQueries)
.build()
}
} catch (ioException: IOException) {
Log.e("PqPreferencesDataSource", "Failed to update recent search video queries", ioException)
Timber.tag("PqPreferencesDataSource")
.e(ioException, "Failed to update recent search video queries")
}
}

Expand All @@ -108,7 +134,8 @@ class PqPreferencesDataSource @Inject constructor(
.build()
}
} catch (ioException: IOException) {
Log.e("PqPreferencesDataSource", "Failed to clear recent search video queries", ioException)
Timber.tag("PqPreferencesDataSource")
.e(ioException, "Failed to clear recent search video queries")
}
}
}
Loading

0 comments on commit 872e4ef

Please sign in to comment.