From fc5b04991c49fd4e432529a9f058cb9351ccfb51 Mon Sep 17 00:00:00 2001 From: "Wei.He" Date: Thu, 15 Feb 2024 14:12:47 +0800 Subject: [PATCH 1/4] feat: Integrate tl.android.lint plugin for unified Lint configurations across modules (#122) --- build-logic/convention/build.gradle.kts | 4 +++ .../AndroidApplicationConventionPlugin.kt | 1 + .../kotlin/AndroidLibraryConventionPlugin.kt | 1 + .../kotlin/AndroidLintConventionPlugin.kt | 30 +++++++++++++++++++ .../main/kotlin/JvmLibraryConventionPlugin.kt | 1 + 5 files changed, 37 insertions(+) create mode 100644 build-logic/convention/src/main/kotlin/AndroidLintConventionPlugin.kt diff --git a/build-logic/convention/build.gradle.kts b/build-logic/convention/build.gradle.kts index 87b2f24a..149bdb43 100644 --- a/build-logic/convention/build.gradle.kts +++ b/build-logic/convention/build.gradle.kts @@ -58,6 +58,10 @@ gradlePlugin { id = "tl.android.application.flavors" implementationClass = "AndroidApplicationFlavorsConventionPlugin" } + register("androidLint") { + id = "tl.android.lint" + implementationClass = "AndroidLintConventionPlugin" + } register("jvmLibrary") { id = "tl.jvm.library" implementationClass = "JvmLibraryConventionPlugin" diff --git a/build-logic/convention/src/main/kotlin/AndroidApplicationConventionPlugin.kt b/build-logic/convention/src/main/kotlin/AndroidApplicationConventionPlugin.kt index ba334132..3d864caa 100644 --- a/build-logic/convention/src/main/kotlin/AndroidApplicationConventionPlugin.kt +++ b/build-logic/convention/src/main/kotlin/AndroidApplicationConventionPlugin.kt @@ -13,6 +13,7 @@ class AndroidApplicationConventionPlugin : Plugin { with(pluginManager) { apply("com.android.application") apply("org.jetbrains.kotlin.android") + apply("tl.android.lint") } extensions.configure { diff --git a/build-logic/convention/src/main/kotlin/AndroidLibraryConventionPlugin.kt b/build-logic/convention/src/main/kotlin/AndroidLibraryConventionPlugin.kt index 3a884934..e2f51e4c 100644 --- a/build-logic/convention/src/main/kotlin/AndroidLibraryConventionPlugin.kt +++ b/build-logic/convention/src/main/kotlin/AndroidLibraryConventionPlugin.kt @@ -18,6 +18,7 @@ class AndroidLibraryConventionPlugin : Plugin { with(pluginManager) { apply("com.android.library") apply("org.jetbrains.kotlin.android") + apply("tl.android.lint") } extensions.configure { diff --git a/build-logic/convention/src/main/kotlin/AndroidLintConventionPlugin.kt b/build-logic/convention/src/main/kotlin/AndroidLintConventionPlugin.kt new file mode 100644 index 00000000..54246d61 --- /dev/null +++ b/build-logic/convention/src/main/kotlin/AndroidLintConventionPlugin.kt @@ -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 { + override fun apply(target: Project) { + with(target) { + when { + pluginManager.hasPlugin("com.android.application") -> + configure { lint(Lint::configure) } + + pluginManager.hasPlugin("com.android.library") -> + configure { lint(Lint::configure) } + + else -> { + pluginManager.apply("com.android.lint") + configure(Lint::configure) + } + } + } + } +} + +private fun Lint.configure() { + xmlReport = true + checkDependencies = true +} diff --git a/build-logic/convention/src/main/kotlin/JvmLibraryConventionPlugin.kt b/build-logic/convention/src/main/kotlin/JvmLibraryConventionPlugin.kt index 2222e4fd..27469192 100644 --- a/build-logic/convention/src/main/kotlin/JvmLibraryConventionPlugin.kt +++ b/build-logic/convention/src/main/kotlin/JvmLibraryConventionPlugin.kt @@ -7,6 +7,7 @@ class JvmLibraryConventionPlugin : Plugin { with(target) { with(pluginManager) { apply("org.jetbrains.kotlin.jvm") + apply("tl.android.lint") } configureKotlinJvm() } From 17882bacd8f09ab21b05c5447efdf19b3b0eefb1 Mon Sep 17 00:00:00 2001 From: "Wei.He" Date: Thu, 15 Feb 2024 14:46:41 +0800 Subject: [PATCH 2/4] fix lint errors --- .../core/datastore/TlPreferencesDataSource.kt | 25 ++++++++++++++---- .../scrollflags/EnterAlwaysCollapsedState.kt | 6 ++--- .../topappbar/scrollflags/EnterAlwaysState.kt | 6 ++--- .../scrollflags/ExitUntilCollapsedState.kt | 6 ++--- .../topappbar/scrollflags/ScrollState.kt | 6 ++--- .../login/welcome/WelcomeScreenRobot.kt | 11 -------- .../main/res/drawable/welcome_background.jpg | Bin 0 -> 64962 bytes .../src/main/res/values-zh-rTW/strings.xml | 3 --- feature/login/src/main/res/values/strings.xml | 3 --- 9 files changed, 28 insertions(+), 38 deletions(-) create mode 100644 feature/login/src/main/res/drawable/welcome_background.jpg diff --git a/core/datastore/src/main/java/com/wei/teachlink/core/datastore/TlPreferencesDataSource.kt b/core/datastore/src/main/java/com/wei/teachlink/core/datastore/TlPreferencesDataSource.kt index 481f645a..abd7d611 100644 --- a/core/datastore/src/main/java/com/wei/teachlink/core/datastore/TlPreferencesDataSource.kt +++ b/core/datastore/src/main/java/com/wei/teachlink/core/datastore/TlPreferencesDataSource.kt @@ -1,12 +1,12 @@ package com.wei.teachlink.core.datastore -import android.util.Log import androidx.datastore.core.DataStore import com.wei.teachlink.core.model.data.DarkThemeConfig import com.wei.teachlink.core.model.data.LanguageConfig import com.wei.teachlink.core.model.data.ThemeBrand import com.wei.teachlink.core.model.data.UserData import kotlinx.coroutines.flow.map +import timber.log.Timber import java.io.IOException import javax.inject.Inject @@ -24,18 +24,33 @@ constructor( useDynamicColor = it.useDynamicColor, themeBrand = when (it.themeBrand) { - null, ThemeBrandProto.THEME_BRAND_UNSPECIFIED, ThemeBrandProto.UNRECOGNIZED, ThemeBrandProto.THEME_BRAND_DEFAULT -> ThemeBrand.DEFAULT + 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 + 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 + 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 }, @@ -50,7 +65,7 @@ constructor( } } } catch (ioException: IOException) { - Log.e("TlPreferences", "Failed to update user preferences", ioException) + Timber.tag("TlPreferences").e(ioException, "Failed to update user preferences") } } } diff --git a/core/designsystem/src/main/java/com/wei/teachlink/core/designsystem/management/states/topappbar/scrollflags/EnterAlwaysCollapsedState.kt b/core/designsystem/src/main/java/com/wei/teachlink/core/designsystem/management/states/topappbar/scrollflags/EnterAlwaysCollapsedState.kt index 48f84e90..89644c90 100644 --- a/core/designsystem/src/main/java/com/wei/teachlink/core/designsystem/management/states/topappbar/scrollflags/EnterAlwaysCollapsedState.kt +++ b/core/designsystem/src/main/java/com/wei/teachlink/core/designsystem/management/states/topappbar/scrollflags/EnterAlwaysCollapsedState.kt @@ -1,19 +1,17 @@ package com.wei.teachlink.core.designsystem.management.states.topappbar.scrollflags import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.saveable.mapSaver import androidx.compose.runtime.setValue -import androidx.compose.runtime.structuralEqualityPolicy import com.wei.teachlink.core.designsystem.management.states.topappbar.ScrollFlagState class EnterAlwaysCollapsedState( heightRange: IntRange, scrollOffset: Float = 0f, ) : ScrollFlagState(heightRange) { - override var mScrollOffset by mutableStateOf( + override var mScrollOffset by mutableFloatStateOf( value = scrollOffset.coerceIn(0f, maxHeight.toFloat()), - policy = structuralEqualityPolicy(), ) override val offset: Float diff --git a/core/designsystem/src/main/java/com/wei/teachlink/core/designsystem/management/states/topappbar/scrollflags/EnterAlwaysState.kt b/core/designsystem/src/main/java/com/wei/teachlink/core/designsystem/management/states/topappbar/scrollflags/EnterAlwaysState.kt index f38e6077..8bc96628 100644 --- a/core/designsystem/src/main/java/com/wei/teachlink/core/designsystem/management/states/topappbar/scrollflags/EnterAlwaysState.kt +++ b/core/designsystem/src/main/java/com/wei/teachlink/core/designsystem/management/states/topappbar/scrollflags/EnterAlwaysState.kt @@ -1,19 +1,17 @@ package com.wei.teachlink.core.designsystem.management.states.topappbar.scrollflags import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.saveable.mapSaver import androidx.compose.runtime.setValue -import androidx.compose.runtime.structuralEqualityPolicy import com.wei.teachlink.core.designsystem.management.states.topappbar.ScrollFlagState class EnterAlwaysState( heightRange: IntRange, scrollOffset: Float = 0f, ) : ScrollFlagState(heightRange) { - override var mScrollOffset by mutableStateOf( + override var mScrollOffset by mutableFloatStateOf( value = scrollOffset.coerceIn(0f, maxHeight.toFloat()), - policy = structuralEqualityPolicy(), ) override val offset: Float diff --git a/core/designsystem/src/main/java/com/wei/teachlink/core/designsystem/management/states/topappbar/scrollflags/ExitUntilCollapsedState.kt b/core/designsystem/src/main/java/com/wei/teachlink/core/designsystem/management/states/topappbar/scrollflags/ExitUntilCollapsedState.kt index 8f3b92f0..ef80da4b 100644 --- a/core/designsystem/src/main/java/com/wei/teachlink/core/designsystem/management/states/topappbar/scrollflags/ExitUntilCollapsedState.kt +++ b/core/designsystem/src/main/java/com/wei/teachlink/core/designsystem/management/states/topappbar/scrollflags/ExitUntilCollapsedState.kt @@ -1,19 +1,17 @@ package com.wei.teachlink.core.designsystem.management.states.topappbar.scrollflags import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.saveable.mapSaver import androidx.compose.runtime.setValue -import androidx.compose.runtime.structuralEqualityPolicy import com.wei.teachlink.core.designsystem.management.states.topappbar.FixedScrollFlagState class ExitUntilCollapsedState( heightRange: IntRange, scrollOffset: Float = 0f, ) : FixedScrollFlagState(heightRange) { - override var mScrollOffset by mutableStateOf( + override var mScrollOffset by mutableFloatStateOf( value = scrollOffset.coerceIn(0f, rangeDifference.toFloat()), - policy = structuralEqualityPolicy(), ) override var scrollOffset: Float diff --git a/core/designsystem/src/main/java/com/wei/teachlink/core/designsystem/management/states/topappbar/scrollflags/ScrollState.kt b/core/designsystem/src/main/java/com/wei/teachlink/core/designsystem/management/states/topappbar/scrollflags/ScrollState.kt index d8f36f9a..125ccc3a 100644 --- a/core/designsystem/src/main/java/com/wei/teachlink/core/designsystem/management/states/topappbar/scrollflags/ScrollState.kt +++ b/core/designsystem/src/main/java/com/wei/teachlink/core/designsystem/management/states/topappbar/scrollflags/ScrollState.kt @@ -1,19 +1,17 @@ package com.wei.teachlink.core.designsystem.management.states.topappbar.scrollflags import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.saveable.mapSaver import androidx.compose.runtime.setValue -import androidx.compose.runtime.structuralEqualityPolicy import com.wei.teachlink.core.designsystem.management.states.topappbar.ScrollFlagState class ScrollState( heightRange: IntRange, scrollOffset: Float = 0f, ) : ScrollFlagState(heightRange) { - override var mScrollOffset by mutableStateOf( + override var mScrollOffset by mutableFloatStateOf( value = scrollOffset.coerceIn(0f, maxHeight.toFloat()), - policy = structuralEqualityPolicy(), ) override val offset: Float diff --git a/feature/login/src/androidTest/java/com/wei/teachlink/feature/login/welcome/WelcomeScreenRobot.kt b/feature/login/src/androidTest/java/com/wei/teachlink/feature/login/welcome/WelcomeScreenRobot.kt index b5383e68..0c17af4a 100644 --- a/feature/login/src/androidTest/java/com/wei/teachlink/feature/login/welcome/WelcomeScreenRobot.kt +++ b/feature/login/src/androidTest/java/com/wei/teachlink/feature/login/welcome/WelcomeScreenRobot.kt @@ -36,7 +36,6 @@ internal open class WelcomeScreenRobot( private val scheduleListTag by composeTestRule.stringResource(R.string.tag_welcome_graphics) private val welcomeTitleString by composeTestRule.stringResource(R.string.welcome_title) - private val welcomeMessageString by composeTestRule.stringResource(R.string.welcome_message) private val getStartedString by composeTestRule.stringResource(R.string.get_started) private val welcomeGraphics by lazy { @@ -51,12 +50,6 @@ internal open class WelcomeScreenRobot( useUnmergedTree = true, ) } - private val welcomeMessage by lazy { - composeTestRule.onNodeWithContentDescription( - welcomeMessageString, - useUnmergedTree = true, - ) - } private val getStarted by lazy { composeTestRule.onNodeWithContentDescription( getStartedString, @@ -84,10 +77,6 @@ internal open class WelcomeScreenRobot( welcomeTitle.assertExists().assertIsDisplayed() } - fun verifyWelcomeMessageDisplayed() { - welcomeMessage.assertExists().assertIsDisplayed() - } - fun verifyGetStartedDisplayed() { getStarted.assertExists().assertIsDisplayed() } diff --git a/feature/login/src/main/res/drawable/welcome_background.jpg b/feature/login/src/main/res/drawable/welcome_background.jpg new file mode 100644 index 0000000000000000000000000000000000000000..c99034a8ec1b1e161c5e5ffc6fc52ee00110d94f GIT binary patch literal 64962 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�a(be?P%H*929Vml;bg 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 像好友一樣的 \n優質教師 - 連接全球的線上家教進行1對1課程。 - \n隨時隨地學習, - \n並根據您的需求定制課程! Get Started diff --git a/feature/login/src/main/res/values/strings.xml b/feature/login/src/main/res/values/strings.xml index 43d9969d..1ea6f8d8 100644 --- a/feature/login/src/main/res/values/strings.xml +++ b/feature/login/src/main/res/values/strings.xml @@ -3,9 +3,6 @@ Quality teachers \nwho are like friends - Access global online tutors for 1-on-1 sessions - \nLearn anytime, anywhere - \nCourses tailored just for you! Get Started From df3fc7d0e8eb7572cdae5a8d18d00a9eb2d043da Mon Sep 17 00:00:00 2001 From: "Wei.He" Date: Thu, 15 Feb 2024 15:20:28 +0800 Subject: [PATCH 3/4] Enforce resourcePrefix on Android library modules (fixed #122) ``` :core:network -> core_network_ :core:data -> core_data_ :core:common -> core_common_ :core:model -> core_model_ :core:domain -> core_domain_ :core:designsystem -> core_designsystem_ :core:testing -> core_testing_ :core:datastore -> core_datastore_ :core:datastore-proto -> core_datastore_proto_ :core:datastore-test -> core_datastore_test_ :core:data-test -> core_data_test_ :ui-test-hilt-manifest -> ui_test_hilt_manifest_ :feature:login -> feature_login_ :feature:teacherschedule -> feature_teacherschedule_ :feature:contactme -> feature_contactme_ :feature:home -> feature_home_ ``` --- .../com/wei/teachlink/ui/NavigationRobot.kt | 4 +- .../teachlink/ui/robot/HomeEndToEndRobot.kt | 2 +- .../teachlink/ui/robot/LoginEndToEndRobot.kt | 2 +- .../ui/robot/ScheduleEndToEndRobot.kt | 4 +- .../ui/robot/WelcomeEndToEndRobot.kt | 4 +- app/src/main/AndroidManifest.xml | 2 +- .../kotlin/AndroidLibraryConventionPlugin.kt | 5 + .../wei/teachlink/core/utils/UiTextTest.kt | 14 +-- core/common/src/main/res/values/strings.xml | 6 +- .../core/designsystem/component/UiExtras.kt | 4 +- .../src/main/res/values-zh-rTW/strings.xml | 4 +- .../src/main/res/values/strings.xml | 4 +- .../contactme/ContactMeScreenRobot.kt | 10 +- .../contactme/contactme/ContactMeScreen.kt | 78 ++++++------ ...e_wei.jpg => feature_contactme_he_wei.jpg} | Bin .../src/main/res/values-zh-rTW/strings.xml | 10 +- .../contactme/src/main/res/values/strings.xml | 10 +- .../feature/home/home/HomeScreenRobot.kt | 66 +++++----- .../teachlink/feature/home/home/HomeScreen.kt | 28 ++--- .../feature/home/home/MyCoursesContent.kt | 114 +++++++++--------- .../feature/home/home/ui/ContactListCard.kt | 38 +++--- .../feature/home/home/ui/HomeTabRow.kt | 38 +++--- .../feature/home/home/ui/HomeTopBar.kt | 50 ++++---- .../feature/home/home/ui/SkillProgressCard.kt | 32 ++--- .../feature/home/home/utilities/Constants.kt | 45 +++---- .../{he_wei.jpg => feature_home_he_wei.jpg} | Bin ..._01.webp => feature_home_img_face_01.webp} | Bin ..._02.webp => feature_home_img_face_02.webp} | Bin ..._03.webp => feature_home_img_face_03.webp} | Bin ...n.webp => feature_home_jamie_coleman.webp} | Bin .../src/main/res/values-zh-rTW/strings.xml | 32 ++--- feature/home/src/main/res/values/strings.xml | 58 ++++----- .../feature/login/login/LoginScreenRobot.kt | 10 +- .../login/welcome/WelcomeScreenRobot.kt | 6 +- .../feature/login/login/LoginScreen.kt | 46 +++---- .../feature/login/welcome/WelcomeScreen.kt | 86 ++++++------- ...g => feature_login_welcome_background.jpg} | Bin ...g => feature_login_welcome_background.jpg} | Bin ...g => feature_login_welcome_background.jpg} | Bin ...g => feature_login_welcome_background.jpg} | Bin ...g => feature_login_welcome_background.jpg} | Bin ...g => feature_login_welcome_background.jpg} | Bin ...{ic_logo.xml => feature_login_ic_logo.xml} | 0 ...g => feature_login_welcome_background.jpg} | Bin .../src/main/res/values-zh-rTW/strings.xml | 18 +-- feature/login/src/main/res/values/strings.xml | 20 +-- .../schedule/ScheduleScreenRobot.kt | 38 +++--- .../ScheduleDetailScreenRobot.kt | 18 +-- .../schedule/ScheduleScreen.kt | 114 +++++++++--------- .../teacherschedule/schedule/ui/TimeList.kt | 82 ++++++------- .../scheduledetail/ScheduleDetailScreen.kt | 74 ++++++------ .../src/main/res/values-zh-rTW/strings.xml | 40 +++--- .../src/main/res/values/strings.xml | 56 ++++----- .../schedule/ScheduleViewModelTest.kt | 36 +++--- 54 files changed, 662 insertions(+), 646 deletions(-) rename feature/contactme/src/main/res/drawable/{he_wei.jpg => feature_contactme_he_wei.jpg} (100%) rename feature/home/src/main/res/drawable/{he_wei.jpg => feature_home_he_wei.jpg} (100%) rename feature/home/src/main/res/drawable/{img_face_01.webp => feature_home_img_face_01.webp} (100%) rename feature/home/src/main/res/drawable/{img_face_02.webp => feature_home_img_face_02.webp} (100%) rename feature/home/src/main/res/drawable/{img_face_03.webp => feature_home_img_face_03.webp} (100%) rename feature/home/src/main/res/drawable/{jamie_coleman.webp => feature_home_jamie_coleman.webp} (100%) rename feature/login/src/main/res/drawable-land/{welcome_background.jpg => feature_login_welcome_background.jpg} (100%) rename feature/login/src/main/res/drawable-port/{welcome_background.jpg => feature_login_welcome_background.jpg} (100%) rename feature/login/src/main/res/drawable-w600dp-land/{welcome_background.jpg => feature_login_welcome_background.jpg} (100%) rename feature/login/src/main/res/drawable-w600dp-port/{welcome_background.jpg => feature_login_welcome_background.jpg} (100%) rename feature/login/src/main/res/drawable-w840dp-land/{welcome_background.jpg => feature_login_welcome_background.jpg} (100%) rename feature/login/src/main/res/drawable-w840dp-port/{welcome_background.jpg => feature_login_welcome_background.jpg} (100%) rename feature/login/src/main/res/drawable/{ic_logo.xml => feature_login_ic_logo.xml} (100%) rename feature/login/src/main/res/drawable/{welcome_background.jpg => feature_login_welcome_background.jpg} (100%) diff --git a/app/src/androidTest/java/com/wei/teachlink/ui/NavigationRobot.kt b/app/src/androidTest/java/com/wei/teachlink/ui/NavigationRobot.kt index 8357173c..38c06d4d 100644 --- a/app/src/androidTest/java/com/wei/teachlink/ui/NavigationRobot.kt +++ b/app/src/androidTest/java/com/wei/teachlink/ui/NavigationRobot.kt @@ -33,7 +33,9 @@ internal open class NavigationRobot( private val schedule by composeTestRule.stringResource(R.string.schedule) private val home by composeTestRule.stringResource(R.string.home) private val contactMe by composeTestRule.stringResource(R.string.contact_me) - private val backDescription by composeTestRule.stringResource(FeatureTeacherscheduleR.string.content_description_back) + private val backDescription by composeTestRule.stringResource( + FeatureTeacherscheduleR.string.feature_teacherschedule_content_description_back, + ) private val back by lazy { composeTestRule.onNodeWithContentDescription( diff --git a/app/src/androidTest/java/com/wei/teachlink/ui/robot/HomeEndToEndRobot.kt b/app/src/androidTest/java/com/wei/teachlink/ui/robot/HomeEndToEndRobot.kt index 47113a1c..c9460c15 100644 --- a/app/src/androidTest/java/com/wei/teachlink/ui/robot/HomeEndToEndRobot.kt +++ b/app/src/androidTest/java/com/wei/teachlink/ui/robot/HomeEndToEndRobot.kt @@ -41,7 +41,7 @@ internal open class HomeEndToEndRobot( } // The strings used for matching in these tests - private val menuDescription by composeTestRule.stringResource(FeatureHomeR.string.menu) + private val menuDescription by composeTestRule.stringResource(FeatureHomeR.string.feature_home_menu) private val menuButton by lazy { composeTestRule.onNode( diff --git a/app/src/androidTest/java/com/wei/teachlink/ui/robot/LoginEndToEndRobot.kt b/app/src/androidTest/java/com/wei/teachlink/ui/robot/LoginEndToEndRobot.kt index 48d99f24..e7606269 100644 --- a/app/src/androidTest/java/com/wei/teachlink/ui/robot/LoginEndToEndRobot.kt +++ b/app/src/androidTest/java/com/wei/teachlink/ui/robot/LoginEndToEndRobot.kt @@ -42,7 +42,7 @@ internal open class LoginEndToEndRobotRobot( } // The strings used for matching in these tests - private val loginDescription by composeTestRule.stringResource(FeatureLoginR.string.content_description_login) + private val loginDescription by composeTestRule.stringResource(FeatureLoginR.string.feature_login_content_description_login) private val loginButton by lazy { composeTestRule.onNode( diff --git a/app/src/androidTest/java/com/wei/teachlink/ui/robot/ScheduleEndToEndRobot.kt b/app/src/androidTest/java/com/wei/teachlink/ui/robot/ScheduleEndToEndRobot.kt index 7804e582..fcc4af78 100644 --- a/app/src/androidTest/java/com/wei/teachlink/ui/robot/ScheduleEndToEndRobot.kt +++ b/app/src/androidTest/java/com/wei/teachlink/ui/robot/ScheduleEndToEndRobot.kt @@ -31,7 +31,9 @@ internal open class ScheduleEndToEndRobot( ) = ReadOnlyProperty { _, _ -> activity.getString(resId) } // The strings used for matching in these tests - private val scheduleTopAppBarTag by composeTestRule.stringResource(FeatureTeacherScheduleR.string.tag_schedule_top_app_bar) + private val scheduleTopAppBarTag by composeTestRule.stringResource( + FeatureTeacherScheduleR.string.feature_teacherschedule_tag_schedule_top_app_bar, + ) private val scheduleTopAppBar by lazy { composeTestRule.onNodeWithTag( diff --git a/app/src/androidTest/java/com/wei/teachlink/ui/robot/WelcomeEndToEndRobot.kt b/app/src/androidTest/java/com/wei/teachlink/ui/robot/WelcomeEndToEndRobot.kt index 8f6e7ea0..bf5a9fb1 100644 --- a/app/src/androidTest/java/com/wei/teachlink/ui/robot/WelcomeEndToEndRobot.kt +++ b/app/src/androidTest/java/com/wei/teachlink/ui/robot/WelcomeEndToEndRobot.kt @@ -32,8 +32,8 @@ internal open class WelcomeEndToEndRobot( ) = ReadOnlyProperty { _, _ -> activity.getString(resId) } // The strings used for matching in these tests - private val welcomeTitleString by composeTestRule.stringResource(FeatureLoginR.string.welcome_title) - private val getStartedString by composeTestRule.stringResource(FeatureLoginR.string.get_started) + private val welcomeTitleString by composeTestRule.stringResource(FeatureLoginR.string.feature_login_welcome_title) + private val getStartedString by composeTestRule.stringResource(FeatureLoginR.string.feature_login_get_started) private val welcomeTitle by lazy { composeTestRule.onNodeWithContentDescription( diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 81c7a140..e112b970 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -12,7 +12,7 @@ android:dataExtractionRules="@xml/data_extraction_rules" android:fullBackupContent="@xml/backup_rules" android:icon="@mipmap/ic_launcher" - android:label="@string/app_name" + android:label="@string/feature_teacherschedule_app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/Theme.Tl.Splash" diff --git a/build-logic/convention/src/main/kotlin/AndroidLibraryConventionPlugin.kt b/build-logic/convention/src/main/kotlin/AndroidLibraryConventionPlugin.kt index e2f51e4c..f06c1975 100644 --- a/build-logic/convention/src/main/kotlin/AndroidLibraryConventionPlugin.kt +++ b/build-logic/convention/src/main/kotlin/AndroidLibraryConventionPlugin.kt @@ -26,6 +26,11 @@ class AndroidLibraryConventionPlugin : Plugin { 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 { configurePrintApksTask(this) diff --git a/core/common/src/androidTest/java/com/wei/teachlink/core/utils/UiTextTest.kt b/core/common/src/androidTest/java/com/wei/teachlink/core/utils/UiTextTest.kt index 738bd18b..ecfc5ad3 100644 --- a/core/common/src/androidTest/java/com/wei/teachlink/core/utils/UiTextTest.kt +++ b/core/common/src/androidTest/java/com/wei/teachlink/core/utils/UiTextTest.kt @@ -35,9 +35,9 @@ class UiTextTest { ) = ReadOnlyProperty { _, _ -> 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 { @@ -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) @@ -81,7 +81,7 @@ class UiTextTest { val argName = "Alice" val uiText = UiText.StringResource( - R.string.greeting_with_name, + R.string.core_common_greeting_with_name, listOf(UiText.StringResource.Args.DynamicString(argName)), ) @@ -105,7 +105,7 @@ class UiTextTest { val argWeather = "sunny" val uiText = UiText.StringResource( - R.string.greeting_with_name_and_weather, + R.string.core_common_greeting_with_name_and_weather, listOf( UiText.StringResource.Args.DynamicString(argName), UiText.StringResource.Args.DynamicString(argWeather), @@ -133,7 +133,7 @@ class UiTextTest { val argWeather = "sunny" val uiText = UiText.StringResource( - R.string.greeting_with_name_and_weather, + R.string.core_common_greeting_with_name_and_weather, listOf( UiText.StringResource.Args.UiTextArg(argName), UiText.StringResource.Args.DynamicString(argWeather), diff --git a/core/common/src/main/res/values/strings.xml b/core/common/src/main/res/values/strings.xml index 22a6c469..0aa1652a 100644 --- a/core/common/src/main/res/values/strings.xml +++ b/core/common/src/main/res/values/strings.xml @@ -1,6 +1,6 @@ - Hello - Hello, %s! - Hello, %1$s! It\'s a %2$s day today. + Hello + Hello, %s! + Hello, %1$s! It\'s a %2$s day today. \ No newline at end of file diff --git a/core/designsystem/src/main/java/com/wei/teachlink/core/designsystem/component/UiExtras.kt b/core/designsystem/src/main/java/com/wei/teachlink/core/designsystem/component/UiExtras.kt index 8082e9ca..c705135e 100644 --- a/core/designsystem/src/main/java/com/wei/teachlink/core/designsystem/component/UiExtras.kt +++ b/core/designsystem/src/main/java/com/wei/teachlink/core/designsystem/component/UiExtras.kt @@ -16,7 +16,7 @@ fun FunctionalityNotAvailablePopup(onDismiss: () -> Unit) { AlertDialog( onDismissRequest = onDismiss, text = { - val functionalityNotAvailable = stringResource(R.string.functionality_not_available) + val functionalityNotAvailable = stringResource(R.string.core_designsystem_functionality_not_available) Text( text = functionalityNotAvailable, style = MaterialTheme.typography.bodyMedium, @@ -24,7 +24,7 @@ fun FunctionalityNotAvailablePopup(onDismiss: () -> Unit) { ) }, confirmButton = { - val close = stringResource(id = R.string.close) + val close = stringResource(id = R.string.core_designsystem_close) TextButton(onClick = onDismiss) { Text( text = close, diff --git a/core/designsystem/src/main/res/values-zh-rTW/strings.xml b/core/designsystem/src/main/res/values-zh-rTW/strings.xml index b1ff95dd..b320e7e4 100644 --- a/core/designsystem/src/main/res/values-zh-rTW/strings.xml +++ b/core/designsystem/src/main/res/values-zh-rTW/strings.xml @@ -1,5 +1,5 @@ - CLOSE - 功能尚未上線 🙈 + CLOSE + 功能尚未上線 🙈 \ No newline at end of file diff --git a/core/designsystem/src/main/res/values/strings.xml b/core/designsystem/src/main/res/values/strings.xml index be1ea8f3..fa1aeea9 100644 --- a/core/designsystem/src/main/res/values/strings.xml +++ b/core/designsystem/src/main/res/values/strings.xml @@ -1,5 +1,5 @@ - CLOSE - Functionality not available 🙈 + CLOSE + Functionality not available 🙈 \ No newline at end of file diff --git a/feature/contactme/src/androidTest/java/com/wei/teachlink/feature/contactme/contactme/ContactMeScreenRobot.kt b/feature/contactme/src/androidTest/java/com/wei/teachlink/feature/contactme/contactme/ContactMeScreenRobot.kt index 70ea9c40..75d6522d 100644 --- a/feature/contactme/src/androidTest/java/com/wei/teachlink/feature/contactme/contactme/ContactMeScreenRobot.kt +++ b/feature/contactme/src/androidTest/java/com/wei/teachlink/feature/contactme/contactme/ContactMeScreenRobot.kt @@ -42,11 +42,11 @@ internal open class ContactMeScreenRobot( @StringRes resId: Int, ) = ReadOnlyProperty { _, _ -> activity.getString(resId) } - private val profilePictureDescription by composeTestRule.stringResource(R.string.profile_picture) - private val linkedinString by composeTestRule.stringResource(R.string.linkedin) - private val emailString by composeTestRule.stringResource(R.string.email) - private val timezoneString by composeTestRule.stringResource(R.string.timezone) - private val callDescription by composeTestRule.stringResource(R.string.call) + private val profilePictureDescription by composeTestRule.stringResource(R.string.feature_contactme_profile_picture) + private val linkedinString by composeTestRule.stringResource(R.string.feature_contactme_linkedin) + private val emailString by composeTestRule.stringResource(R.string.feature_contactme_email) + private val timezoneString by composeTestRule.stringResource(R.string.feature_contactme_timezone) + private val callDescription by composeTestRule.stringResource(R.string.feature_contactme_call) private val profilePicture by lazy { composeTestRule.onNodeWithContentDescription( diff --git a/feature/contactme/src/main/java/com/wei/teachlink/feature/contactme/contactme/ContactMeScreen.kt b/feature/contactme/src/main/java/com/wei/teachlink/feature/contactme/contactme/ContactMeScreen.kt index 23080758..13e33f0a 100644 --- a/feature/contactme/src/main/java/com/wei/teachlink/feature/contactme/contactme/ContactMeScreen.kt +++ b/feature/contactme/src/main/java/com/wei/teachlink/feature/contactme/contactme/ContactMeScreen.kt @@ -125,8 +125,8 @@ internal fun ContactMeScreen( ) { Surface( modifier = - Modifier - .fillMaxSize(), + Modifier + .fillMaxSize(), ) { if (contentType == TlContentType.DUAL_PANE) { Box( @@ -155,19 +155,19 @@ internal fun ContactMeScreen( ) }, strategy = - HorizontalTwoPaneStrategy( - splitFraction = 0.5f, - gapWidth = SPACING_LARGE.dp, - ), + HorizontalTwoPaneStrategy( + splitFraction = 0.5f, + gapWidth = SPACING_LARGE.dp, + ), displayFeatures = displayFeatures, ) } } else { Box( modifier = - Modifier - .fillMaxSize() - .background(color = MaterialTheme.colorScheme.background), + Modifier + .fillMaxSize() + .background(color = MaterialTheme.colorScheme.background), contentAlignment = Alignment.Center, ) { DecorativeBackgroundText( @@ -301,18 +301,18 @@ internal fun DisplayHeadShot( name: String, isPreview: Boolean, ) { - val resId = R.drawable.he_wei + val resId = R.drawable.feature_contactme_he_wei val painter = coilImagePainter(resId, isPreview) - val profilePictureDescription = stringResource(R.string.profile_picture).format(name) + val profilePictureDescription = stringResource(R.string.feature_contactme_profile_picture).format(name) Image( painter = painter, contentDescription = profilePictureDescription, modifier = - modifier - .clip(CircleShape) - .size(300.dp) - .border(2.dp, MaterialTheme.colorScheme.onBackground, CircleShape), + modifier + .clip(CircleShape) + .size(300.dp) + .border(2.dp, MaterialTheme.colorScheme.onBackground, CircleShape), ) } @@ -324,19 +324,19 @@ fun ContactMeCard( ) { Card( modifier = - modifier - .padding(horizontal = SPACING_EXTRA_LARGE.dp) - .clip(CardDefaults.shape), + modifier + .padding(horizontal = SPACING_EXTRA_LARGE.dp) + .clip(CardDefaults.shape), colors = - CardDefaults.cardColors( - containerColor = MaterialTheme.colorScheme.surfaceVariant, - ), + CardDefaults.cardColors( + containerColor = MaterialTheme.colorScheme.surfaceVariant, + ), ) { Column( modifier = - Modifier - .padding(SPACING_LARGE.dp) - .fillMaxWidth(), + Modifier + .padding(SPACING_LARGE.dp) + .fillMaxWidth(), ) { Row( modifier = Modifier.fillMaxWidth(), @@ -352,17 +352,17 @@ fun ContactMeCard( ) } ProfileProperty( - label = stringResource(id = R.string.linkedin), + label = stringResource(id = R.string.feature_contactme_linkedin), value = uiStates.linkedinUrl, isLink = true, ) ProfileProperty( - label = stringResource(id = R.string.email), + label = stringResource(id = R.string.feature_contactme_email), value = uiStates.email, isLink = true, ) ProfileProperty( - label = stringResource(id = R.string.timezone), + label = stringResource(id = R.string.feature_contactme_timezone), value = uiStates.timeZone, isLink = false, ) @@ -384,9 +384,9 @@ private fun NameAndPosition( text = name, style = MaterialTheme.typography.headlineSmall, modifier = - Modifier - .baselineHeight(32.dp) - .semantics { contentDescription = name }, + Modifier + .baselineHeight(32.dp) + .semantics { contentDescription = name }, ) val position = uiStates.position Text( @@ -394,10 +394,10 @@ private fun NameAndPosition( style = MaterialTheme.typography.bodyLarge, color = MaterialTheme.colorScheme.onSurfaceVariant, modifier = - Modifier - .padding(bottom = 20.dp) - .baselineHeight(SPACING_EXTRA_LARGE.dp) - .semantics { contentDescription = position }, + Modifier + .padding(bottom = 20.dp) + .baselineHeight(SPACING_EXTRA_LARGE.dp) + .semantics { contentDescription = position }, ) } } @@ -417,17 +417,17 @@ private fun PhoneButton( ) } - val phoneDescription = stringResource(id = R.string.call).format(name) + val phoneDescription = stringResource(id = R.string.feature_contactme_call).format(name) IconButton( onClick = { showPopup.value = true onPhoneClick() }, modifier = - Modifier - .clip(CircleShape) - .background(MaterialTheme.colorScheme.surface) - .semantics { contentDescription = phoneDescription }, + Modifier + .clip(CircleShape) + .background(MaterialTheme.colorScheme.surface) + .semantics { contentDescription = phoneDescription }, ) { Icon( imageVector = TlIcons.Phone, diff --git a/feature/contactme/src/main/res/drawable/he_wei.jpg b/feature/contactme/src/main/res/drawable/feature_contactme_he_wei.jpg similarity index 100% rename from feature/contactme/src/main/res/drawable/he_wei.jpg rename to feature/contactme/src/main/res/drawable/feature_contactme_he_wei.jpg diff --git a/feature/contactme/src/main/res/values-zh-rTW/strings.xml b/feature/contactme/src/main/res/values-zh-rTW/strings.xml index 9fc1d699..da59e52d 100644 --- a/feature/contactme/src/main/res/values-zh-rTW/strings.xml +++ b/feature/contactme/src/main/res/values-zh-rTW/strings.xml @@ -1,8 +1,8 @@ - %s 的大頭貼 - LinkedIn - Email - 時區 - 撥打給 %s + %s 的大頭貼 + LinkedIn + Email + 時區 + 撥打給 %s \ No newline at end of file diff --git a/feature/contactme/src/main/res/values/strings.xml b/feature/contactme/src/main/res/values/strings.xml index b426f46e..19b7ab5a 100644 --- a/feature/contactme/src/main/res/values/strings.xml +++ b/feature/contactme/src/main/res/values/strings.xml @@ -1,8 +1,8 @@ - %s\'s profile picture - LinkedIn - Email - Timezone - Call %s + %s\'s profile picture + LinkedIn + Email + Timezone + Call %s \ No newline at end of file diff --git a/feature/home/src/androidTest/java/com/wei/teachlink/feature/home/home/HomeScreenRobot.kt b/feature/home/src/androidTest/java/com/wei/teachlink/feature/home/home/HomeScreenRobot.kt index ce6f8b14..6134970a 100644 --- a/feature/home/src/androidTest/java/com/wei/teachlink/feature/home/home/HomeScreenRobot.kt +++ b/feature/home/src/androidTest/java/com/wei/teachlink/feature/home/home/HomeScreenRobot.kt @@ -37,25 +37,25 @@ internal open class HomeScreenRobot( ) = ReadOnlyProperty { _, _ -> activity.getString(resId) } // The strings used for matching in these tests - private val userAvatarTag by composeTestRule.stringResource(R.string.tag_user_avatar) - private val addUserString by composeTestRule.stringResource(R.string.add_user) - private val menuString by composeTestRule.stringResource(R.string.menu) - private val helloUserNameTextTag by composeTestRule.stringResource(R.string.tag_hello_user_name_text) - private val myCoursesString by composeTestRule.stringResource(R.string.my_courses) - private val chatsString by composeTestRule.stringResource(R.string.chats) - private val tutorsString by composeTestRule.stringResource(R.string.tutors) - private val courseProgressCardTag by composeTestRule.stringResource(R.string.tag_course_progress_card) - private val pupilRatingCardTag by composeTestRule.stringResource(R.string.tag_pupil_rating_card) - private val tutorButtonTag by composeTestRule.stringResource(R.string.tag_tutor_button) - private val classNameTag by composeTestRule.stringResource(R.string.tag_class_name) - private val classInfoTag by composeTestRule.stringResource(R.string.tag_class_info) - private val contactCardString by composeTestRule.stringResource(R.string.contact_card) - private val skillNameTag by composeTestRule.stringResource(R.string.tag_skill_name) - private val skillLevelTag by composeTestRule.stringResource(R.string.tag_skill_level) - private val circularProgressTag by composeTestRule.stringResource(R.string.tag_circular_progress) - private val loadingContentTag by composeTestRule.stringResource(R.string.tag_loading_content) - private val loadingErrorContentTag by composeTestRule.stringResource(R.string.tag_loading_error_content) - private val screenNotAvailableString by composeTestRule.stringResource(R.string.screen_not_available) + private val userAvatarTag by composeTestRule.stringResource(R.string.feature_home_tag_user_avatar) + private val addUserString by composeTestRule.stringResource(R.string.feature_home_add_user) + private val menuString by composeTestRule.stringResource(R.string.feature_home_menu) + private val helloUserNameTextTag by composeTestRule.stringResource(R.string.feature_home_tag_hello_user_name_text) + private val myCoursesString by composeTestRule.stringResource(R.string.feature_home_my_courses) + private val chatsString by composeTestRule.stringResource(R.string.feature_home_chats) + private val tutorsString by composeTestRule.stringResource(R.string.feature_home_tutors) + private val courseProgressCardTag by composeTestRule.stringResource(R.string.feature_home_tag_course_progress_card) + private val pupilRatingCardTag by composeTestRule.stringResource(R.string.feature_home_tag_pupil_rating_card) + private val tutorButtonTag by composeTestRule.stringResource(R.string.feature_home_tag_tutor_button) + private val classNameTag by composeTestRule.stringResource(R.string.feature_home_tag_class_name) + private val classInfoTag by composeTestRule.stringResource(R.string.feature_home_tag_class_info) + private val contactCardString by composeTestRule.stringResource(R.string.feature_home_contact_card) + private val skillNameTag by composeTestRule.stringResource(R.string.feature_home_tag_skill_name) + private val skillLevelTag by composeTestRule.stringResource(R.string.feature_home_tag_skill_level) + private val circularProgressTag by composeTestRule.stringResource(R.string.feature_home_tag_circular_progress) + private val loadingContentTag by composeTestRule.stringResource(R.string.feature_home_tag_loading_content) + private val loadingErrorContentTag by composeTestRule.stringResource(R.string.feature_home_tag_loading_error_content) + private val screenNotAvailableString by composeTestRule.stringResource(R.string.feature_home_screen_not_available) private var clickedTab: Tab? = null @@ -317,18 +317,18 @@ val testHomeViewState: HomeViewState = selectedTab = Tab.MY_COURSES, chatCount = 102, myCoursesContentState = - MyCoursesContentState( - courseProgress = 20, - courseCount = 14, - pupilRating = 9.9, - tutorName = "TEST_TUTOR_NAME", - className = "TEST_CLASS_NAME", - lessonsCountDisplay = "30+", - ratingCount = 4.9, - startedDate = "11.04", - contacts = listOf(), - skillName = "TEST_SKILL_NAME", - skillLevel = "TEST_SKILL_LEVEL", - skillLevelProgress = 64, - ), + MyCoursesContentState( + courseProgress = 20, + courseCount = 14, + pupilRating = 9.9, + tutorName = "TEST_TUTOR_NAME", + className = "TEST_CLASS_NAME", + lessonsCountDisplay = "30+", + ratingCount = 4.9, + startedDate = "11.04", + contacts = listOf(), + skillName = "TEST_SKILL_NAME", + skillLevel = "TEST_SKILL_LEVEL", + skillLevelProgress = 64, + ), ) diff --git a/feature/home/src/main/java/com/wei/teachlink/feature/home/home/HomeScreen.kt b/feature/home/src/main/java/com/wei/teachlink/feature/home/home/HomeScreen.kt index 4594c15e..ca9d55ad 100644 --- a/feature/home/src/main/java/com/wei/teachlink/feature/home/home/HomeScreen.kt +++ b/feature/home/src/main/java/com/wei/teachlink/feature/home/home/HomeScreen.kt @@ -117,7 +117,7 @@ internal fun HomeScreen( HomeTopBar( modifier = horizontalBasePadding, userName = uiStates.userDisplayName, - avatarId = R.drawable.he_wei, + avatarId = R.drawable.feature_home_he_wei, onAddUserClick = { // TODO showPopup.value = true @@ -193,7 +193,7 @@ private fun TabContent( @Composable private fun UnavailableScreenContent() { - val screenNotAvailable = stringResource(R.string.screen_not_available) + val screenNotAvailable = stringResource(R.string.feature_home_screen_not_available) Column { Spacer(modifier = Modifier.weight(1f)) @@ -202,8 +202,8 @@ private fun UnavailableScreenContent() { color = MaterialTheme.colorScheme.error, style = MaterialTheme.typography.headlineMedium, modifier = - Modifier - .semantics { contentDescription = screenNotAvailable }, + Modifier + .semantics { contentDescription = screenNotAvailable }, ) Spacer(modifier = Modifier.weight(1f)) } @@ -213,9 +213,9 @@ private fun UnavailableScreenContent() { private fun LoadingErrorContent() { Box( modifier = - Modifier - .fillMaxSize() - .testTag(stringResource(R.string.tag_loading_error_content)), + Modifier + .fillMaxSize() + .testTag(stringResource(R.string.feature_home_tag_loading_error_content)), ) { // TODO Error Content } @@ -225,9 +225,9 @@ private fun LoadingErrorContent() { private fun LoadingContent() { Box( modifier = - Modifier - .fillMaxSize() - .testTag(stringResource(R.string.tag_loading_content)), + Modifier + .fillMaxSize() + .testTag(stringResource(R.string.feature_home_tag_loading_content)), contentAlignment = Alignment.Center, ) { CircularProgressIndicator(modifier = Modifier.size(30.dp)) @@ -240,10 +240,10 @@ fun HomeScreenPreview() { TlTheme { HomeScreen( uiStates = - HomeViewState( - loadingState = HomeViewLoadingState.Success, - userDisplayName = "Wei", - ), + HomeViewState( + loadingState = HomeViewLoadingState.Success, + userDisplayName = "Wei", + ), isPreview = true, onTabClick = { }, ) diff --git a/feature/home/src/main/java/com/wei/teachlink/feature/home/home/MyCoursesContent.kt b/feature/home/src/main/java/com/wei/teachlink/feature/home/home/MyCoursesContent.kt index 9d04413f..227330d6 100644 --- a/feature/home/src/main/java/com/wei/teachlink/feature/home/home/MyCoursesContent.kt +++ b/feature/home/src/main/java/com/wei/teachlink/feature/home/home/MyCoursesContent.kt @@ -91,9 +91,9 @@ fun MyCoursesContent( Spacer(modifier = Modifier.width(SPACING_SMALL.dp)) SkillProgressCard( modifier = - Modifier - .size(cardSize.dp) - .weight(1f), + Modifier + .size(cardSize.dp) + .weight(1f), skillName = uiStates.skillName, skillLevel = uiStates.skillLevel, progress = uiStates.skillLevelProgress, @@ -112,10 +112,10 @@ fun CourseProgressCard( courseCount: Int, onClick: () -> Unit, ) { - val completed = stringResource(id = R.string.completed) + val completed = stringResource(id = R.string.feature_home_completed) val contentCourseProgressCard = stringResource( - R.string.course_progress_card, + R.string.feature_home_course_progress_card, completed, courseProgress, courseCount, @@ -123,11 +123,11 @@ fun CourseProgressCard( StatusCard( modifier = - modifier - .testTag(stringResource(R.string.tag_course_progress_card)) - .semantics { - contentDescription = contentCourseProgressCard - }, + modifier + .testTag(stringResource(R.string.feature_home_tag_course_progress_card)) + .semantics { + contentDescription = contentCourseProgressCard + }, onClick = onClick, content = { Row { @@ -157,17 +157,17 @@ fun PupilRatingCard( pupilRating: Double, onClick: () -> Unit, ) { - val pupil = stringResource(id = R.string.pupil) - val rating = stringResource(id = R.string.rating) + val pupil = stringResource(id = R.string.feature_home_pupil) + val rating = stringResource(id = R.string.feature_home_rating) val contentPupilRatingCard = "$pupil $rating $pupilRating" StatusCard( modifier = - modifier - .testTag(stringResource(R.string.tag_pupil_rating_card)) - .semantics { - contentDescription = contentPupilRatingCard - }, + modifier + .testTag(stringResource(R.string.feature_home_tag_pupil_rating_card)) + .semantics { + contentDescription = contentPupilRatingCard + }, onClick = onClick, content = { Row { @@ -259,20 +259,20 @@ private fun ClassInfo( ratingCount: Double, startedDate: String, ) { - val lessons = stringResource(id = R.string.lessons) - val rating = stringResource(id = R.string.rating) - val started = stringResource(id = R.string.started) + val lessons = stringResource(id = R.string.feature_home_lessons) + val rating = stringResource(id = R.string.feature_home_rating) + val started = stringResource(id = R.string.feature_home_started) val contentClassInfo = "$lessons $lessonsCountDisplay, $rating $ratingCount, $started $startedDate" Row( modifier = - Modifier - .padding(horizontal = SPACING_SMALL.dp) - .testTag(stringResource(R.string.tag_class_info)) - .semantics { - contentDescription = contentClassInfo - }, + Modifier + .padding(horizontal = SPACING_SMALL.dp) + .testTag(stringResource(R.string.feature_home_tag_class_info)) + .semantics { + contentDescription = contentClassInfo + }, ) { Row( verticalAlignment = Alignment.Bottom, @@ -322,12 +322,12 @@ private fun ClassInfo( private fun ClassName(className: String) { Row( modifier = - Modifier - .padding(horizontal = SPACING_SMALL.dp) - .testTag(stringResource(R.string.tag_class_name)) - .semantics { - contentDescription = className - }, + Modifier + .padding(horizontal = SPACING_SMALL.dp) + .testTag(stringResource(R.string.feature_home_tag_class_name)) + .semantics { + contentDescription = className + }, ) { Text( text = className, @@ -343,24 +343,24 @@ private fun TutorButton( tutorName: String, onTutorClick: () -> Unit, ) { - val tutor = stringResource(id = R.string.tutor) + val tutor = stringResource(id = R.string.feature_home_tutor) val contentTutorButton = "$tutor $tutorName" Button( onClick = onTutorClick, colors = - ButtonDefaults.buttonColors( - containerColor = MaterialTheme.colorScheme.background, - ), + ButtonDefaults.buttonColors( + containerColor = MaterialTheme.colorScheme.background, + ), contentPadding = ButtonDefaults.TextButtonContentPadding, modifier = - Modifier - .testTag( - stringResource(R.string.tag_tutor_button), - ) - .semantics { - contentDescription = contentTutorButton - }, + Modifier + .testTag( + stringResource(R.string.feature_home_tag_tutor_button), + ) + .semantics { + contentDescription = contentTutorButton + }, ) { Icon( imageVector = TlIcons.Person, @@ -396,20 +396,20 @@ fun MyCoursesContentPreview() { MyCoursesContent( modifier = Modifier.padding(horizontal = SPACING_LARGE.dp), uiStates = - MyCoursesContentState( - courseProgress = 20, - courseCount = 30, - pupilRating = 9.9, - tutorName = TEST_TUTOR_NAME, - className = TEST_CLASS_NAME, - lessonsCountDisplay = "40+", - ratingCount = 4.9, - startedDate = "11.04", - contacts = TestContacts, - skillName = TEST_SKILL_NAME, - skillLevel = TEST_SKILL_LEVEL, - skillLevelProgress = TEST_SKILL_LEVEL_PROGRESS, - ), + MyCoursesContentState( + courseProgress = 20, + courseCount = 30, + pupilRating = 9.9, + tutorName = TEST_TUTOR_NAME, + className = TEST_CLASS_NAME, + lessonsCountDisplay = "40+", + ratingCount = 4.9, + startedDate = "11.04", + contacts = TestContacts, + skillName = TEST_SKILL_NAME, + skillLevel = TEST_SKILL_LEVEL, + skillLevelProgress = TEST_SKILL_LEVEL_PROGRESS, + ), isPreview = true, onCardClick = {}, ) diff --git a/feature/home/src/main/java/com/wei/teachlink/feature/home/home/ui/ContactListCard.kt b/feature/home/src/main/java/com/wei/teachlink/feature/home/home/ui/ContactListCard.kt index 78698e60..20e80e80 100644 --- a/feature/home/src/main/java/com/wei/teachlink/feature/home/home/ui/ContactListCard.kt +++ b/feature/home/src/main/java/com/wei/teachlink/feature/home/home/ui/ContactListCard.kt @@ -49,20 +49,20 @@ fun ContactCard( val maxDisplayContacts = 4 val contactsPerRow = 2 val spacingSize = SPACING_EXTRA_SMALL.dp - val contactCard = stringResource(R.string.contact_card) + val contactCard = stringResource(R.string.feature_home_contact_card) Card( modifier = - modifier - .semantics { - contentDescription = contactCard - }, + modifier + .semantics { + contentDescription = contactCard + }, shape = shapes.extraLarge, colors = - CardDefaults.cardColors( - contentColor = MaterialTheme.colorScheme.onSecondary, - containerColor = MaterialTheme.colorScheme.secondary, - ), + CardDefaults.cardColors( + contentColor = MaterialTheme.colorScheme.onSecondary, + containerColor = MaterialTheme.colorScheme.secondary, + ), ) { Column( modifier = Modifier.padding(SPACING_MEDIUM.dp), @@ -103,21 +103,21 @@ internal fun ContactAvatar( isPreview: Boolean, ) { val painter = coilImagePainter(avatarId, true) - val profilePictureDescription = stringResource(R.string.profile_picture).format(name) + val profilePictureDescription = stringResource(R.string.feature_home_profile_picture).format(name) Box( modifier = - modifier - .size(CONTACT_HEAD_SHOT_SIZE.dp) - .statusIndicator(status), + modifier + .size(CONTACT_HEAD_SHOT_SIZE.dp) + .statusIndicator(status), ) { Image( painter = painter, contentDescription = profilePictureDescription, modifier = - Modifier - .matchParentSize() - .clip(CircleShape), + Modifier + .matchParentSize() + .clip(CircleShape), ) } } @@ -172,9 +172,9 @@ fun Modifier.statusIndicator( fun PlaceholderAvatar(modifier: Modifier = Modifier) { Box( modifier = - modifier - .background(MaterialTheme.colorScheme.secondary) - .size(CONTACT_HEAD_SHOT_SIZE.dp), + modifier + .background(MaterialTheme.colorScheme.secondary) + .size(CONTACT_HEAD_SHOT_SIZE.dp), ) } diff --git a/feature/home/src/main/java/com/wei/teachlink/feature/home/home/ui/HomeTabRow.kt b/feature/home/src/main/java/com/wei/teachlink/feature/home/home/ui/HomeTabRow.kt index e6011a5d..d7a5263a 100644 --- a/feature/home/src/main/java/com/wei/teachlink/feature/home/home/ui/HomeTabRow.kt +++ b/feature/home/src/main/java/com/wei/teachlink/feature/home/home/ui/HomeTabRow.kt @@ -68,15 +68,15 @@ fun MyCoursesTab( isSelected: Boolean, onTabClick: () -> Unit, ) { - val myCourses = stringResource(id = R.string.my_courses) + val myCourses = stringResource(id = R.string.feature_home_my_courses) Tab( selected = isSelected, onClick = onTabClick, modifier = - Modifier.semantics { - contentDescription = myCourses - }, + Modifier.semantics { + contentDescription = myCourses + }, ) { Text( text = myCourses, @@ -94,15 +94,15 @@ fun ChatTab( shouldDisplayChatCount: Boolean, onTabClick: () -> Unit, ) { - val chats = stringResource(R.string.chats) + val chats = stringResource(R.string.feature_home_chats) Tab( selected = isSelected, onClick = onTabClick, modifier = - Modifier.semantics { - contentDescription = chats - }, + Modifier.semantics { + contentDescription = chats + }, ) { Row( modifier = Modifier.padding(SPACING_LARGE.dp), @@ -125,9 +125,9 @@ fun ChatTab( fun ChatCountBadge(count: String) { Box( modifier = - Modifier - .clip(shape = shapes.medium) - .background(color = MaterialTheme.colorScheme.primary), + Modifier + .clip(shape = shapes.medium) + .background(color = MaterialTheme.colorScheme.primary), ) { Text( text = count, @@ -143,15 +143,15 @@ fun TutorsTab( isSelected: Boolean, onTabClick: () -> Unit, ) { - val tutors = stringResource(id = R.string.tutors) + val tutors = stringResource(id = R.string.feature_home_tutors) Tab( selected = isSelected, onClick = onTabClick, modifier = - Modifier.semantics { - contentDescription = tutors - }, + Modifier.semantics { + contentDescription = tutors + }, ) { Text( text = tutors, @@ -168,10 +168,10 @@ fun HomeTabRowPreview() { TlTheme { HomeTabRow( uiStates = - HomeViewState( - selectedTab = Tab.MY_COURSES, - chatCount = 999, - ), + HomeViewState( + selectedTab = Tab.MY_COURSES, + chatCount = 999, + ), onTabSelected = {}, ) } diff --git a/feature/home/src/main/java/com/wei/teachlink/feature/home/home/ui/HomeTopBar.kt b/feature/home/src/main/java/com/wei/teachlink/feature/home/home/ui/HomeTopBar.kt index 0d416a73..92874197 100644 --- a/feature/home/src/main/java/com/wei/teachlink/feature/home/home/ui/HomeTopBar.kt +++ b/feature/home/src/main/java/com/wei/teachlink/feature/home/home/ui/HomeTopBar.kt @@ -60,16 +60,16 @@ fun HomeTopBar( MenuButton(onMenuClick = onMenuClick) } Spacer(modifier = Modifier.height(SPACING_SMALL.dp)) - val helloUserName = stringResource(R.string.hello, userName) + val helloUserName = stringResource(R.string.feature_home_hello, userName) Text( text = helloUserName, style = MaterialTheme.typography.headlineMedium, fontWeight = FontWeight.Normal, modifier = - Modifier - .testTag(stringResource(R.string.tag_hello_user_name_text)) - .semantics { contentDescription = helloUserName }, + Modifier + .testTag(stringResource(R.string.feature_home_tag_hello_user_name_text)) + .semantics { contentDescription = helloUserName }, ) Spacer(modifier = Modifier.height(SPACING_SMALL.dp)) } @@ -81,17 +81,17 @@ private fun AddUserButton( onAddUserClick: () -> Unit, ) { Box(modifier = modifier) { - val addUser = stringResource(R.string.add_user) + val addUser = stringResource(R.string.feature_home_add_user) IconButton( onClick = { onAddUserClick() }, modifier = - Modifier - .clip(CircleShape) - .background(MaterialTheme.colorScheme.surfaceVariant) - .semantics { contentDescription = addUser }, + Modifier + .clip(CircleShape) + .background(MaterialTheme.colorScheme.surfaceVariant) + .semantics { contentDescription = addUser }, ) { Icon( imageVector = TlIcons.Add, @@ -110,40 +110,40 @@ internal fun UserAvatar( onUserProfileImageClick: () -> Unit, ) { val painter = coilImagePainter(avatarId, true) - val profilePictureDescription = stringResource(R.string.profile_picture).format(userName) + val profilePictureDescription = stringResource(R.string.feature_home_profile_picture).format(userName) IconButton( onClick = onUserProfileImageClick, modifier = - modifier - .size(48.dp) - .testTag(stringResource(R.string.tag_user_avatar)) - .semantics { - contentDescription = profilePictureDescription - }, + modifier + .size(48.dp) + .testTag(stringResource(R.string.feature_home_tag_user_avatar)) + .semantics { + contentDescription = profilePictureDescription + }, ) { Image( painter = painter, contentDescription = null, modifier = - modifier - .clip(CircleShape) - .fillMaxSize(), + modifier + .clip(CircleShape) + .fillMaxSize(), ) } } @Composable private fun MenuButton(onMenuClick: () -> Unit) { - val menu = stringResource(R.string.menu) + val menu = stringResource(R.string.feature_home_menu) IconButton( onClick = onMenuClick, modifier = - Modifier - .clip(CircleShape) - .background(MaterialTheme.colorScheme.surfaceVariant) - .semantics { contentDescription = menu }, + Modifier + .clip(CircleShape) + .background(MaterialTheme.colorScheme.surfaceVariant) + .semantics { contentDescription = menu }, ) { Icon( imageVector = TlIcons.Menu, @@ -161,7 +161,7 @@ fun HomeTopBarPreview() { HomeTopBar( modifier = Modifier.padding(horizontal = SPACING_LARGE.dp), userName = "TEST_NAME", - avatarId = R.drawable.he_wei, + avatarId = R.drawable.feature_home_he_wei, onUserProfileImageClick = {}, onAddUserClick = {}, onMenuClick = {}, diff --git a/feature/home/src/main/java/com/wei/teachlink/feature/home/home/ui/SkillProgressCard.kt b/feature/home/src/main/java/com/wei/teachlink/feature/home/home/ui/SkillProgressCard.kt index 49a1cc16..c91f6031 100644 --- a/feature/home/src/main/java/com/wei/teachlink/feature/home/home/ui/SkillProgressCard.kt +++ b/feature/home/src/main/java/com/wei/teachlink/feature/home/home/ui/SkillProgressCard.kt @@ -40,22 +40,22 @@ fun SkillProgressCard( ) { val contentSkillProgressCard = stringResource( - R.string.skill_progress_card, + R.string.feature_home_skill_progress_card, skillName, skillLevel, progress, ) Card( modifier = - modifier.semantics { - contentDescription = contentSkillProgressCard - }, + modifier.semantics { + contentDescription = contentSkillProgressCard + }, shape = shapes.extraLarge, colors = - CardDefaults.cardColors( - contentColor = MaterialTheme.colorScheme.onPrimary, - containerColor = MaterialTheme.colorScheme.primary, - ), + CardDefaults.cardColors( + contentColor = MaterialTheme.colorScheme.onPrimary, + containerColor = MaterialTheme.colorScheme.primary, + ), onClick = onClick, ) { Column( @@ -66,7 +66,7 @@ fun SkillProgressCard( style = MaterialTheme.typography.titleLarge, maxLines = 1, overflow = TextOverflow.Ellipsis, - modifier = Modifier.testTag(stringResource(R.string.tag_skill_name)), + modifier = Modifier.testTag(stringResource(R.string.feature_home_tag_skill_name)), ) Text( text = skillLevel, @@ -74,14 +74,14 @@ fun SkillProgressCard( color = MaterialTheme.colorScheme.outlineVariant, maxLines = 1, overflow = TextOverflow.Ellipsis, - modifier = Modifier.testTag(stringResource(R.string.tag_skill_level)), + modifier = Modifier.testTag(stringResource(R.string.feature_home_tag_skill_level)), ) Spacer(modifier = Modifier.height(SPACING_LARGE.dp)) CircularProgress( modifier = - Modifier - .weight(1f) - .testTag(stringResource(R.string.tag_circular_progress)), + Modifier + .weight(1f) + .testTag(stringResource(R.string.feature_home_tag_circular_progress)), progress = progress, ) }, @@ -134,9 +134,9 @@ fun HomeScreenPreview() { TlTheme { SkillProgressCard( modifier = - Modifier - .width(200.dp) - .height(152.dp), + Modifier + .width(200.dp) + .height(152.dp), skillName = "Business English", skillLevel = "Advanced level", progress = 64, diff --git a/feature/home/src/main/java/com/wei/teachlink/feature/home/home/utilities/Constants.kt b/feature/home/src/main/java/com/wei/teachlink/feature/home/home/utilities/Constants.kt index c8e7866f..0a89f621 100644 --- a/feature/home/src/main/java/com/wei/teachlink/feature/home/home/utilities/Constants.kt +++ b/feature/home/src/main/java/com/wei/teachlink/feature/home/home/utilities/Constants.kt @@ -20,25 +20,26 @@ val OfflineColor = Color(0xFFB6B6B6) const val CONTACT_HEAD_SHOT_SIZE = 64 -val TestContacts = listOf( - Contact( - name = "jamie-coleman", - avatarId = R.drawable.jamie_coleman, - status = OnlineStatus.FREE, - ), - Contact( - name = "contact tutor 1", - avatarId = R.drawable.img_face_01, - status = OnlineStatus.BUSY, - ), - Contact( - name = "contact tutor 2", - avatarId = R.drawable.img_face_02, - status = OnlineStatus.FREE, - ), - Contact( - name = "contact tutor 3", - avatarId = R.drawable.img_face_03, - status = OnlineStatus.OFFLINE, - ), -) +val TestContacts = + listOf( + Contact( + name = "jamie-coleman", + avatarId = R.drawable.feature_home_jamie_coleman, + status = OnlineStatus.FREE, + ), + Contact( + name = "contact tutor 1", + avatarId = R.drawable.feature_home_img_face_01, + status = OnlineStatus.BUSY, + ), + Contact( + name = "contact tutor 2", + avatarId = R.drawable.feature_home_img_face_02, + status = OnlineStatus.FREE, + ), + Contact( + name = "contact tutor 3", + avatarId = R.drawable.feature_home_img_face_03, + status = OnlineStatus.OFFLINE, + ), + ) diff --git a/feature/home/src/main/res/drawable/he_wei.jpg b/feature/home/src/main/res/drawable/feature_home_he_wei.jpg similarity index 100% rename from feature/home/src/main/res/drawable/he_wei.jpg rename to feature/home/src/main/res/drawable/feature_home_he_wei.jpg diff --git a/feature/home/src/main/res/drawable/img_face_01.webp b/feature/home/src/main/res/drawable/feature_home_img_face_01.webp similarity index 100% rename from feature/home/src/main/res/drawable/img_face_01.webp rename to feature/home/src/main/res/drawable/feature_home_img_face_01.webp diff --git a/feature/home/src/main/res/drawable/img_face_02.webp b/feature/home/src/main/res/drawable/feature_home_img_face_02.webp similarity index 100% rename from feature/home/src/main/res/drawable/img_face_02.webp rename to feature/home/src/main/res/drawable/feature_home_img_face_02.webp diff --git a/feature/home/src/main/res/drawable/img_face_03.webp b/feature/home/src/main/res/drawable/feature_home_img_face_03.webp similarity index 100% rename from feature/home/src/main/res/drawable/img_face_03.webp rename to feature/home/src/main/res/drawable/feature_home_img_face_03.webp diff --git a/feature/home/src/main/res/drawable/jamie_coleman.webp b/feature/home/src/main/res/drawable/feature_home_jamie_coleman.webp similarity index 100% rename from feature/home/src/main/res/drawable/jamie_coleman.webp rename to feature/home/src/main/res/drawable/feature_home_jamie_coleman.webp diff --git a/feature/home/src/main/res/values-zh-rTW/strings.xml b/feature/home/src/main/res/values-zh-rTW/strings.xml index a0ff4a7b..4ba6faff 100644 --- a/feature/home/src/main/res/values-zh-rTW/strings.xml +++ b/feature/home/src/main/res/values-zh-rTW/strings.xml @@ -1,21 +1,21 @@ - %s 的大頭貼 - 新增使用者 - 選單 - 教師 - 教師 - 對話 - 我的課程 - 完成 - 學生 - 評價 - 堂數 - 開始時間 - Hello, %1$s - %1$s, 百分比 %2$s, 堂數 %3$s - 聯絡人卡牌 - %1$s %2$s, 百分比 %3$s + %s 的大頭貼 + 新增使用者 + 選單 + 教師 + 教師 + 對話 + 我的課程 + 完成 + 學生 + 評價 + 堂數 + 開始時間 + Hello, %1$s + %1$s, 百分比 %2$s, 堂數 %3$s + 聯絡人卡牌 + %1$s %2$s, 百分比 %3$s diff --git a/feature/home/src/main/res/values/strings.xml b/feature/home/src/main/res/values/strings.xml index f05e65ae..3c67c963 100644 --- a/feature/home/src/main/res/values/strings.xml +++ b/feature/home/src/main/res/values/strings.xml @@ -1,36 +1,36 @@ - %s\'s profile picture - Add User - Menu - Tutor - Tutors - Chats - My Courses - Completed - Pupil - Rating - Lessons - Started - Hello, %1$s - %1$s, percentage %2$s, count %3$s - 聯絡人卡牌 - %1$s %2$s, percentage %3$s - Screen not available 🙈 + %s\'s profile picture + Add User + Menu + Tutor + Tutors + Chats + My Courses + Completed + Pupil + Rating + Lessons + Started + Hello, %1$s + %1$s, percentage %2$s, count %3$s + 聯絡人卡牌 + %1$s %2$s, percentage %3$s + Screen not available 🙈 - UserAvatar - HelloUserNameText - CourseProgressCard - PupilRatingCard - TutorButton - ClassName - ClassInfo - SkillName - SkillLevel - CircularProgress - LoadingContent - LoadingErrorContent + UserAvatar + HelloUserNameText + CourseProgressCard + PupilRatingCard + TutorButton + ClassName + ClassInfo + SkillName + SkillLevel + CircularProgress + LoadingContent + LoadingErrorContent diff --git a/feature/login/src/androidTest/java/com/wei/teachlink/feature/login/login/LoginScreenRobot.kt b/feature/login/src/androidTest/java/com/wei/teachlink/feature/login/login/LoginScreenRobot.kt index 3d4425c7..6cd9a965 100644 --- a/feature/login/src/androidTest/java/com/wei/teachlink/feature/login/login/LoginScreenRobot.kt +++ b/feature/login/src/androidTest/java/com/wei/teachlink/feature/login/login/LoginScreenRobot.kt @@ -50,11 +50,11 @@ internal open class LoginScreenRobot( } // The strings used for matching in these tests - private val accountDescription by composeTestRule.stringResource(R.string.content_description_account) - private val passwordDescription by composeTestRule.stringResource(R.string.content_description_password) - private val forgotPasswordDescription by composeTestRule.stringResource(R.string.forgot_password) - private val loginString by composeTestRule.stringResource(R.string.login) - private val loginDescription by composeTestRule.stringResource(R.string.content_description_login) + private val accountDescription by composeTestRule.stringResource(R.string.feature_login_content_description_account) + private val passwordDescription by composeTestRule.stringResource(R.string.feature_login_content_description_password) + private val forgotPasswordDescription by composeTestRule.stringResource(R.string.feature_login_forgot_password) + private val loginString by composeTestRule.stringResource(R.string.feature_login_login) + private val loginDescription by composeTestRule.stringResource(R.string.feature_login_content_description_login) private val loginTitle by lazy { composeTestRule.onNode( diff --git a/feature/login/src/androidTest/java/com/wei/teachlink/feature/login/welcome/WelcomeScreenRobot.kt b/feature/login/src/androidTest/java/com/wei/teachlink/feature/login/welcome/WelcomeScreenRobot.kt index 0c17af4a..d1087c10 100644 --- a/feature/login/src/androidTest/java/com/wei/teachlink/feature/login/welcome/WelcomeScreenRobot.kt +++ b/feature/login/src/androidTest/java/com/wei/teachlink/feature/login/welcome/WelcomeScreenRobot.kt @@ -33,10 +33,10 @@ internal open class WelcomeScreenRobot( ) = ReadOnlyProperty { _, _ -> activity.getString(resId) } // The strings used for matching in these tests - private val scheduleListTag by composeTestRule.stringResource(R.string.tag_welcome_graphics) + private val scheduleListTag by composeTestRule.stringResource(R.string.feature_login_tag_welcome_graphics) - private val welcomeTitleString by composeTestRule.stringResource(R.string.welcome_title) - private val getStartedString by composeTestRule.stringResource(R.string.get_started) + private val welcomeTitleString by composeTestRule.stringResource(R.string.feature_login_welcome_title) + private val getStartedString by composeTestRule.stringResource(R.string.feature_login_get_started) private val welcomeGraphics by lazy { composeTestRule.onNodeWithTag( diff --git a/feature/login/src/main/java/com/wei/teachlink/feature/login/login/LoginScreen.kt b/feature/login/src/main/java/com/wei/teachlink/feature/login/login/LoginScreen.kt index fdc40e58..9c7dda45 100644 --- a/feature/login/src/main/java/com/wei/teachlink/feature/login/login/LoginScreen.kt +++ b/feature/login/src/main/java/com/wei/teachlink/feature/login/login/LoginScreen.kt @@ -112,9 +112,9 @@ internal fun LoginScreen( ) { Box( modifier = - Modifier - .fillMaxSize() - .padding(SPACING_LARGE.dp), + Modifier + .fillMaxSize() + .padding(SPACING_LARGE.dp), contentAlignment = Alignment.Center, ) { Column( @@ -147,27 +147,27 @@ internal fun LoginScreen( @Composable private fun Title(modifier: Modifier = Modifier) { - val title = stringResource(R.string.login) + val title = stringResource(R.string.feature_login_login) Text( text = title, style = MaterialTheme.typography.displayMedium, modifier = - modifier - .semantics { contentDescription = "" }, + modifier + .semantics { contentDescription = "" }, ) } @Composable internal fun AccountTextField(accountState: MutableState) { - val account = stringResource(R.string.account) - val accountDescription = stringResource(R.string.content_description_account) + val account = stringResource(R.string.feature_login_account) + val accountDescription = stringResource(R.string.feature_login_content_description_account) TextField( value = accountState.value, modifier = - Modifier - .semantics { contentDescription = accountDescription }, + Modifier + .semantics { contentDescription = accountDescription }, onValueChange = { accountState.value = it }, @@ -180,14 +180,14 @@ internal fun AccountTextField(accountState: MutableState) { @Composable internal fun PasswordTextField(passwordState: MutableState) { - val password = stringResource(R.string.password) - val passwordDescription = stringResource(R.string.content_description_password) + val password = stringResource(R.string.feature_login_password) + val passwordDescription = stringResource(R.string.feature_login_content_description_password) TextField( value = passwordState.value, modifier = - Modifier - .semantics { contentDescription = passwordDescription }, + Modifier + .semantics { contentDescription = passwordDescription }, onValueChange = { passwordState.value = it }, @@ -201,15 +201,15 @@ internal fun PasswordTextField(passwordState: MutableState) { @Composable internal fun ForgotPasswordText(modifier: Modifier = Modifier) { - val forgotPassword = stringResource(R.string.forgot_password) + val forgotPassword = stringResource(R.string.feature_login_forgot_password) Text( text = forgotPassword, style = MaterialTheme.typography.bodyMedium, modifier = - Modifier - .padding(top = SPACING_LARGE.dp) - .semantics { contentDescription = forgotPassword }, + Modifier + .padding(top = SPACING_LARGE.dp) + .semantics { contentDescription = forgotPassword }, ) } @@ -219,17 +219,17 @@ internal fun LoginButton( passwordState: MutableState, login: (String, String) -> Unit, ) { - val loginText = stringResource(R.string.login) - val loginTextDescription = stringResource(R.string.content_description_login) + val loginText = stringResource(R.string.feature_login_login) + val loginTextDescription = stringResource(R.string.feature_login_content_description_login) Button( onClick = { login(accountState.value, passwordState.value) }, modifier = - Modifier - .padding(top = SPACING_SMALL.dp) - .semantics { contentDescription = loginTextDescription }, + Modifier + .padding(top = SPACING_SMALL.dp) + .semantics { contentDescription = loginTextDescription }, ) { Text( loginText, diff --git a/feature/login/src/main/java/com/wei/teachlink/feature/login/welcome/WelcomeScreen.kt b/feature/login/src/main/java/com/wei/teachlink/feature/login/welcome/WelcomeScreen.kt index 9bd488b2..37c361fe 100644 --- a/feature/login/src/main/java/com/wei/teachlink/feature/login/welcome/WelcomeScreen.kt +++ b/feature/login/src/main/java/com/wei/teachlink/feature/login/welcome/WelcomeScreen.kt @@ -118,8 +118,8 @@ internal fun WelcomeScreen( Surface(modifier = Modifier.fillMaxSize()) { Column( modifier = - Modifier - .fillMaxSize(), + Modifier + .fillMaxSize(), ) { if (withTopSpacer) { Spacer(Modifier.windowInsetsTopHeight(WindowInsets.safeDrawing)) @@ -127,13 +127,13 @@ internal fun WelcomeScreen( WelcomeScreenToolbar( modifier = - if (isPortrait) { - Modifier.padding(horizontal = SPACING_LARGE.dp) - } else { - Modifier.padding( - horizontal = SPACING_EXTRA_LARGE.dp, - ) - }, + if (isPortrait) { + Modifier.padding(horizontal = SPACING_LARGE.dp) + } else { + Modifier.padding( + horizontal = SPACING_EXTRA_LARGE.dp, + ) + }, isPreview = isPreview, onGetStartedButtonClicked = onGetStartedButtonClicked, ) @@ -175,7 +175,7 @@ fun TlLogoImg( modifier: Modifier = Modifier, isPreview: Boolean, ) { - val resId = R.drawable.ic_logo + val resId = R.drawable.feature_login_ic_logo val painter = coilImagePainter(resId, isPreview) Image( @@ -190,7 +190,7 @@ fun GetStartedButton( modifier: Modifier = Modifier, onGetStartedButtonClicked: () -> Unit, ) { - val getStarted = stringResource(R.string.get_started) + val getStarted = stringResource(R.string.feature_login_get_started) IconButton( onClick = { onGetStartedButtonClicked() }, ) { @@ -206,7 +206,7 @@ fun WelcomeGraphics( modifier: Modifier = Modifier, isPreview: Boolean, ) { - val resId = R.drawable.welcome_background + val resId = R.drawable.feature_login_welcome_background val painter = coilImagePainter(resId, isPreview) Box(modifier = modifier.fillMaxWidth()) { @@ -214,9 +214,9 @@ fun WelcomeGraphics( painter = painter, contentDescription = null, modifier = - modifier - .fillMaxSize() - .testTag(stringResource(R.string.tag_welcome_graphics)), + modifier + .fillMaxSize() + .testTag(stringResource(R.string.feature_login_tag_welcome_graphics)), contentScale = ContentScale.Crop, ) } @@ -246,20 +246,20 @@ fun WelcomeTitlePortrait( modifier: Modifier = Modifier, style: TextStyle, ) { - val welcomeTitle = stringResource(R.string.welcome_title) + val welcomeTitle = stringResource(R.string.feature_login_welcome_title) Box( modifier = - modifier - .fillMaxWidth() - .gradientBackgroundPortrait(), + modifier + .fillMaxWidth() + .gradientBackgroundPortrait(), ) { Text( modifier = - Modifier - .padding(vertical = SPACING_EXTRA_LARGE.dp) - .semantics { contentDescription = welcomeTitle } - .align(alignment = Alignment.Center), + Modifier + .padding(vertical = SPACING_EXTRA_LARGE.dp) + .semantics { contentDescription = welcomeTitle } + .align(alignment = Alignment.Center), style = style, text = welcomeTitle, textAlign = TextAlign.Center, @@ -273,20 +273,20 @@ fun WelcomeTitleLandscape( modifier: Modifier = Modifier, style: TextStyle, ) { - val welcomeTitle = stringResource(R.string.welcome_title) + val welcomeTitle = stringResource(R.string.feature_login_welcome_title) Box( modifier = - modifier - .fillMaxHeight() - .gradientBackgroundLandscape(), + modifier + .fillMaxHeight() + .gradientBackgroundLandscape(), ) { Text( modifier = - Modifier - .padding(horizontal = SPACING_EXTRA_LARGE.dp) - .semantics { contentDescription = welcomeTitle } - .align(alignment = Alignment.Center), + Modifier + .padding(horizontal = SPACING_EXTRA_LARGE.dp) + .semantics { contentDescription = welcomeTitle } + .align(alignment = Alignment.Center), style = style, text = welcomeTitle, textAlign = TextAlign.Start, @@ -298,25 +298,25 @@ fun WelcomeTitleLandscape( internal fun Modifier.gradientBackgroundPortrait(): Modifier = this.background( brush = - Brush.verticalGradient( - colors = - listOf( - Color.Black.copy(alpha = 0f), - Color.Black.copy(alpha = 0.5f), + Brush.verticalGradient( + colors = + listOf( + Color.Black.copy(alpha = 0f), + Color.Black.copy(alpha = 0.5f), + ), ), - ), ) internal fun Modifier.gradientBackgroundLandscape(): Modifier = this.background( brush = - Brush.horizontalGradient( - colors = - listOf( - Color.White.copy(alpha = 0.5f), - Color.White.copy(alpha = 0f), + Brush.horizontalGradient( + colors = + listOf( + Color.White.copy(alpha = 0.5f), + Color.White.copy(alpha = 0f), + ), ), - ), ) @DevicePortraitPreviews diff --git a/feature/login/src/main/res/drawable-land/welcome_background.jpg b/feature/login/src/main/res/drawable-land/feature_login_welcome_background.jpg similarity index 100% rename from feature/login/src/main/res/drawable-land/welcome_background.jpg rename to feature/login/src/main/res/drawable-land/feature_login_welcome_background.jpg diff --git a/feature/login/src/main/res/drawable-port/welcome_background.jpg b/feature/login/src/main/res/drawable-port/feature_login_welcome_background.jpg similarity index 100% rename from feature/login/src/main/res/drawable-port/welcome_background.jpg rename to feature/login/src/main/res/drawable-port/feature_login_welcome_background.jpg diff --git a/feature/login/src/main/res/drawable-w600dp-land/welcome_background.jpg b/feature/login/src/main/res/drawable-w600dp-land/feature_login_welcome_background.jpg similarity index 100% rename from feature/login/src/main/res/drawable-w600dp-land/welcome_background.jpg rename to feature/login/src/main/res/drawable-w600dp-land/feature_login_welcome_background.jpg diff --git a/feature/login/src/main/res/drawable-w600dp-port/welcome_background.jpg b/feature/login/src/main/res/drawable-w600dp-port/feature_login_welcome_background.jpg similarity index 100% rename from feature/login/src/main/res/drawable-w600dp-port/welcome_background.jpg rename to feature/login/src/main/res/drawable-w600dp-port/feature_login_welcome_background.jpg diff --git a/feature/login/src/main/res/drawable-w840dp-land/welcome_background.jpg b/feature/login/src/main/res/drawable-w840dp-land/feature_login_welcome_background.jpg similarity index 100% rename from feature/login/src/main/res/drawable-w840dp-land/welcome_background.jpg rename to feature/login/src/main/res/drawable-w840dp-land/feature_login_welcome_background.jpg diff --git a/feature/login/src/main/res/drawable-w840dp-port/welcome_background.jpg b/feature/login/src/main/res/drawable-w840dp-port/feature_login_welcome_background.jpg similarity index 100% rename from feature/login/src/main/res/drawable-w840dp-port/welcome_background.jpg rename to feature/login/src/main/res/drawable-w840dp-port/feature_login_welcome_background.jpg diff --git a/feature/login/src/main/res/drawable/ic_logo.xml b/feature/login/src/main/res/drawable/feature_login_ic_logo.xml similarity index 100% rename from feature/login/src/main/res/drawable/ic_logo.xml rename to feature/login/src/main/res/drawable/feature_login_ic_logo.xml diff --git a/feature/login/src/main/res/drawable/welcome_background.jpg b/feature/login/src/main/res/drawable/feature_login_welcome_background.jpg similarity index 100% rename from feature/login/src/main/res/drawable/welcome_background.jpg rename to feature/login/src/main/res/drawable/feature_login_welcome_background.jpg diff --git a/feature/login/src/main/res/values-zh-rTW/strings.xml b/feature/login/src/main/res/values-zh-rTW/strings.xml index 4aea273d..e961c022 100644 --- a/feature/login/src/main/res/values-zh-rTW/strings.xml +++ b/feature/login/src/main/res/values-zh-rTW/strings.xml @@ -2,19 +2,19 @@ - 像好友一樣的 + 像好友一樣的 \n優質教師 - Get Started + Get Started - Account - Password - Login - 忘記密碼? + Account + Password + Login + 忘記密碼? - 帳號 - 密碼 - 登入 + 帳號 + 密碼 + 登入 \ No newline at end of file diff --git a/feature/login/src/main/res/values/strings.xml b/feature/login/src/main/res/values/strings.xml index 1ea6f8d8..9a6323ed 100644 --- a/feature/login/src/main/res/values/strings.xml +++ b/feature/login/src/main/res/values/strings.xml @@ -1,21 +1,21 @@ - Quality teachers + Quality teachers \nwho are like friends - Get Started + Get Started - Account - Password - Login - Forgot Password? + Account + Password + Login + Forgot Password? - Account - Password - Login + Account + Password + Login - tag_welcome_graphics + tag_welcome_graphics \ No newline at end of file diff --git a/feature/teacherschedule/src/androidTest/java/com/wei/teachlink/feature/teacherschedule/schedule/ScheduleScreenRobot.kt b/feature/teacherschedule/src/androidTest/java/com/wei/teachlink/feature/teacherschedule/schedule/ScheduleScreenRobot.kt index 4e5012d8..b92a0a13 100644 --- a/feature/teacherschedule/src/androidTest/java/com/wei/teachlink/feature/teacherschedule/schedule/ScheduleScreenRobot.kt +++ b/feature/teacherschedule/src/androidTest/java/com/wei/teachlink/feature/teacherschedule/schedule/ScheduleScreenRobot.kt @@ -59,22 +59,28 @@ internal open class ScheduleScreenRobot( ) = ReadOnlyProperty { _, _ -> activity.getString(resId) } // The strings used for matching in these tests - private val morningString by composeTestRule.stringResource(R.string.morning) - private val afternoonString by composeTestRule.stringResource(R.string.afternoon) - private val eveningString by composeTestRule.stringResource(R.string.evening) - private val loadingString by composeTestRule.stringResource(R.string.loading) - private val loadFailedString by composeTestRule.stringResource(R.string.load_failed) - private val yourLocalTimeZoneString by composeTestRule.stringResource(R.string.your_local_time_zone) - - private val previousWeekDescription by composeTestRule.stringResource(R.string.content_description_previous_week) - private val nextWeekDescription by composeTestRule.stringResource(R.string.content_description_next_week) - private val weekDateDescription by composeTestRule.stringResource(R.string.content_description_week_date) - private val availableTimeSlotDescription by composeTestRule.stringResource(R.string.content_description_available_time_slot) - private val unavailableTimeSlotDescription by composeTestRule.stringResource(R.string.content_description_unavailable_time_slot) - - private val scheduleTopAppBarTag by composeTestRule.stringResource(R.string.tag_schedule_top_app_bar) - private val scheduleToolbarTag by composeTestRule.stringResource(R.string.tag_schedule_toolbar) - private val scheduleListTag by composeTestRule.stringResource(R.string.tag_schedule_list) + private val morningString by composeTestRule.stringResource(R.string.feature_teacherschedule_morning) + private val afternoonString by composeTestRule.stringResource(R.string.feature_teacherschedule_afternoon) + private val eveningString by composeTestRule.stringResource(R.string.feature_teacherschedule_evening) + private val loadingString by composeTestRule.stringResource(R.string.feature_teacherschedule_loading) + private val loadFailedString by composeTestRule.stringResource(R.string.feature_teacherschedule_load_failed) + private val yourLocalTimeZoneString by composeTestRule.stringResource(R.string.feature_teacherschedule_your_local_time_zone) + + private val previousWeekDescription by composeTestRule.stringResource( + R.string.feature_teacherschedule_content_description_previous_week, + ) + private val nextWeekDescription by composeTestRule.stringResource(R.string.feature_teacherschedule_content_description_next_week) + private val weekDateDescription by composeTestRule.stringResource(R.string.feature_teacherschedule_content_description_week_date) + private val availableTimeSlotDescription by composeTestRule.stringResource( + R.string.feature_teacherschedule_content_description_available_time_slot, + ) + private val unavailableTimeSlotDescription by composeTestRule.stringResource( + R.string.feature_teacherschedule_content_description_unavailable_time_slot, + ) + + private val scheduleTopAppBarTag by composeTestRule.stringResource(R.string.feature_teacherschedule_tag_schedule_top_app_bar) + private val scheduleToolbarTag by composeTestRule.stringResource(R.string.feature_teacherschedule_tag_schedule_toolbar) + private val scheduleListTag by composeTestRule.stringResource(R.string.feature_teacherschedule_tag_schedule_list) private val scheduleViewState = ScheduleViewState( diff --git a/feature/teacherschedule/src/androidTest/java/com/wei/teachlink/feature/teacherschedule/scheduledetail/ScheduleDetailScreenRobot.kt b/feature/teacherschedule/src/androidTest/java/com/wei/teachlink/feature/teacherschedule/scheduledetail/ScheduleDetailScreenRobot.kt index e1793a96..68262abf 100644 --- a/feature/teacherschedule/src/androidTest/java/com/wei/teachlink/feature/teacherschedule/scheduledetail/ScheduleDetailScreenRobot.kt +++ b/feature/teacherschedule/src/androidTest/java/com/wei/teachlink/feature/teacherschedule/scheduledetail/ScheduleDetailScreenRobot.kt @@ -39,15 +39,15 @@ internal open class ScheduleDetailScreenRobot( ) = ReadOnlyProperty { _, _ -> activity.getString(resId) } // The strings used for matching in these tests - private val startTimeDescription by composeTestRule.stringResource(R.string.content_description_start_time) - private val endTimeDescription by composeTestRule.stringResource(R.string.content_description_end_time) - private val backDescription by composeTestRule.stringResource(R.string.content_description_back) - - private val teacherNameTag by composeTestRule.stringResource(R.string.tag_teacher_name) - private val startTimeTag by composeTestRule.stringResource(R.string.tag_start_time) - private val endTimeTag by composeTestRule.stringResource(R.string.tag_end_time) - private val stateTag by composeTestRule.stringResource(R.string.tag_state) - private val duringDayTypeTag by composeTestRule.stringResource(R.string.tag_during_day_type) + private val startTimeDescription by composeTestRule.stringResource(R.string.feature_teacherschedule_content_description_start_time) + private val endTimeDescription by composeTestRule.stringResource(R.string.feature_teacherschedule_content_description_end_time) + private val backDescription by composeTestRule.stringResource(R.string.feature_teacherschedule_content_description_back) + + private val teacherNameTag by composeTestRule.stringResource(R.string.feature_teacherschedule_tag_teacher_name) + private val startTimeTag by composeTestRule.stringResource(R.string.feature_teacherschedule_tag_start_time) + private val endTimeTag by composeTestRule.stringResource(R.string.feature_teacherschedule_tag_end_time) + private val stateTag by composeTestRule.stringResource(R.string.feature_teacherschedule_tag_state) + private val duringDayTypeTag by composeTestRule.stringResource(R.string.feature_teacherschedule_tag_during_day_type) private val teacherName by lazy { composeTestRule.onNodeWithTag( diff --git a/feature/teacherschedule/src/main/java/com/wei/teachlink/feature/teacherschedule/schedule/ScheduleScreen.kt b/feature/teacherschedule/src/main/java/com/wei/teachlink/feature/teacherschedule/schedule/ScheduleScreen.kt index b5fef107..e0950404 100644 --- a/feature/teacherschedule/src/main/java/com/wei/teachlink/feature/teacherschedule/schedule/ScheduleScreen.kt +++ b/feature/teacherschedule/src/main/java/com/wei/teachlink/feature/teacherschedule/schedule/ScheduleScreen.kt @@ -228,22 +228,22 @@ internal fun ScheduleScreen( */ Box( modifier = - Modifier - .fillMaxSize() - .nestedScroll(nestedScrollConnection), + Modifier + .fillMaxSize() + .nestedScroll(nestedScrollConnection), ) { ScheduleList( modifier = - Modifier - .background(MaterialTheme.colorScheme.background) - .fillMaxSize() - .graphicsLayer { translationY = toolbarState.height + toolbarState.offset } - .pointerInput(Unit) { - detectTapGestures( - onPress = { scope.coroutineContext.cancelChildren() }, - ) - } - .testTag(stringResource(R.string.tag_schedule_list)), + Modifier + .background(MaterialTheme.colorScheme.background) + .fillMaxSize() + .graphicsLayer { translationY = toolbarState.height + toolbarState.offset } + .pointerInput(Unit) { + detectTapGestures( + onPress = { scope.coroutineContext.cancelChildren() }, + ) + } + .testTag(stringResource(R.string.feature_teacherschedule_tag_schedule_list)), timeListUiState = uiStates.timeListUiState, listState = listState, contentPadding = PaddingValues(bottom = if (toolbarState is FixedScrollFlagState) MinToolbarHeight else 0.dp), @@ -254,10 +254,10 @@ internal fun ScheduleScreen( ScheduleToolbar( progress = toolbarState.progress, modifier = - Modifier - .fillMaxWidth() - .height(with(LocalDensity.current) { toolbarState.height.toDp() }) - .graphicsLayer { translationY = toolbarState.offset }, + Modifier + .fillMaxWidth() + .height(with(LocalDensity.current) { toolbarState.height.toDp() }) + .graphicsLayer { translationY = toolbarState.offset }, uiStates = uiStates, onPreviousWeekClick = onPreviousWeekClick, onNextWeekClick = onNextWeekClick, @@ -277,17 +277,17 @@ private fun ScheduleTopAppBar(title: String) { Text( text = title, modifier = - Modifier - .testTag(stringResource(id = R.string.tag_schedule_top_app_bar)) - .semantics { contentDescription = title }, + Modifier + .testTag(stringResource(id = R.string.feature_teacherschedule_tag_schedule_top_app_bar)) + .semantics { contentDescription = title }, ) }, navigationIcon = { }, actions = { }, colors = - TopAppBarDefaults.centerAlignedTopAppBarColors( - containerColor = Color.Transparent, - ), + TopAppBarDefaults.centerAlignedTopAppBarColors( + containerColor = Color.Transparent, + ), ) } @@ -346,28 +346,28 @@ internal fun ScheduleList( is TimeListUiState.Loading -> item { - val loading = stringResource(R.string.loading) + val loading = stringResource(R.string.feature_teacherschedule_loading) Text( text = loading, style = MaterialTheme.typography.bodyLarge, modifier = - Modifier - .padding(SPACING_LARGE.dp) - .semantics { contentDescription = loading }, + Modifier + .padding(SPACING_LARGE.dp) + .semantics { contentDescription = loading }, ) } is TimeListUiState.LoadFailed -> item { - val loadFailed = stringResource(R.string.load_failed) + val loadFailed = stringResource(R.string.feature_teacherschedule_load_failed) Text( - text = stringResource(R.string.load_failed), + text = stringResource(R.string.feature_teacherschedule_load_failed), style = MaterialTheme.typography.bodyLarge, color = MaterialTheme.colorScheme.error, modifier = - Modifier - .padding(SPACING_LARGE.dp) - .semantics { contentDescription = loadFailed }, + Modifier + .padding(SPACING_LARGE.dp) + .semantics { contentDescription = loadFailed }, ) } } @@ -391,7 +391,7 @@ private fun ScheduleToolbar( onTabClick: (Int, OffsetDateTime) -> Unit, ) { Surface( - modifier = modifier.testTag(stringResource(id = R.string.tag_schedule_toolbar)), + modifier = modifier.testTag(stringResource(id = R.string.feature_teacherschedule_tag_schedule_toolbar)), ) { Column { WeekActionBar( @@ -426,19 +426,19 @@ fun WeekActionBar( Box( contentAlignment = Alignment.Center, modifier = - Modifier - .height(actionBarSizeDp.dp) - .fillMaxWidth() - .background(MaterialTheme.colorScheme.surfaceVariant), + Modifier + .height(actionBarSizeDp.dp) + .fillMaxWidth() + .background(MaterialTheme.colorScheme.surfaceVariant), ) { Row( modifier = - Modifier - .fillMaxWidth(), + Modifier + .fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically, ) { - val previousWeekDescription = stringResource(R.string.content_description_previous_week) + val previousWeekDescription = stringResource(R.string.feature_teacherschedule_content_description_previous_week) IconButton( onClick = { if (uiStates.isAvailablePreviousWeek) { @@ -451,28 +451,28 @@ fun WeekActionBar( imageVector = TlIcons.ArrowBackIosNew, contentDescription = null, tint = - if (uiStates.isAvailablePreviousWeek) { - MaterialTheme.colorScheme.primary - } else { - LocalContentColor.current - }, + if (uiStates.isAvailablePreviousWeek) { + MaterialTheme.colorScheme.primary + } else { + LocalContentColor.current + }, ) } val (weekStart, weekEnd) = uiStates.weekDateText val weekDataDescription = - stringResource(R.string.content_description_week_date).format( + stringResource(R.string.feature_teacherschedule_content_description_week_date).format( weekStart, weekEnd, ) val weekDateText = "$weekStart - $weekEnd" TextButton( modifier = - Modifier - .weight(1f) - .semantics { contentDescription = weekDataDescription }, + Modifier + .weight(1f) + .semantics { contentDescription = weekDataDescription }, onClick = { - onWeekDateClick(R.string.clickWeekDate, weekDateText) + onWeekDateClick(R.string.feature_teacherschedule_clickWeekDate, weekDateText) }, ) { Text( @@ -483,7 +483,7 @@ fun WeekActionBar( ) } - val nextWeekDescription = stringResource(R.string.content_description_next_week) + val nextWeekDescription = stringResource(R.string.feature_teacherschedule_content_description_next_week) IconButton( onClick = { onNextWeekClick() @@ -509,9 +509,9 @@ private fun WeekActionBarBottom( Box( contentAlignment = Alignment.Center, modifier = - Modifier - .fillMaxSize() - .padding(horizontal = SPACING_LARGE.dp), + Modifier + .fillMaxSize() + .padding(horizontal = SPACING_LARGE.dp), ) { Column { Spacer(modifier = Modifier.height(SPACING_LARGE.dp)) @@ -622,9 +622,9 @@ fun ScheduleListPreview( TlTheme { ScheduleList( modifier = - Modifier - .background(MaterialTheme.colorScheme.background) - .fillMaxSize(), + Modifier + .background(MaterialTheme.colorScheme.background) + .fillMaxSize(), timeListUiState = timeListUiState, listState = rememberLazyListState(), contentPadding = PaddingValues(bottom = 0.dp), diff --git a/feature/teacherschedule/src/main/java/com/wei/teachlink/feature/teacherschedule/schedule/ui/TimeList.kt b/feature/teacherschedule/src/main/java/com/wei/teachlink/feature/teacherschedule/schedule/ui/TimeList.kt index d9b1af36..fe86031a 100644 --- a/feature/teacherschedule/src/main/java/com/wei/teachlink/feature/teacherschedule/schedule/ui/TimeList.kt +++ b/feature/teacherschedule/src/main/java/com/wei/teachlink/feature/teacherschedule/schedule/ui/TimeList.kt @@ -35,7 +35,7 @@ val timeSlotFormatter: DateTimeFormatter = DateTimeFormatter.ofPattern("H:mm") internal fun YourLocalTimeZoneText(clock: Clock = Clock.systemDefaultZone()) { val yourLocalTimeZone = String.format( - stringResource(R.string.your_local_time_zone), + stringResource(R.string.feature_teacherschedule_your_local_time_zone), clock.zone, yourLocalTimeZoneFormatter.format(OffsetDateTime.now(clock).offset), ) @@ -45,10 +45,10 @@ internal fun YourLocalTimeZoneText(clock: Clock = Clock.systemDefaultZone()) { color = MaterialTheme.colorScheme.onSurfaceVariant, style = MaterialTheme.typography.bodySmall, modifier = - Modifier - .padding(top = SPACING_LARGE.dp) - .padding(horizontal = SPACING_LARGE.dp) - .semantics { contentDescription = yourLocalTimeZone }, + Modifier + .padding(top = SPACING_LARGE.dp) + .padding(horizontal = SPACING_LARGE.dp) + .semantics { contentDescription = yourLocalTimeZone }, ) } @@ -56,10 +56,10 @@ internal fun YourLocalTimeZoneText(clock: Clock = Clock.systemDefaultZone()) { internal fun DuringDay(duringDayType: DuringDayType) { val duringDay = when (duringDayType) { - DuringDayType.Morning -> stringResource(R.string.morning) - DuringDayType.Afternoon -> stringResource(R.string.afternoon) - DuringDayType.Evening -> stringResource(R.string.evening) - else -> stringResource(R.string.morning) + DuringDayType.Morning -> stringResource(R.string.feature_teacherschedule_morning) + DuringDayType.Afternoon -> stringResource(R.string.feature_teacherschedule_afternoon) + DuringDayType.Evening -> stringResource(R.string.feature_teacherschedule_evening) + else -> stringResource(R.string.feature_teacherschedule_morning) } Text( @@ -67,10 +67,10 @@ internal fun DuringDay(duringDayType: DuringDayType) { color = MaterialTheme.colorScheme.onSurface, style = MaterialTheme.typography.bodyMedium, modifier = - Modifier - .padding(top = SPACING_LARGE.dp) - .padding(horizontal = SPACING_LARGE.dp) - .semantics { contentDescription = duringDay }, + Modifier + .padding(top = SPACING_LARGE.dp) + .padding(horizontal = SPACING_LARGE.dp) + .semantics { contentDescription = duringDay }, ) } @@ -100,18 +100,18 @@ private fun AvailableTimeSlot( val startTimeText = timeSlotFormatter.format(timeSlot.start) val availableDescription = String.format( - stringResource(R.string.content_description_available_time_slot), + stringResource(R.string.feature_teacherschedule_content_description_available_time_slot), startTimeText, ) Button( onClick = { onTimeSlotClick() }, modifier = - modifier - .padding(top = SPACING_LARGE.dp) - .padding(horizontal = SPACING_LARGE.dp) - .fillMaxWidth(0.5f) - .semantics { contentDescription = availableDescription }, + modifier + .padding(top = SPACING_LARGE.dp) + .padding(horizontal = SPACING_LARGE.dp) + .fillMaxWidth(0.5f) + .semantics { contentDescription = availableDescription }, shape = shapes.medium, ) { Text( @@ -129,7 +129,7 @@ private fun UnavailableTimeSlot( val startTimeText = timeSlotFormatter.format(timeSlot.start) val unavailableDescription = String.format( - stringResource(R.string.content_description_unavailable_time_slot), + stringResource(R.string.feature_teacherschedule_content_description_unavailable_time_slot), startTimeText, ) @@ -137,17 +137,17 @@ private fun UnavailableTimeSlot( onClick = {}, enabled = false, modifier = - modifier - .padding(top = SPACING_LARGE.dp) - .padding(horizontal = SPACING_LARGE.dp) - .fillMaxWidth(0.5f) - .semantics { contentDescription = unavailableDescription }, + modifier + .padding(top = SPACING_LARGE.dp) + .padding(horizontal = SPACING_LARGE.dp) + .fillMaxWidth(0.5f) + .semantics { contentDescription = unavailableDescription }, shape = shapes.medium, colors = - ButtonDefaults.outlinedButtonColors( - containerColor = MaterialTheme.colorScheme.background, - contentColor = MaterialTheme.colorScheme.onBackground, - ), + ButtonDefaults.outlinedButtonColors( + containerColor = MaterialTheme.colorScheme.background, + contentColor = MaterialTheme.colorScheme.onBackground, + ), border = BorderStroke(1.dp, MaterialTheme.colorScheme.outline), ) { Text( @@ -181,12 +181,12 @@ fun AvailableTimeSlotPreview() { Box(modifier = Modifier.fillMaxWidth()) { AvailableTimeSlot( timeSlot = - IntervalScheduleTimeSlot( - start = OffsetDateTime.now(), - end = OffsetDateTime.now(), - state = ScheduleState.AVAILABLE, - duringDayType = DuringDayType.Morning, - ), + IntervalScheduleTimeSlot( + start = OffsetDateTime.now(), + end = OffsetDateTime.now(), + state = ScheduleState.AVAILABLE, + duringDayType = DuringDayType.Morning, + ), onTimeSlotClick = { }, ) } @@ -200,12 +200,12 @@ fun UnavailableTimeSlotPreview() { Box(modifier = Modifier.fillMaxWidth()) { UnavailableTimeSlot( timeSlot = - IntervalScheduleTimeSlot( - start = OffsetDateTime.now(), - end = OffsetDateTime.now(), - state = ScheduleState.BOOKED, - duringDayType = DuringDayType.Morning, - ), + IntervalScheduleTimeSlot( + start = OffsetDateTime.now(), + end = OffsetDateTime.now(), + state = ScheduleState.BOOKED, + duringDayType = DuringDayType.Morning, + ), ) } } diff --git a/feature/teacherschedule/src/main/java/com/wei/teachlink/feature/teacherschedule/scheduledetail/ScheduleDetailScreen.kt b/feature/teacherschedule/src/main/java/com/wei/teachlink/feature/teacherschedule/scheduledetail/ScheduleDetailScreen.kt index d26a27e9..270139b2 100644 --- a/feature/teacherschedule/src/main/java/com/wei/teachlink/feature/teacherschedule/scheduledetail/ScheduleDetailScreen.kt +++ b/feature/teacherschedule/src/main/java/com/wei/teachlink/feature/teacherschedule/scheduledetail/ScheduleDetailScreen.kt @@ -104,68 +104,68 @@ internal fun ScheduleDetailScreen( val teacherName = uiStates.teacherName.toString() val teacherNameDescription = - stringResource(R.string.content_description_teacher_name).format(teacherName) + stringResource(R.string.feature_teacherschedule_content_description_teacher_name).format(teacherName) Text( text = teacherName, style = MaterialTheme.typography.headlineLarge, modifier = - Modifier - .padding(horizontal = SPACING_LARGE.dp) - .testTag(stringResource(id = R.string.tag_teacher_name)) - .semantics { contentDescription = teacherNameDescription }, + Modifier + .padding(horizontal = SPACING_LARGE.dp) + .testTag(stringResource(id = R.string.feature_teacherschedule_tag_teacher_name)) + .semantics { contentDescription = teacherNameDescription }, ) val startTimeDescription = - stringResource(R.string.content_description_start_time).format(uiStates.start.toString()) + stringResource(R.string.feature_teacherschedule_content_description_start_time).format(uiStates.start.toString()) Text( text = startTimeDescription, style = MaterialTheme.typography.bodyMedium, modifier = - Modifier - .padding(horizontal = SPACING_LARGE.dp) - .padding(top = SPACING_MEDIUM.dp) - .testTag(stringResource(id = R.string.tag_start_time)) - .semantics { contentDescription = startTimeDescription }, + Modifier + .padding(horizontal = SPACING_LARGE.dp) + .padding(top = SPACING_MEDIUM.dp) + .testTag(stringResource(id = R.string.feature_teacherschedule_tag_start_time)) + .semantics { contentDescription = startTimeDescription }, ) val endTimeDescription = - stringResource(R.string.content_description_end_time).format(uiStates.end.toString()) + stringResource(R.string.feature_teacherschedule_content_description_end_time).format(uiStates.end.toString()) Text( text = endTimeDescription, style = MaterialTheme.typography.bodyMedium, modifier = - Modifier - .padding(horizontal = SPACING_LARGE.dp) - .padding(top = SPACING_MEDIUM.dp) - .testTag(stringResource(id = R.string.tag_end_time)) - .semantics { contentDescription = endTimeDescription }, + Modifier + .padding(horizontal = SPACING_LARGE.dp) + .padding(top = SPACING_MEDIUM.dp) + .testTag(stringResource(id = R.string.feature_teacherschedule_tag_end_time)) + .semantics { contentDescription = endTimeDescription }, ) val state = uiStates.state?.name.toString() - val stateDescription = stringResource(R.string.content_description_state).format(state) + val stateDescription = stringResource(R.string.feature_teacherschedule_content_description_state).format(state) Text( text = state, style = MaterialTheme.typography.bodyMedium, modifier = - Modifier - .padding(horizontal = SPACING_LARGE.dp) - .padding(top = SPACING_MEDIUM.dp) - .testTag(stringResource(id = R.string.tag_state)) - .semantics { contentDescription = stateDescription }, + Modifier + .padding(horizontal = SPACING_LARGE.dp) + .padding(top = SPACING_MEDIUM.dp) + .testTag(stringResource(id = R.string.feature_teacherschedule_tag_state)) + .semantics { contentDescription = stateDescription }, ) val duringDayType = uiStates.duringDayType?.name.toString() val duringDayTypeDescription = - stringResource(R.string.content_description_during_day_type).format(duringDayType) + stringResource(R.string.feature_teacherschedule_content_description_during_day_type).format(duringDayType) Text( text = duringDayType, style = MaterialTheme.typography.bodyMedium, modifier = - Modifier - .padding(horizontal = SPACING_LARGE.dp) - .padding(top = SPACING_MEDIUM.dp) - .testTag(stringResource(id = R.string.tag_during_day_type)) - .semantics { contentDescription = duringDayTypeDescription }, + Modifier + .padding(horizontal = SPACING_LARGE.dp) + .padding(top = SPACING_MEDIUM.dp) + .testTag(stringResource(id = R.string.feature_teacherschedule_tag_during_day_type)) + .semantics { contentDescription = duringDayTypeDescription }, ) } } @@ -180,7 +180,7 @@ private fun ScheduleDetailToolbar( verticalAlignment = Alignment.CenterVertically, modifier = modifier.fillMaxWidth(), ) { - val back = stringResource(R.string.content_description_back) + val back = stringResource(R.string.feature_teacherschedule_content_description_back) IconButton( onClick = { onBackClick() }, modifier = Modifier.semantics { contentDescription = back }, @@ -201,13 +201,13 @@ fun ScheduleDetailScreenPreview() { TlTheme { ScheduleDetailScreen( uiStates = - ScheduleDetailViewState( - teacherName = "Teacher Name", - start = nowTime, - end = nowTime.plusMinutes(30), - state = ScheduleState.BOOKED, - duringDayType = DuringDayType.Morning, - ), + ScheduleDetailViewState( + teacherName = "Teacher Name", + start = nowTime, + end = nowTime.plusMinutes(30), + state = ScheduleState.BOOKED, + duringDayType = DuringDayType.Morning, + ), onBackClick = { }, ) } diff --git a/feature/teacherschedule/src/main/res/values-zh-rTW/strings.xml b/feature/teacherschedule/src/main/res/values-zh-rTW/strings.xml index 3a3ba66e..d678e408 100644 --- a/feature/teacherschedule/src/main/res/values-zh-rTW/strings.xml +++ b/feature/teacherschedule/src/main/res/values-zh-rTW/strings.xml @@ -1,31 +1,31 @@ - TeachLink + TeachLink - 您當地的時區: %1$s GMT %2$s - 您正在查詢 %1$s 老師 + 您當地的時區: %1$s GMT %2$s + 您正在查詢 %1$s 老師 \n%2$s 的行事曆。 - 開啟日曆: %s - 上午 - 下午 - 晚上 - Loading… - 🛠️ Load failed + 開啟日曆: %s + 上午 + 下午 + 晚上 + Loading… + 🛠️ Load failed - 日期範圍 %1$s 到 %2$s - 上一週 - 下一週 - 可預訂時段 %s - 不可預定時段 %s + 日期範圍 %1$s 到 %2$s + 上一週 + 下一週 + 可預訂時段 %s + 不可預定時段 %s - %s - 開始時間: %s - 結束時間: %s - 狀態 %s - 時間區間 %s - 上一頁 + %s + 開始時間: %s + 結束時間: %s + 狀態 %s + 時間區間 %s + 上一頁 \ No newline at end of file diff --git a/feature/teacherschedule/src/main/res/values/strings.xml b/feature/teacherschedule/src/main/res/values/strings.xml index 35789389..cdb6b9f9 100644 --- a/feature/teacherschedule/src/main/res/values/strings.xml +++ b/feature/teacherschedule/src/main/res/values/strings.xml @@ -1,41 +1,41 @@ - TeachLink + TeachLink - Your local time zone: %1$s GMT %2$s - You are inquiring about %1$s\'s calendar for + Your local time zone: %1$s GMT %2$s + You are inquiring about %1$s\'s calendar for \n%2$s. - Open calendar: %s - Morning - Afternoon - Evening - Loading… - 🛠️ Load failed + Open calendar: %s + Morning + Afternoon + Evening + Loading… + 🛠️ Load failed - The date range is from %1$s to %2$s - Previous Week - Next Week - Available Time Slot %s - Unavailable Time Slot %s + The date range is from %1$s to %2$s + Previous Week + Next Week + Available Time Slot %s + Unavailable Time Slot %s - %s - Start Time: %s - End Time: %s - State %s - During Day %s - Navigate up + %s + Start Time: %s + End Time: %s + State %s + During Day %s + Navigate up - tag_schedule_top_app_bar - tag_schedule_toolbar - tag_schedule_list + tag_schedule_top_app_bar + tag_schedule_toolbar + tag_schedule_list - tag_teacher_name - tag_start_time - tag_end_time - tag_state - tag_during_day_type + tag_teacher_name + tag_start_time + tag_end_time + tag_state + tag_during_day_type \ No newline at end of file diff --git a/feature/teacherschedule/src/test/java/com/wei/teachlink/feature/teacherschedule/schedule/ScheduleViewModelTest.kt b/feature/teacherschedule/src/test/java/com/wei/teachlink/feature/teacherschedule/schedule/ScheduleViewModelTest.kt index c1bdd44f..17f06347 100644 --- a/feature/teacherschedule/src/test/java/com/wei/teachlink/feature/teacherschedule/schedule/ScheduleViewModelTest.kt +++ b/feature/teacherschedule/src/test/java/com/wei/teachlink/feature/teacherschedule/schedule/ScheduleViewModelTest.kt @@ -100,7 +100,7 @@ class ScheduleViewModelTest { fun `dispatch showSnackBar action should set correct snackbar when message with single arg`() = runTest { // Arrange - val resId = R.string.clickWeekDate + val resId = R.string.feature_teacherschedule_clickWeekDate val message = listOf(TEST_TEACHER_NAME) val testUiText = UiText.StringResource( @@ -128,7 +128,7 @@ class ScheduleViewModelTest { fun `dispatch showSnackBar action should set correct snackbar when message with multiple args`() = runTest { // Arrange - val resId = R.string.inquirying_teacher_calendar + val resId = R.string.feature_teacherschedule_inquirying_teacher_calendar val message = listOf(TEST_TEACHER_NAME, TEST_WEEK_DATE_TEXT) val testUiText = UiText.StringResource( @@ -167,10 +167,10 @@ class ScheduleViewModelTest { copy( selectedIndex = 0, _queryDateUtc = - weekDataHelper.getQueryDateUtc( - queryDateLocal = previousWeekMondayLocalDate, - resetToStartOfDay = true, - ), + weekDataHelper.getQueryDateUtc( + queryDateLocal = previousWeekMondayLocalDate, + resetToStartOfDay = true, + ), ) } @@ -193,10 +193,10 @@ class ScheduleViewModelTest { copy( selectedIndex = 0, _queryDateUtc = - weekDataHelper.getQueryDateUtc( - queryDateLocal = OffsetDateTime.now(fixedClock).getLocalOffsetDateTime(), - resetToStartOfDay = false, - ), + weekDataHelper.getQueryDateUtc( + queryDateLocal = OffsetDateTime.now(fixedClock).getLocalOffsetDateTime(), + resetToStartOfDay = false, + ), ) } @@ -218,10 +218,10 @@ class ScheduleViewModelTest { copy( selectedIndex = 0, _queryDateUtc = - weekDataHelper.getQueryDateUtc( - queryDateLocal = viewModel.states.value.weekStart.plusWeeks(1), - resetToStartOfDay = true, - ), + weekDataHelper.getQueryDateUtc( + queryDateLocal = viewModel.states.value.weekStart.plusWeeks(1), + resetToStartOfDay = true, + ), ) } @@ -243,10 +243,10 @@ class ScheduleViewModelTest { copy( selectedIndex = 0, _queryDateUtc = - weekDataHelper.getQueryDateUtc( - queryDateLocal = viewModel.states.value.weekStart.plusWeeks(1), - resetToStartOfDay = true, - ), + weekDataHelper.getQueryDateUtc( + queryDateLocal = viewModel.states.value.weekStart.plusWeeks(1), + resetToStartOfDay = true, + ), ) } From 1025868eb84169bb165b442a63aaa368b038fbbf Mon Sep 17 00:00:00 2001 From: "Wei.He" Date: Thu, 15 Feb 2024 15:27:26 +0800 Subject: [PATCH 4/4] fix spotless issues --- .../contactme/contactme/ContactMeScreen.kt | 66 ++++++------- .../feature/home/home/HomeScreenRobot.kt | 28 +++--- .../teachlink/feature/home/home/HomeScreen.kt | 24 ++--- .../feature/home/home/MyCoursesContent.kt | 98 +++++++++---------- .../feature/home/home/ui/ContactListCard.kt | 34 +++---- .../feature/home/home/ui/HomeTabRow.kt | 32 +++--- .../feature/home/home/ui/HomeTopBar.kt | 40 ++++---- .../feature/home/home/ui/SkillProgressCard.kt | 26 ++--- .../feature/login/login/LoginScreen.kt | 30 +++--- .../feature/login/welcome/WelcomeScreen.kt | 76 +++++++------- .../schedule/ScheduleScreen.kt | 98 +++++++++---------- .../teacherschedule/schedule/ui/TimeList.kt | 68 ++++++------- .../scheduledetail/ScheduleDetailScreen.kt | 62 ++++++------ .../schedule/ScheduleViewModelTest.kt | 32 +++--- 14 files changed, 357 insertions(+), 357 deletions(-) diff --git a/feature/contactme/src/main/java/com/wei/teachlink/feature/contactme/contactme/ContactMeScreen.kt b/feature/contactme/src/main/java/com/wei/teachlink/feature/contactme/contactme/ContactMeScreen.kt index 13e33f0a..0dbb80a1 100644 --- a/feature/contactme/src/main/java/com/wei/teachlink/feature/contactme/contactme/ContactMeScreen.kt +++ b/feature/contactme/src/main/java/com/wei/teachlink/feature/contactme/contactme/ContactMeScreen.kt @@ -125,8 +125,8 @@ internal fun ContactMeScreen( ) { Surface( modifier = - Modifier - .fillMaxSize(), + Modifier + .fillMaxSize(), ) { if (contentType == TlContentType.DUAL_PANE) { Box( @@ -155,19 +155,19 @@ internal fun ContactMeScreen( ) }, strategy = - HorizontalTwoPaneStrategy( - splitFraction = 0.5f, - gapWidth = SPACING_LARGE.dp, - ), + HorizontalTwoPaneStrategy( + splitFraction = 0.5f, + gapWidth = SPACING_LARGE.dp, + ), displayFeatures = displayFeatures, ) } } else { Box( modifier = - Modifier - .fillMaxSize() - .background(color = MaterialTheme.colorScheme.background), + Modifier + .fillMaxSize() + .background(color = MaterialTheme.colorScheme.background), contentAlignment = Alignment.Center, ) { DecorativeBackgroundText( @@ -309,10 +309,10 @@ internal fun DisplayHeadShot( painter = painter, contentDescription = profilePictureDescription, modifier = - modifier - .clip(CircleShape) - .size(300.dp) - .border(2.dp, MaterialTheme.colorScheme.onBackground, CircleShape), + modifier + .clip(CircleShape) + .size(300.dp) + .border(2.dp, MaterialTheme.colorScheme.onBackground, CircleShape), ) } @@ -324,19 +324,19 @@ fun ContactMeCard( ) { Card( modifier = - modifier - .padding(horizontal = SPACING_EXTRA_LARGE.dp) - .clip(CardDefaults.shape), + modifier + .padding(horizontal = SPACING_EXTRA_LARGE.dp) + .clip(CardDefaults.shape), colors = - CardDefaults.cardColors( - containerColor = MaterialTheme.colorScheme.surfaceVariant, - ), + CardDefaults.cardColors( + containerColor = MaterialTheme.colorScheme.surfaceVariant, + ), ) { Column( modifier = - Modifier - .padding(SPACING_LARGE.dp) - .fillMaxWidth(), + Modifier + .padding(SPACING_LARGE.dp) + .fillMaxWidth(), ) { Row( modifier = Modifier.fillMaxWidth(), @@ -384,9 +384,9 @@ private fun NameAndPosition( text = name, style = MaterialTheme.typography.headlineSmall, modifier = - Modifier - .baselineHeight(32.dp) - .semantics { contentDescription = name }, + Modifier + .baselineHeight(32.dp) + .semantics { contentDescription = name }, ) val position = uiStates.position Text( @@ -394,10 +394,10 @@ private fun NameAndPosition( style = MaterialTheme.typography.bodyLarge, color = MaterialTheme.colorScheme.onSurfaceVariant, modifier = - Modifier - .padding(bottom = 20.dp) - .baselineHeight(SPACING_EXTRA_LARGE.dp) - .semantics { contentDescription = position }, + Modifier + .padding(bottom = 20.dp) + .baselineHeight(SPACING_EXTRA_LARGE.dp) + .semantics { contentDescription = position }, ) } } @@ -424,10 +424,10 @@ private fun PhoneButton( onPhoneClick() }, modifier = - Modifier - .clip(CircleShape) - .background(MaterialTheme.colorScheme.surface) - .semantics { contentDescription = phoneDescription }, + Modifier + .clip(CircleShape) + .background(MaterialTheme.colorScheme.surface) + .semantics { contentDescription = phoneDescription }, ) { Icon( imageVector = TlIcons.Phone, diff --git a/feature/home/src/androidTest/java/com/wei/teachlink/feature/home/home/HomeScreenRobot.kt b/feature/home/src/androidTest/java/com/wei/teachlink/feature/home/home/HomeScreenRobot.kt index 6134970a..6efc824e 100644 --- a/feature/home/src/androidTest/java/com/wei/teachlink/feature/home/home/HomeScreenRobot.kt +++ b/feature/home/src/androidTest/java/com/wei/teachlink/feature/home/home/HomeScreenRobot.kt @@ -317,18 +317,18 @@ val testHomeViewState: HomeViewState = selectedTab = Tab.MY_COURSES, chatCount = 102, myCoursesContentState = - MyCoursesContentState( - courseProgress = 20, - courseCount = 14, - pupilRating = 9.9, - tutorName = "TEST_TUTOR_NAME", - className = "TEST_CLASS_NAME", - lessonsCountDisplay = "30+", - ratingCount = 4.9, - startedDate = "11.04", - contacts = listOf(), - skillName = "TEST_SKILL_NAME", - skillLevel = "TEST_SKILL_LEVEL", - skillLevelProgress = 64, - ), + MyCoursesContentState( + courseProgress = 20, + courseCount = 14, + pupilRating = 9.9, + tutorName = "TEST_TUTOR_NAME", + className = "TEST_CLASS_NAME", + lessonsCountDisplay = "30+", + ratingCount = 4.9, + startedDate = "11.04", + contacts = listOf(), + skillName = "TEST_SKILL_NAME", + skillLevel = "TEST_SKILL_LEVEL", + skillLevelProgress = 64, + ), ) diff --git a/feature/home/src/main/java/com/wei/teachlink/feature/home/home/HomeScreen.kt b/feature/home/src/main/java/com/wei/teachlink/feature/home/home/HomeScreen.kt index ca9d55ad..512240ea 100644 --- a/feature/home/src/main/java/com/wei/teachlink/feature/home/home/HomeScreen.kt +++ b/feature/home/src/main/java/com/wei/teachlink/feature/home/home/HomeScreen.kt @@ -202,8 +202,8 @@ private fun UnavailableScreenContent() { color = MaterialTheme.colorScheme.error, style = MaterialTheme.typography.headlineMedium, modifier = - Modifier - .semantics { contentDescription = screenNotAvailable }, + Modifier + .semantics { contentDescription = screenNotAvailable }, ) Spacer(modifier = Modifier.weight(1f)) } @@ -213,9 +213,9 @@ private fun UnavailableScreenContent() { private fun LoadingErrorContent() { Box( modifier = - Modifier - .fillMaxSize() - .testTag(stringResource(R.string.feature_home_tag_loading_error_content)), + Modifier + .fillMaxSize() + .testTag(stringResource(R.string.feature_home_tag_loading_error_content)), ) { // TODO Error Content } @@ -225,9 +225,9 @@ private fun LoadingErrorContent() { private fun LoadingContent() { Box( modifier = - Modifier - .fillMaxSize() - .testTag(stringResource(R.string.feature_home_tag_loading_content)), + Modifier + .fillMaxSize() + .testTag(stringResource(R.string.feature_home_tag_loading_content)), contentAlignment = Alignment.Center, ) { CircularProgressIndicator(modifier = Modifier.size(30.dp)) @@ -240,10 +240,10 @@ fun HomeScreenPreview() { TlTheme { HomeScreen( uiStates = - HomeViewState( - loadingState = HomeViewLoadingState.Success, - userDisplayName = "Wei", - ), + HomeViewState( + loadingState = HomeViewLoadingState.Success, + userDisplayName = "Wei", + ), isPreview = true, onTabClick = { }, ) diff --git a/feature/home/src/main/java/com/wei/teachlink/feature/home/home/MyCoursesContent.kt b/feature/home/src/main/java/com/wei/teachlink/feature/home/home/MyCoursesContent.kt index 227330d6..1bae48c3 100644 --- a/feature/home/src/main/java/com/wei/teachlink/feature/home/home/MyCoursesContent.kt +++ b/feature/home/src/main/java/com/wei/teachlink/feature/home/home/MyCoursesContent.kt @@ -91,9 +91,9 @@ fun MyCoursesContent( Spacer(modifier = Modifier.width(SPACING_SMALL.dp)) SkillProgressCard( modifier = - Modifier - .size(cardSize.dp) - .weight(1f), + Modifier + .size(cardSize.dp) + .weight(1f), skillName = uiStates.skillName, skillLevel = uiStates.skillLevel, progress = uiStates.skillLevelProgress, @@ -123,11 +123,11 @@ fun CourseProgressCard( StatusCard( modifier = - modifier - .testTag(stringResource(R.string.feature_home_tag_course_progress_card)) - .semantics { - contentDescription = contentCourseProgressCard - }, + modifier + .testTag(stringResource(R.string.feature_home_tag_course_progress_card)) + .semantics { + contentDescription = contentCourseProgressCard + }, onClick = onClick, content = { Row { @@ -163,11 +163,11 @@ fun PupilRatingCard( StatusCard( modifier = - modifier - .testTag(stringResource(R.string.feature_home_tag_pupil_rating_card)) - .semantics { - contentDescription = contentPupilRatingCard - }, + modifier + .testTag(stringResource(R.string.feature_home_tag_pupil_rating_card)) + .semantics { + contentDescription = contentPupilRatingCard + }, onClick = onClick, content = { Row { @@ -267,12 +267,12 @@ private fun ClassInfo( Row( modifier = - Modifier - .padding(horizontal = SPACING_SMALL.dp) - .testTag(stringResource(R.string.feature_home_tag_class_info)) - .semantics { - contentDescription = contentClassInfo - }, + Modifier + .padding(horizontal = SPACING_SMALL.dp) + .testTag(stringResource(R.string.feature_home_tag_class_info)) + .semantics { + contentDescription = contentClassInfo + }, ) { Row( verticalAlignment = Alignment.Bottom, @@ -322,12 +322,12 @@ private fun ClassInfo( private fun ClassName(className: String) { Row( modifier = - Modifier - .padding(horizontal = SPACING_SMALL.dp) - .testTag(stringResource(R.string.feature_home_tag_class_name)) - .semantics { - contentDescription = className - }, + Modifier + .padding(horizontal = SPACING_SMALL.dp) + .testTag(stringResource(R.string.feature_home_tag_class_name)) + .semantics { + contentDescription = className + }, ) { Text( text = className, @@ -349,18 +349,18 @@ private fun TutorButton( Button( onClick = onTutorClick, colors = - ButtonDefaults.buttonColors( - containerColor = MaterialTheme.colorScheme.background, - ), + ButtonDefaults.buttonColors( + containerColor = MaterialTheme.colorScheme.background, + ), contentPadding = ButtonDefaults.TextButtonContentPadding, modifier = - Modifier - .testTag( - stringResource(R.string.feature_home_tag_tutor_button), - ) - .semantics { - contentDescription = contentTutorButton - }, + Modifier + .testTag( + stringResource(R.string.feature_home_tag_tutor_button), + ) + .semantics { + contentDescription = contentTutorButton + }, ) { Icon( imageVector = TlIcons.Person, @@ -396,20 +396,20 @@ fun MyCoursesContentPreview() { MyCoursesContent( modifier = Modifier.padding(horizontal = SPACING_LARGE.dp), uiStates = - MyCoursesContentState( - courseProgress = 20, - courseCount = 30, - pupilRating = 9.9, - tutorName = TEST_TUTOR_NAME, - className = TEST_CLASS_NAME, - lessonsCountDisplay = "40+", - ratingCount = 4.9, - startedDate = "11.04", - contacts = TestContacts, - skillName = TEST_SKILL_NAME, - skillLevel = TEST_SKILL_LEVEL, - skillLevelProgress = TEST_SKILL_LEVEL_PROGRESS, - ), + MyCoursesContentState( + courseProgress = 20, + courseCount = 30, + pupilRating = 9.9, + tutorName = TEST_TUTOR_NAME, + className = TEST_CLASS_NAME, + lessonsCountDisplay = "40+", + ratingCount = 4.9, + startedDate = "11.04", + contacts = TestContacts, + skillName = TEST_SKILL_NAME, + skillLevel = TEST_SKILL_LEVEL, + skillLevelProgress = TEST_SKILL_LEVEL_PROGRESS, + ), isPreview = true, onCardClick = {}, ) diff --git a/feature/home/src/main/java/com/wei/teachlink/feature/home/home/ui/ContactListCard.kt b/feature/home/src/main/java/com/wei/teachlink/feature/home/home/ui/ContactListCard.kt index 20e80e80..17b3c449 100644 --- a/feature/home/src/main/java/com/wei/teachlink/feature/home/home/ui/ContactListCard.kt +++ b/feature/home/src/main/java/com/wei/teachlink/feature/home/home/ui/ContactListCard.kt @@ -53,16 +53,16 @@ fun ContactCard( Card( modifier = - modifier - .semantics { - contentDescription = contactCard - }, + modifier + .semantics { + contentDescription = contactCard + }, shape = shapes.extraLarge, colors = - CardDefaults.cardColors( - contentColor = MaterialTheme.colorScheme.onSecondary, - containerColor = MaterialTheme.colorScheme.secondary, - ), + CardDefaults.cardColors( + contentColor = MaterialTheme.colorScheme.onSecondary, + containerColor = MaterialTheme.colorScheme.secondary, + ), ) { Column( modifier = Modifier.padding(SPACING_MEDIUM.dp), @@ -107,17 +107,17 @@ internal fun ContactAvatar( Box( modifier = - modifier - .size(CONTACT_HEAD_SHOT_SIZE.dp) - .statusIndicator(status), + modifier + .size(CONTACT_HEAD_SHOT_SIZE.dp) + .statusIndicator(status), ) { Image( painter = painter, contentDescription = profilePictureDescription, modifier = - Modifier - .matchParentSize() - .clip(CircleShape), + Modifier + .matchParentSize() + .clip(CircleShape), ) } } @@ -172,9 +172,9 @@ fun Modifier.statusIndicator( fun PlaceholderAvatar(modifier: Modifier = Modifier) { Box( modifier = - modifier - .background(MaterialTheme.colorScheme.secondary) - .size(CONTACT_HEAD_SHOT_SIZE.dp), + modifier + .background(MaterialTheme.colorScheme.secondary) + .size(CONTACT_HEAD_SHOT_SIZE.dp), ) } diff --git a/feature/home/src/main/java/com/wei/teachlink/feature/home/home/ui/HomeTabRow.kt b/feature/home/src/main/java/com/wei/teachlink/feature/home/home/ui/HomeTabRow.kt index d7a5263a..9a3f6f4e 100644 --- a/feature/home/src/main/java/com/wei/teachlink/feature/home/home/ui/HomeTabRow.kt +++ b/feature/home/src/main/java/com/wei/teachlink/feature/home/home/ui/HomeTabRow.kt @@ -74,9 +74,9 @@ fun MyCoursesTab( selected = isSelected, onClick = onTabClick, modifier = - Modifier.semantics { - contentDescription = myCourses - }, + Modifier.semantics { + contentDescription = myCourses + }, ) { Text( text = myCourses, @@ -100,9 +100,9 @@ fun ChatTab( selected = isSelected, onClick = onTabClick, modifier = - Modifier.semantics { - contentDescription = chats - }, + Modifier.semantics { + contentDescription = chats + }, ) { Row( modifier = Modifier.padding(SPACING_LARGE.dp), @@ -125,9 +125,9 @@ fun ChatTab( fun ChatCountBadge(count: String) { Box( modifier = - Modifier - .clip(shape = shapes.medium) - .background(color = MaterialTheme.colorScheme.primary), + Modifier + .clip(shape = shapes.medium) + .background(color = MaterialTheme.colorScheme.primary), ) { Text( text = count, @@ -149,9 +149,9 @@ fun TutorsTab( selected = isSelected, onClick = onTabClick, modifier = - Modifier.semantics { - contentDescription = tutors - }, + Modifier.semantics { + contentDescription = tutors + }, ) { Text( text = tutors, @@ -168,10 +168,10 @@ fun HomeTabRowPreview() { TlTheme { HomeTabRow( uiStates = - HomeViewState( - selectedTab = Tab.MY_COURSES, - chatCount = 999, - ), + HomeViewState( + selectedTab = Tab.MY_COURSES, + chatCount = 999, + ), onTabSelected = {}, ) } diff --git a/feature/home/src/main/java/com/wei/teachlink/feature/home/home/ui/HomeTopBar.kt b/feature/home/src/main/java/com/wei/teachlink/feature/home/home/ui/HomeTopBar.kt index 92874197..44cc1733 100644 --- a/feature/home/src/main/java/com/wei/teachlink/feature/home/home/ui/HomeTopBar.kt +++ b/feature/home/src/main/java/com/wei/teachlink/feature/home/home/ui/HomeTopBar.kt @@ -67,9 +67,9 @@ fun HomeTopBar( style = MaterialTheme.typography.headlineMedium, fontWeight = FontWeight.Normal, modifier = - Modifier - .testTag(stringResource(R.string.feature_home_tag_hello_user_name_text)) - .semantics { contentDescription = helloUserName }, + Modifier + .testTag(stringResource(R.string.feature_home_tag_hello_user_name_text)) + .semantics { contentDescription = helloUserName }, ) Spacer(modifier = Modifier.height(SPACING_SMALL.dp)) } @@ -88,10 +88,10 @@ private fun AddUserButton( onAddUserClick() }, modifier = - Modifier - .clip(CircleShape) - .background(MaterialTheme.colorScheme.surfaceVariant) - .semantics { contentDescription = addUser }, + Modifier + .clip(CircleShape) + .background(MaterialTheme.colorScheme.surfaceVariant) + .semantics { contentDescription = addUser }, ) { Icon( imageVector = TlIcons.Add, @@ -115,20 +115,20 @@ internal fun UserAvatar( IconButton( onClick = onUserProfileImageClick, modifier = - modifier - .size(48.dp) - .testTag(stringResource(R.string.feature_home_tag_user_avatar)) - .semantics { - contentDescription = profilePictureDescription - }, + modifier + .size(48.dp) + .testTag(stringResource(R.string.feature_home_tag_user_avatar)) + .semantics { + contentDescription = profilePictureDescription + }, ) { Image( painter = painter, contentDescription = null, modifier = - modifier - .clip(CircleShape) - .fillMaxSize(), + modifier + .clip(CircleShape) + .fillMaxSize(), ) } } @@ -140,10 +140,10 @@ private fun MenuButton(onMenuClick: () -> Unit) { IconButton( onClick = onMenuClick, modifier = - Modifier - .clip(CircleShape) - .background(MaterialTheme.colorScheme.surfaceVariant) - .semantics { contentDescription = menu }, + Modifier + .clip(CircleShape) + .background(MaterialTheme.colorScheme.surfaceVariant) + .semantics { contentDescription = menu }, ) { Icon( imageVector = TlIcons.Menu, diff --git a/feature/home/src/main/java/com/wei/teachlink/feature/home/home/ui/SkillProgressCard.kt b/feature/home/src/main/java/com/wei/teachlink/feature/home/home/ui/SkillProgressCard.kt index c91f6031..b6cd28b4 100644 --- a/feature/home/src/main/java/com/wei/teachlink/feature/home/home/ui/SkillProgressCard.kt +++ b/feature/home/src/main/java/com/wei/teachlink/feature/home/home/ui/SkillProgressCard.kt @@ -47,15 +47,15 @@ fun SkillProgressCard( ) Card( modifier = - modifier.semantics { - contentDescription = contentSkillProgressCard - }, + modifier.semantics { + contentDescription = contentSkillProgressCard + }, shape = shapes.extraLarge, colors = - CardDefaults.cardColors( - contentColor = MaterialTheme.colorScheme.onPrimary, - containerColor = MaterialTheme.colorScheme.primary, - ), + CardDefaults.cardColors( + contentColor = MaterialTheme.colorScheme.onPrimary, + containerColor = MaterialTheme.colorScheme.primary, + ), onClick = onClick, ) { Column( @@ -79,9 +79,9 @@ fun SkillProgressCard( Spacer(modifier = Modifier.height(SPACING_LARGE.dp)) CircularProgress( modifier = - Modifier - .weight(1f) - .testTag(stringResource(R.string.feature_home_tag_circular_progress)), + Modifier + .weight(1f) + .testTag(stringResource(R.string.feature_home_tag_circular_progress)), progress = progress, ) }, @@ -134,9 +134,9 @@ fun HomeScreenPreview() { TlTheme { SkillProgressCard( modifier = - Modifier - .width(200.dp) - .height(152.dp), + Modifier + .width(200.dp) + .height(152.dp), skillName = "Business English", skillLevel = "Advanced level", progress = 64, diff --git a/feature/login/src/main/java/com/wei/teachlink/feature/login/login/LoginScreen.kt b/feature/login/src/main/java/com/wei/teachlink/feature/login/login/LoginScreen.kt index 9c7dda45..d6a6f7fa 100644 --- a/feature/login/src/main/java/com/wei/teachlink/feature/login/login/LoginScreen.kt +++ b/feature/login/src/main/java/com/wei/teachlink/feature/login/login/LoginScreen.kt @@ -112,9 +112,9 @@ internal fun LoginScreen( ) { Box( modifier = - Modifier - .fillMaxSize() - .padding(SPACING_LARGE.dp), + Modifier + .fillMaxSize() + .padding(SPACING_LARGE.dp), contentAlignment = Alignment.Center, ) { Column( @@ -153,8 +153,8 @@ private fun Title(modifier: Modifier = Modifier) { text = title, style = MaterialTheme.typography.displayMedium, modifier = - modifier - .semantics { contentDescription = "" }, + modifier + .semantics { contentDescription = "" }, ) } @@ -166,8 +166,8 @@ internal fun AccountTextField(accountState: MutableState) { TextField( value = accountState.value, modifier = - Modifier - .semantics { contentDescription = accountDescription }, + Modifier + .semantics { contentDescription = accountDescription }, onValueChange = { accountState.value = it }, @@ -186,8 +186,8 @@ internal fun PasswordTextField(passwordState: MutableState) { TextField( value = passwordState.value, modifier = - Modifier - .semantics { contentDescription = passwordDescription }, + Modifier + .semantics { contentDescription = passwordDescription }, onValueChange = { passwordState.value = it }, @@ -207,9 +207,9 @@ internal fun ForgotPasswordText(modifier: Modifier = Modifier) { text = forgotPassword, style = MaterialTheme.typography.bodyMedium, modifier = - Modifier - .padding(top = SPACING_LARGE.dp) - .semantics { contentDescription = forgotPassword }, + Modifier + .padding(top = SPACING_LARGE.dp) + .semantics { contentDescription = forgotPassword }, ) } @@ -227,9 +227,9 @@ internal fun LoginButton( login(accountState.value, passwordState.value) }, modifier = - Modifier - .padding(top = SPACING_SMALL.dp) - .semantics { contentDescription = loginTextDescription }, + Modifier + .padding(top = SPACING_SMALL.dp) + .semantics { contentDescription = loginTextDescription }, ) { Text( loginText, diff --git a/feature/login/src/main/java/com/wei/teachlink/feature/login/welcome/WelcomeScreen.kt b/feature/login/src/main/java/com/wei/teachlink/feature/login/welcome/WelcomeScreen.kt index 37c361fe..06114643 100644 --- a/feature/login/src/main/java/com/wei/teachlink/feature/login/welcome/WelcomeScreen.kt +++ b/feature/login/src/main/java/com/wei/teachlink/feature/login/welcome/WelcomeScreen.kt @@ -118,8 +118,8 @@ internal fun WelcomeScreen( Surface(modifier = Modifier.fillMaxSize()) { Column( modifier = - Modifier - .fillMaxSize(), + Modifier + .fillMaxSize(), ) { if (withTopSpacer) { Spacer(Modifier.windowInsetsTopHeight(WindowInsets.safeDrawing)) @@ -127,13 +127,13 @@ internal fun WelcomeScreen( WelcomeScreenToolbar( modifier = - if (isPortrait) { - Modifier.padding(horizontal = SPACING_LARGE.dp) - } else { - Modifier.padding( - horizontal = SPACING_EXTRA_LARGE.dp, - ) - }, + if (isPortrait) { + Modifier.padding(horizontal = SPACING_LARGE.dp) + } else { + Modifier.padding( + horizontal = SPACING_EXTRA_LARGE.dp, + ) + }, isPreview = isPreview, onGetStartedButtonClicked = onGetStartedButtonClicked, ) @@ -214,9 +214,9 @@ fun WelcomeGraphics( painter = painter, contentDescription = null, modifier = - modifier - .fillMaxSize() - .testTag(stringResource(R.string.feature_login_tag_welcome_graphics)), + modifier + .fillMaxSize() + .testTag(stringResource(R.string.feature_login_tag_welcome_graphics)), contentScale = ContentScale.Crop, ) } @@ -250,16 +250,16 @@ fun WelcomeTitlePortrait( Box( modifier = - modifier - .fillMaxWidth() - .gradientBackgroundPortrait(), + modifier + .fillMaxWidth() + .gradientBackgroundPortrait(), ) { Text( modifier = - Modifier - .padding(vertical = SPACING_EXTRA_LARGE.dp) - .semantics { contentDescription = welcomeTitle } - .align(alignment = Alignment.Center), + Modifier + .padding(vertical = SPACING_EXTRA_LARGE.dp) + .semantics { contentDescription = welcomeTitle } + .align(alignment = Alignment.Center), style = style, text = welcomeTitle, textAlign = TextAlign.Center, @@ -277,16 +277,16 @@ fun WelcomeTitleLandscape( Box( modifier = - modifier - .fillMaxHeight() - .gradientBackgroundLandscape(), + modifier + .fillMaxHeight() + .gradientBackgroundLandscape(), ) { Text( modifier = - Modifier - .padding(horizontal = SPACING_EXTRA_LARGE.dp) - .semantics { contentDescription = welcomeTitle } - .align(alignment = Alignment.Center), + Modifier + .padding(horizontal = SPACING_EXTRA_LARGE.dp) + .semantics { contentDescription = welcomeTitle } + .align(alignment = Alignment.Center), style = style, text = welcomeTitle, textAlign = TextAlign.Start, @@ -298,25 +298,25 @@ fun WelcomeTitleLandscape( internal fun Modifier.gradientBackgroundPortrait(): Modifier = this.background( brush = - Brush.verticalGradient( - colors = - listOf( - Color.Black.copy(alpha = 0f), - Color.Black.copy(alpha = 0.5f), - ), + Brush.verticalGradient( + colors = + listOf( + Color.Black.copy(alpha = 0f), + Color.Black.copy(alpha = 0.5f), ), + ), ) internal fun Modifier.gradientBackgroundLandscape(): Modifier = this.background( brush = - Brush.horizontalGradient( - colors = - listOf( - Color.White.copy(alpha = 0.5f), - Color.White.copy(alpha = 0f), - ), + Brush.horizontalGradient( + colors = + listOf( + Color.White.copy(alpha = 0.5f), + Color.White.copy(alpha = 0f), ), + ), ) @DevicePortraitPreviews diff --git a/feature/teacherschedule/src/main/java/com/wei/teachlink/feature/teacherschedule/schedule/ScheduleScreen.kt b/feature/teacherschedule/src/main/java/com/wei/teachlink/feature/teacherschedule/schedule/ScheduleScreen.kt index e0950404..a091e2a1 100644 --- a/feature/teacherschedule/src/main/java/com/wei/teachlink/feature/teacherschedule/schedule/ScheduleScreen.kt +++ b/feature/teacherschedule/src/main/java/com/wei/teachlink/feature/teacherschedule/schedule/ScheduleScreen.kt @@ -228,22 +228,22 @@ internal fun ScheduleScreen( */ Box( modifier = - Modifier - .fillMaxSize() - .nestedScroll(nestedScrollConnection), + Modifier + .fillMaxSize() + .nestedScroll(nestedScrollConnection), ) { ScheduleList( modifier = - Modifier - .background(MaterialTheme.colorScheme.background) - .fillMaxSize() - .graphicsLayer { translationY = toolbarState.height + toolbarState.offset } - .pointerInput(Unit) { - detectTapGestures( - onPress = { scope.coroutineContext.cancelChildren() }, - ) - } - .testTag(stringResource(R.string.feature_teacherschedule_tag_schedule_list)), + Modifier + .background(MaterialTheme.colorScheme.background) + .fillMaxSize() + .graphicsLayer { translationY = toolbarState.height + toolbarState.offset } + .pointerInput(Unit) { + detectTapGestures( + onPress = { scope.coroutineContext.cancelChildren() }, + ) + } + .testTag(stringResource(R.string.feature_teacherschedule_tag_schedule_list)), timeListUiState = uiStates.timeListUiState, listState = listState, contentPadding = PaddingValues(bottom = if (toolbarState is FixedScrollFlagState) MinToolbarHeight else 0.dp), @@ -254,10 +254,10 @@ internal fun ScheduleScreen( ScheduleToolbar( progress = toolbarState.progress, modifier = - Modifier - .fillMaxWidth() - .height(with(LocalDensity.current) { toolbarState.height.toDp() }) - .graphicsLayer { translationY = toolbarState.offset }, + Modifier + .fillMaxWidth() + .height(with(LocalDensity.current) { toolbarState.height.toDp() }) + .graphicsLayer { translationY = toolbarState.offset }, uiStates = uiStates, onPreviousWeekClick = onPreviousWeekClick, onNextWeekClick = onNextWeekClick, @@ -277,17 +277,17 @@ private fun ScheduleTopAppBar(title: String) { Text( text = title, modifier = - Modifier - .testTag(stringResource(id = R.string.feature_teacherschedule_tag_schedule_top_app_bar)) - .semantics { contentDescription = title }, + Modifier + .testTag(stringResource(id = R.string.feature_teacherschedule_tag_schedule_top_app_bar)) + .semantics { contentDescription = title }, ) }, navigationIcon = { }, actions = { }, colors = - TopAppBarDefaults.centerAlignedTopAppBarColors( - containerColor = Color.Transparent, - ), + TopAppBarDefaults.centerAlignedTopAppBarColors( + containerColor = Color.Transparent, + ), ) } @@ -351,9 +351,9 @@ internal fun ScheduleList( text = loading, style = MaterialTheme.typography.bodyLarge, modifier = - Modifier - .padding(SPACING_LARGE.dp) - .semantics { contentDescription = loading }, + Modifier + .padding(SPACING_LARGE.dp) + .semantics { contentDescription = loading }, ) } @@ -365,9 +365,9 @@ internal fun ScheduleList( style = MaterialTheme.typography.bodyLarge, color = MaterialTheme.colorScheme.error, modifier = - Modifier - .padding(SPACING_LARGE.dp) - .semantics { contentDescription = loadFailed }, + Modifier + .padding(SPACING_LARGE.dp) + .semantics { contentDescription = loadFailed }, ) } } @@ -426,15 +426,15 @@ fun WeekActionBar( Box( contentAlignment = Alignment.Center, modifier = - Modifier - .height(actionBarSizeDp.dp) - .fillMaxWidth() - .background(MaterialTheme.colorScheme.surfaceVariant), + Modifier + .height(actionBarSizeDp.dp) + .fillMaxWidth() + .background(MaterialTheme.colorScheme.surfaceVariant), ) { Row( modifier = - Modifier - .fillMaxWidth(), + Modifier + .fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically, ) { @@ -451,11 +451,11 @@ fun WeekActionBar( imageVector = TlIcons.ArrowBackIosNew, contentDescription = null, tint = - if (uiStates.isAvailablePreviousWeek) { - MaterialTheme.colorScheme.primary - } else { - LocalContentColor.current - }, + if (uiStates.isAvailablePreviousWeek) { + MaterialTheme.colorScheme.primary + } else { + LocalContentColor.current + }, ) } @@ -468,9 +468,9 @@ fun WeekActionBar( val weekDateText = "$weekStart - $weekEnd" TextButton( modifier = - Modifier - .weight(1f) - .semantics { contentDescription = weekDataDescription }, + Modifier + .weight(1f) + .semantics { contentDescription = weekDataDescription }, onClick = { onWeekDateClick(R.string.feature_teacherschedule_clickWeekDate, weekDateText) }, @@ -509,9 +509,9 @@ private fun WeekActionBarBottom( Box( contentAlignment = Alignment.Center, modifier = - Modifier - .fillMaxSize() - .padding(horizontal = SPACING_LARGE.dp), + Modifier + .fillMaxSize() + .padding(horizontal = SPACING_LARGE.dp), ) { Column { Spacer(modifier = Modifier.height(SPACING_LARGE.dp)) @@ -622,9 +622,9 @@ fun ScheduleListPreview( TlTheme { ScheduleList( modifier = - Modifier - .background(MaterialTheme.colorScheme.background) - .fillMaxSize(), + Modifier + .background(MaterialTheme.colorScheme.background) + .fillMaxSize(), timeListUiState = timeListUiState, listState = rememberLazyListState(), contentPadding = PaddingValues(bottom = 0.dp), diff --git a/feature/teacherschedule/src/main/java/com/wei/teachlink/feature/teacherschedule/schedule/ui/TimeList.kt b/feature/teacherschedule/src/main/java/com/wei/teachlink/feature/teacherschedule/schedule/ui/TimeList.kt index fe86031a..0bd58b8c 100644 --- a/feature/teacherschedule/src/main/java/com/wei/teachlink/feature/teacherschedule/schedule/ui/TimeList.kt +++ b/feature/teacherschedule/src/main/java/com/wei/teachlink/feature/teacherschedule/schedule/ui/TimeList.kt @@ -45,10 +45,10 @@ internal fun YourLocalTimeZoneText(clock: Clock = Clock.systemDefaultZone()) { color = MaterialTheme.colorScheme.onSurfaceVariant, style = MaterialTheme.typography.bodySmall, modifier = - Modifier - .padding(top = SPACING_LARGE.dp) - .padding(horizontal = SPACING_LARGE.dp) - .semantics { contentDescription = yourLocalTimeZone }, + Modifier + .padding(top = SPACING_LARGE.dp) + .padding(horizontal = SPACING_LARGE.dp) + .semantics { contentDescription = yourLocalTimeZone }, ) } @@ -67,10 +67,10 @@ internal fun DuringDay(duringDayType: DuringDayType) { color = MaterialTheme.colorScheme.onSurface, style = MaterialTheme.typography.bodyMedium, modifier = - Modifier - .padding(top = SPACING_LARGE.dp) - .padding(horizontal = SPACING_LARGE.dp) - .semantics { contentDescription = duringDay }, + Modifier + .padding(top = SPACING_LARGE.dp) + .padding(horizontal = SPACING_LARGE.dp) + .semantics { contentDescription = duringDay }, ) } @@ -107,11 +107,11 @@ private fun AvailableTimeSlot( Button( onClick = { onTimeSlotClick() }, modifier = - modifier - .padding(top = SPACING_LARGE.dp) - .padding(horizontal = SPACING_LARGE.dp) - .fillMaxWidth(0.5f) - .semantics { contentDescription = availableDescription }, + modifier + .padding(top = SPACING_LARGE.dp) + .padding(horizontal = SPACING_LARGE.dp) + .fillMaxWidth(0.5f) + .semantics { contentDescription = availableDescription }, shape = shapes.medium, ) { Text( @@ -137,17 +137,17 @@ private fun UnavailableTimeSlot( onClick = {}, enabled = false, modifier = - modifier - .padding(top = SPACING_LARGE.dp) - .padding(horizontal = SPACING_LARGE.dp) - .fillMaxWidth(0.5f) - .semantics { contentDescription = unavailableDescription }, + modifier + .padding(top = SPACING_LARGE.dp) + .padding(horizontal = SPACING_LARGE.dp) + .fillMaxWidth(0.5f) + .semantics { contentDescription = unavailableDescription }, shape = shapes.medium, colors = - ButtonDefaults.outlinedButtonColors( - containerColor = MaterialTheme.colorScheme.background, - contentColor = MaterialTheme.colorScheme.onBackground, - ), + ButtonDefaults.outlinedButtonColors( + containerColor = MaterialTheme.colorScheme.background, + contentColor = MaterialTheme.colorScheme.onBackground, + ), border = BorderStroke(1.dp, MaterialTheme.colorScheme.outline), ) { Text( @@ -181,12 +181,12 @@ fun AvailableTimeSlotPreview() { Box(modifier = Modifier.fillMaxWidth()) { AvailableTimeSlot( timeSlot = - IntervalScheduleTimeSlot( - start = OffsetDateTime.now(), - end = OffsetDateTime.now(), - state = ScheduleState.AVAILABLE, - duringDayType = DuringDayType.Morning, - ), + IntervalScheduleTimeSlot( + start = OffsetDateTime.now(), + end = OffsetDateTime.now(), + state = ScheduleState.AVAILABLE, + duringDayType = DuringDayType.Morning, + ), onTimeSlotClick = { }, ) } @@ -200,12 +200,12 @@ fun UnavailableTimeSlotPreview() { Box(modifier = Modifier.fillMaxWidth()) { UnavailableTimeSlot( timeSlot = - IntervalScheduleTimeSlot( - start = OffsetDateTime.now(), - end = OffsetDateTime.now(), - state = ScheduleState.BOOKED, - duringDayType = DuringDayType.Morning, - ), + IntervalScheduleTimeSlot( + start = OffsetDateTime.now(), + end = OffsetDateTime.now(), + state = ScheduleState.BOOKED, + duringDayType = DuringDayType.Morning, + ), ) } } diff --git a/feature/teacherschedule/src/main/java/com/wei/teachlink/feature/teacherschedule/scheduledetail/ScheduleDetailScreen.kt b/feature/teacherschedule/src/main/java/com/wei/teachlink/feature/teacherschedule/scheduledetail/ScheduleDetailScreen.kt index 270139b2..a3cadb66 100644 --- a/feature/teacherschedule/src/main/java/com/wei/teachlink/feature/teacherschedule/scheduledetail/ScheduleDetailScreen.kt +++ b/feature/teacherschedule/src/main/java/com/wei/teachlink/feature/teacherschedule/scheduledetail/ScheduleDetailScreen.kt @@ -109,10 +109,10 @@ internal fun ScheduleDetailScreen( text = teacherName, style = MaterialTheme.typography.headlineLarge, modifier = - Modifier - .padding(horizontal = SPACING_LARGE.dp) - .testTag(stringResource(id = R.string.feature_teacherschedule_tag_teacher_name)) - .semantics { contentDescription = teacherNameDescription }, + Modifier + .padding(horizontal = SPACING_LARGE.dp) + .testTag(stringResource(id = R.string.feature_teacherschedule_tag_teacher_name)) + .semantics { contentDescription = teacherNameDescription }, ) val startTimeDescription = @@ -121,11 +121,11 @@ internal fun ScheduleDetailScreen( text = startTimeDescription, style = MaterialTheme.typography.bodyMedium, modifier = - Modifier - .padding(horizontal = SPACING_LARGE.dp) - .padding(top = SPACING_MEDIUM.dp) - .testTag(stringResource(id = R.string.feature_teacherschedule_tag_start_time)) - .semantics { contentDescription = startTimeDescription }, + Modifier + .padding(horizontal = SPACING_LARGE.dp) + .padding(top = SPACING_MEDIUM.dp) + .testTag(stringResource(id = R.string.feature_teacherschedule_tag_start_time)) + .semantics { contentDescription = startTimeDescription }, ) val endTimeDescription = @@ -134,11 +134,11 @@ internal fun ScheduleDetailScreen( text = endTimeDescription, style = MaterialTheme.typography.bodyMedium, modifier = - Modifier - .padding(horizontal = SPACING_LARGE.dp) - .padding(top = SPACING_MEDIUM.dp) - .testTag(stringResource(id = R.string.feature_teacherschedule_tag_end_time)) - .semantics { contentDescription = endTimeDescription }, + Modifier + .padding(horizontal = SPACING_LARGE.dp) + .padding(top = SPACING_MEDIUM.dp) + .testTag(stringResource(id = R.string.feature_teacherschedule_tag_end_time)) + .semantics { contentDescription = endTimeDescription }, ) val state = uiStates.state?.name.toString() @@ -147,11 +147,11 @@ internal fun ScheduleDetailScreen( text = state, style = MaterialTheme.typography.bodyMedium, modifier = - Modifier - .padding(horizontal = SPACING_LARGE.dp) - .padding(top = SPACING_MEDIUM.dp) - .testTag(stringResource(id = R.string.feature_teacherschedule_tag_state)) - .semantics { contentDescription = stateDescription }, + Modifier + .padding(horizontal = SPACING_LARGE.dp) + .padding(top = SPACING_MEDIUM.dp) + .testTag(stringResource(id = R.string.feature_teacherschedule_tag_state)) + .semantics { contentDescription = stateDescription }, ) val duringDayType = uiStates.duringDayType?.name.toString() @@ -161,11 +161,11 @@ internal fun ScheduleDetailScreen( text = duringDayType, style = MaterialTheme.typography.bodyMedium, modifier = - Modifier - .padding(horizontal = SPACING_LARGE.dp) - .padding(top = SPACING_MEDIUM.dp) - .testTag(stringResource(id = R.string.feature_teacherschedule_tag_during_day_type)) - .semantics { contentDescription = duringDayTypeDescription }, + Modifier + .padding(horizontal = SPACING_LARGE.dp) + .padding(top = SPACING_MEDIUM.dp) + .testTag(stringResource(id = R.string.feature_teacherschedule_tag_during_day_type)) + .semantics { contentDescription = duringDayTypeDescription }, ) } } @@ -201,13 +201,13 @@ fun ScheduleDetailScreenPreview() { TlTheme { ScheduleDetailScreen( uiStates = - ScheduleDetailViewState( - teacherName = "Teacher Name", - start = nowTime, - end = nowTime.plusMinutes(30), - state = ScheduleState.BOOKED, - duringDayType = DuringDayType.Morning, - ), + ScheduleDetailViewState( + teacherName = "Teacher Name", + start = nowTime, + end = nowTime.plusMinutes(30), + state = ScheduleState.BOOKED, + duringDayType = DuringDayType.Morning, + ), onBackClick = { }, ) } diff --git a/feature/teacherschedule/src/test/java/com/wei/teachlink/feature/teacherschedule/schedule/ScheduleViewModelTest.kt b/feature/teacherschedule/src/test/java/com/wei/teachlink/feature/teacherschedule/schedule/ScheduleViewModelTest.kt index 17f06347..ac7685b1 100644 --- a/feature/teacherschedule/src/test/java/com/wei/teachlink/feature/teacherschedule/schedule/ScheduleViewModelTest.kt +++ b/feature/teacherschedule/src/test/java/com/wei/teachlink/feature/teacherschedule/schedule/ScheduleViewModelTest.kt @@ -167,10 +167,10 @@ class ScheduleViewModelTest { copy( selectedIndex = 0, _queryDateUtc = - weekDataHelper.getQueryDateUtc( - queryDateLocal = previousWeekMondayLocalDate, - resetToStartOfDay = true, - ), + weekDataHelper.getQueryDateUtc( + queryDateLocal = previousWeekMondayLocalDate, + resetToStartOfDay = true, + ), ) } @@ -193,10 +193,10 @@ class ScheduleViewModelTest { copy( selectedIndex = 0, _queryDateUtc = - weekDataHelper.getQueryDateUtc( - queryDateLocal = OffsetDateTime.now(fixedClock).getLocalOffsetDateTime(), - resetToStartOfDay = false, - ), + weekDataHelper.getQueryDateUtc( + queryDateLocal = OffsetDateTime.now(fixedClock).getLocalOffsetDateTime(), + resetToStartOfDay = false, + ), ) } @@ -218,10 +218,10 @@ class ScheduleViewModelTest { copy( selectedIndex = 0, _queryDateUtc = - weekDataHelper.getQueryDateUtc( - queryDateLocal = viewModel.states.value.weekStart.plusWeeks(1), - resetToStartOfDay = true, - ), + weekDataHelper.getQueryDateUtc( + queryDateLocal = viewModel.states.value.weekStart.plusWeeks(1), + resetToStartOfDay = true, + ), ) } @@ -243,10 +243,10 @@ class ScheduleViewModelTest { copy( selectedIndex = 0, _queryDateUtc = - weekDataHelper.getQueryDateUtc( - queryDateLocal = viewModel.states.value.weekStart.plusWeeks(1), - resetToStartOfDay = true, - ), + weekDataHelper.getQueryDateUtc( + queryDateLocal = viewModel.states.value.weekStart.plusWeeks(1), + resetToStartOfDay = true, + ), ) }