diff --git a/app/src/main/java/org/openedx/app/AnalyticsManager.kt b/app/src/main/java/org/openedx/app/AnalyticsManager.kt index 4f1f089b1..993d30f5e 100644 --- a/app/src/main/java/org/openedx/app/AnalyticsManager.kt +++ b/app/src/main/java/org/openedx/app/AnalyticsManager.kt @@ -171,6 +171,14 @@ class AnalyticsManager( logEvent(Event.COOKIE_POLICY_CLICKED) } + override fun dataSellClickedEvent() { + logEvent(Event.DATE_SELL_CLICKED) + } + + override fun faqClickedEvent() { + logEvent(Event.FAQ_CLICKED) + } + override fun emailSupportClickedEvent() { logEvent(Event.EMAIL_SUPPORT_CLICKED) } @@ -421,6 +429,8 @@ private enum class Event(val eventName: String) { PRIVACY_POLICY_CLICKED("Privacy_Policy_Clicked"), TERMS_OF_USE_CLICKED("Terms_Of_Use_Clicked"), COOKIE_POLICY_CLICKED("Cookie_Policy_Clicked"), + DATE_SELL_CLICKED("Data_Sell_Clicked"), + FAQ_CLICKED("FAQ_Clicked"), EMAIL_SUPPORT_CLICKED("Email_Support_Clicked"), COURSE_ENROLL_CLICKED("Course_Enroll_Clicked"), COURSE_ENROLL_SUCCESS("Course_Enroll_Success"), diff --git a/app/src/main/java/org/openedx/app/di/AppModule.kt b/app/src/main/java/org/openedx/app/di/AppModule.kt index 9311c238f..860e98de0 100644 --- a/app/src/main/java/org/openedx/app/di/AppModule.kt +++ b/app/src/main/java/org/openedx/app/di/AppModule.kt @@ -134,15 +134,7 @@ val appModule = module { DownloadWorkerController(get(), get(), get()) } - single { - val config = get() - AppData( - BuildConfig.VERSION_NAME, - config.getFeedbackEmailAddress(), - config.getAgreementUrlsConfig().tosUrl, - config.getAgreementUrlsConfig().privacyPolicyUrl - ) - } + single { AppData(versionName = BuildConfig.VERSION_NAME) } factory { (activity: AppCompatActivity) -> AppReviewManager(activity, get(), get()) } single { TranscriptManager(get()) } diff --git a/core/src/main/java/org/openedx/core/config/AgreementUrlsConfig.kt b/core/src/main/java/org/openedx/core/config/AgreementUrlsConfig.kt index 2a457df3e..8532ba53a 100644 --- a/core/src/main/java/org/openedx/core/config/AgreementUrlsConfig.kt +++ b/core/src/main/java/org/openedx/core/config/AgreementUrlsConfig.kt @@ -1,14 +1,57 @@ package org.openedx.core.config +import android.net.Uri import com.google.gson.annotations.SerializedName +import org.openedx.core.domain.model.Agreement +import org.openedx.core.domain.model.AgreementUrls -data class AgreementUrlsConfig( +internal data class AgreementUrlsConfig( @SerializedName("PRIVACY_POLICY_URL") - val privacyPolicyUrl: String = "", - + private val privacyPolicyUrl: String = "", + @SerializedName("COOKIE_POLICY_URL") + private val cookiePolicyUrl: String = "", + @SerializedName("DATA_SELL_CONSENT_URL") + private val dataSellConsentUrl: String = "", @SerializedName("TOS_URL") - val tosUrl: String = "", + private val tosUrl: String = "", + @SerializedName("EULA_URL") + private val eulaUrl: String = "", + @SerializedName("SUPPORTED_LANGUAGES") + private val supportedLanguages: List = emptyList(), +) { + fun mapToDomain(): Agreement { + val defaultAgreementUrls = AgreementUrls( + privacyPolicyUrl = privacyPolicyUrl, + cookiePolicyUrl = cookiePolicyUrl, + dataSellConsentUrl = dataSellConsentUrl, + tosUrl = tosUrl, + eulaUrl = eulaUrl, + supportedLanguages = supportedLanguages, + ) + val agreementUrls = if (supportedLanguages.isNotEmpty()) { + supportedLanguages.associateWith { + AgreementUrls( + privacyPolicyUrl = privacyPolicyUrl.appendLocale(it), + cookiePolicyUrl = cookiePolicyUrl.appendLocale(it), + dataSellConsentUrl = dataSellConsentUrl.appendLocale(it), + tosUrl = tosUrl.appendLocale(it), + eulaUrl = eulaUrl.appendLocale(it), + supportedLanguages = supportedLanguages, + ) + } + } else { + mapOf() + } + return Agreement(agreementUrls, defaultAgreementUrls) + } - @SerializedName("CONTACT_US_URL") - val contactUsUrl: String = "", -) + private fun String.appendLocale(locale: String): String { + if (this.isBlank()) return this + val uri = Uri.parse(this) + return Uri.Builder().scheme(uri.scheme) + .authority(uri.authority) + .appendPath(locale + uri.encodedPath) + .build() + .toString() + } +} diff --git a/core/src/main/java/org/openedx/core/config/Config.kt b/core/src/main/java/org/openedx/core/config/Config.kt index f8c489450..b38befbf8 100644 --- a/core/src/main/java/org/openedx/core/config/Config.kt +++ b/core/src/main/java/org/openedx/core/config/Config.kt @@ -5,6 +5,7 @@ import com.google.gson.Gson import com.google.gson.JsonElement import com.google.gson.JsonObject import com.google.gson.JsonParser +import org.openedx.core.domain.model.AgreementUrls import java.io.InputStreamReader class Config(context: Context) { @@ -34,12 +35,18 @@ class Config(context: Context) { return getString(TOKEN_TYPE, "") } + fun getFaqUrl(): String { + return getString(FAQ_URL, "") + } + fun getFeedbackEmailAddress(): String { return getString(FEEDBACK_EMAIL_ADDRESS, "") } - fun getAgreementUrlsConfig(): AgreementUrlsConfig { - return getObjectOrNewInstance(AGREEMENT_URLS, AgreementUrlsConfig::class.java) + fun getAgreement(locale: String): AgreementUrls { + val agreement = + getObjectOrNewInstance(AGREEMENT_URLS, AgreementUrlsConfig::class.java).mapToDomain() + return agreement.getAgreementForLocale(locale) } fun getFirebaseConfig(): FirebaseConfig { @@ -106,6 +113,7 @@ class Config(context: Context) { private const val API_HOST_URL = "API_HOST_URL" private const val OAUTH_CLIENT_ID = "OAUTH_CLIENT_ID" private const val TOKEN_TYPE = "TOKEN_TYPE" + private const val FAQ_URL = "FAQ_URL" private const val FEEDBACK_EMAIL_ADDRESS = "FEEDBACK_EMAIL_ADDRESS" private const val AGREEMENT_URLS = "AGREEMENT_URLS" private const val WHATS_NEW_ENABLED = "WHATS_NEW_ENABLED" diff --git a/core/src/main/java/org/openedx/core/domain/model/AgreementUrls.kt b/core/src/main/java/org/openedx/core/domain/model/AgreementUrls.kt new file mode 100644 index 000000000..a6d3e4e66 --- /dev/null +++ b/core/src/main/java/org/openedx/core/domain/model/AgreementUrls.kt @@ -0,0 +1,25 @@ +package org.openedx.core.domain.model + +/** + * Data class with information about user agreements URLs + * + * @param agreementUrls Map with keys from SUPPORTED_LANGUAGES config + * @param defaultAgreementUrls AgreementUrls for default language ('en') + */ +internal data class Agreement( + private val agreementUrls: Map = mapOf(), + private val defaultAgreementUrls: AgreementUrls +) { + fun getAgreementForLocale(locale: String): AgreementUrls { + return agreementUrls.getOrDefault(locale, defaultAgreementUrls) + } +} + +data class AgreementUrls( + val privacyPolicyUrl: String = "", + val cookiePolicyUrl: String = "", + val dataSellConsentUrl: String = "", + val tosUrl: String = "", + val eulaUrl: String = "", + val supportedLanguages: List = emptyList(), +) diff --git a/core/src/main/java/org/openedx/core/presentation/global/AppData.kt b/core/src/main/java/org/openedx/core/presentation/global/AppData.kt index c006d2e4b..324d3325a 100644 --- a/core/src/main/java/org/openedx/core/presentation/global/AppData.kt +++ b/core/src/main/java/org/openedx/core/presentation/global/AppData.kt @@ -2,7 +2,4 @@ package org.openedx.core.presentation.global data class AppData( val versionName: String, - val feedbackEmailAddress: String, - val tosUrl: String, - val privacyPolicyUrl: String, -) \ No newline at end of file +) diff --git a/core/src/main/java/org/openedx/core/presentation/global/webview/WebContentFragment.kt b/core/src/main/java/org/openedx/core/presentation/global/webview/WebContentFragment.kt index de7346dcb..b1a496743 100644 --- a/core/src/main/java/org/openedx/core/presentation/global/webview/WebContentFragment.kt +++ b/core/src/main/java/org/openedx/core/presentation/global/webview/WebContentFragment.kt @@ -3,15 +3,21 @@ package org.openedx.core.presentation.global.webview import android.os.Bundle import android.view.LayoutInflater import android.view.ViewGroup +import android.webkit.CookieManager import androidx.compose.ui.platform.ComposeView import androidx.compose.ui.platform.ViewCompositionStrategy import androidx.core.os.bundleOf import androidx.fragment.app.Fragment +import org.koin.android.ext.android.inject +import org.openedx.core.config.Config import org.openedx.core.ui.WebContentScreen import org.openedx.core.ui.rememberWindowSize import org.openedx.core.ui.theme.OpenEdXTheme class WebContentFragment : Fragment() { + + private val config: Config by inject() + override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, @@ -22,6 +28,7 @@ class WebContentFragment : Fragment() { OpenEdXTheme { val windowSize = rememberWindowSize() WebContentScreen( + apiHostUrl = config.getApiHostURL(), windowSize = windowSize, title = requireArguments().getString(ARG_TITLE, ""), contentUrl = requireArguments().getString(ARG_URL, ""), @@ -32,6 +39,11 @@ class WebContentFragment : Fragment() { } } + override fun onDestroy() { + super.onDestroy() + CookieManager.getInstance().flush() + } + companion object { private const val ARG_TITLE = "argTitle" private const val ARG_URL = "argUrl" diff --git a/core/src/main/res/values/strings.xml b/core/src/main/res/values/strings.xml index 6ac3080f4..211ee9f09 100644 --- a/core/src/main/res/values/strings.xml +++ b/core/src/main/res/values/strings.xml @@ -8,6 +8,9 @@ Something went wrong Try again Privacy policy + Cookie policy + Do not sell my personal information + View FAQ Terms of use Profile Cancel diff --git a/default_config/dev/config.yaml b/default_config/dev/config.yaml index 054277c9a..0e764a157 100644 --- a/default_config/dev/config.yaml +++ b/default_config/dev/config.yaml @@ -2,12 +2,17 @@ API_HOST_URL: 'http://localhost:8000' APPLICATION_ID: 'org.openedx.app' ENVIRONMENT_DISPLAY_NAME: 'Localhost' FEEDBACK_EMAIL_ADDRESS: 'support@example.com' +FAQ_URL: '' OAUTH_CLIENT_ID: 'OAUTH_CLIENT_ID' +# Keep empty to hide setting AGREEMENT_URLS: - PRIVACY_POLICY_URL: 'https://example.com/privacy' - TOS_URL: 'https://example.com/tos' - CONTACT_US_URL: 'https://example.com/contact' + PRIVACY_POLICY_URL: '' + COOKIE_POLICY_URL: '' + DATA_SELL_CONSENT_URL: '' + TOS_URL: '' + EULA_URL: '' + SUPPORTED_LANGUAGES: [ ] #en is defalut language GOOGLE: ENABLED: false diff --git a/default_config/prod/config.yaml b/default_config/prod/config.yaml index 054277c9a..0e764a157 100644 --- a/default_config/prod/config.yaml +++ b/default_config/prod/config.yaml @@ -2,12 +2,17 @@ API_HOST_URL: 'http://localhost:8000' APPLICATION_ID: 'org.openedx.app' ENVIRONMENT_DISPLAY_NAME: 'Localhost' FEEDBACK_EMAIL_ADDRESS: 'support@example.com' +FAQ_URL: '' OAUTH_CLIENT_ID: 'OAUTH_CLIENT_ID' +# Keep empty to hide setting AGREEMENT_URLS: - PRIVACY_POLICY_URL: 'https://example.com/privacy' - TOS_URL: 'https://example.com/tos' - CONTACT_US_URL: 'https://example.com/contact' + PRIVACY_POLICY_URL: '' + COOKIE_POLICY_URL: '' + DATA_SELL_CONSENT_URL: '' + TOS_URL: '' + EULA_URL: '' + SUPPORTED_LANGUAGES: [ ] #en is defalut language GOOGLE: ENABLED: false diff --git a/default_config/stage/config.yaml b/default_config/stage/config.yaml index 054277c9a..0e764a157 100644 --- a/default_config/stage/config.yaml +++ b/default_config/stage/config.yaml @@ -2,12 +2,17 @@ API_HOST_URL: 'http://localhost:8000' APPLICATION_ID: 'org.openedx.app' ENVIRONMENT_DISPLAY_NAME: 'Localhost' FEEDBACK_EMAIL_ADDRESS: 'support@example.com' +FAQ_URL: '' OAUTH_CLIENT_ID: 'OAUTH_CLIENT_ID' +# Keep empty to hide setting AGREEMENT_URLS: - PRIVACY_POLICY_URL: 'https://example.com/privacy' - TOS_URL: 'https://example.com/tos' - CONTACT_US_URL: 'https://example.com/contact' + PRIVACY_POLICY_URL: '' + COOKIE_POLICY_URL: '' + DATA_SELL_CONSENT_URL: '' + TOS_URL: '' + EULA_URL: '' + SUPPORTED_LANGUAGES: [ ] #en is defalut language GOOGLE: ENABLED: false diff --git a/profile/src/main/java/org/openedx/profile/domain/model/Configuration.kt b/profile/src/main/java/org/openedx/profile/domain/model/Configuration.kt new file mode 100644 index 000000000..f35d18ac4 --- /dev/null +++ b/profile/src/main/java/org/openedx/profile/domain/model/Configuration.kt @@ -0,0 +1,16 @@ +package org.openedx.profile.domain.model + +import org.openedx.core.domain.model.AgreementUrls + +/** + * @param agreementUrls User agreement urls + * @param faqUrl FAQ url + * @param supportEmail Email address of support + * @param versionName Version of the application (1.0.0) + */ +data class Configuration( + val agreementUrls: AgreementUrls, + val faqUrl: String, + val supportEmail: String, + val versionName: String, +) diff --git a/profile/src/main/java/org/openedx/profile/presentation/ProfileAnalytics.kt b/profile/src/main/java/org/openedx/profile/presentation/ProfileAnalytics.kt index 418ce08e0..66d43b607 100644 --- a/profile/src/main/java/org/openedx/profile/presentation/ProfileAnalytics.kt +++ b/profile/src/main/java/org/openedx/profile/presentation/ProfileAnalytics.kt @@ -8,6 +8,8 @@ interface ProfileAnalytics { fun privacyPolicyClickedEvent() fun termsOfUseClickedEvent() fun cookiePolicyClickedEvent() + fun dataSellClickedEvent() + fun faqClickedEvent() fun emailSupportClickedEvent() fun logoutEvent(force: Boolean) -} \ No newline at end of file +} diff --git a/profile/src/main/java/org/openedx/profile/presentation/profile/ProfileFragment.kt b/profile/src/main/java/org/openedx/profile/presentation/profile/ProfileFragment.kt index 6f558bd3a..4c18cd103 100644 --- a/profile/src/main/java/org/openedx/profile/presentation/profile/ProfileFragment.kt +++ b/profile/src/main/java/org/openedx/profile/presentation/profile/ProfileFragment.kt @@ -68,6 +68,20 @@ class ProfileFragment : Fragment() { ) } + ProfileViewAction.CookiePolicyClick -> { + viewModel.cookiePolicyClicked( + requireParentFragment().parentFragmentManager + ) + } + + ProfileViewAction.DataSellClick -> { + viewModel.dataSellClicked( + requireParentFragment().parentFragmentManager + ) + } + + ProfileViewAction.FaqClick -> viewModel.faqClicked() + ProfileViewAction.SupportClick -> { viewModel.emailSupportClicked(requireContext()) } diff --git a/profile/src/main/java/org/openedx/profile/presentation/profile/ProfileUIState.kt b/profile/src/main/java/org/openedx/profile/presentation/profile/ProfileUIState.kt index 86c0f364b..f869b7f0b 100644 --- a/profile/src/main/java/org/openedx/profile/presentation/profile/ProfileUIState.kt +++ b/profile/src/main/java/org/openedx/profile/presentation/profile/ProfileUIState.kt @@ -1,15 +1,16 @@ package org.openedx.profile.presentation.profile import org.openedx.profile.domain.model.Account +import org.openedx.profile.domain.model.Configuration sealed class ProfileUIState { /** * @param account User account data - * @param versionName Version of the application (1.0.0) + * @param configuration Configuration data */ data class Data( val account: Account, - val versionName: String, + val configuration: Configuration, ) : ProfileUIState() object Loading : ProfileUIState() diff --git a/profile/src/main/java/org/openedx/profile/presentation/profile/ProfileViewModel.kt b/profile/src/main/java/org/openedx/profile/presentation/profile/ProfileViewModel.kt index 139a8f81f..0b0eafd8a 100644 --- a/profile/src/main/java/org/openedx/profile/presentation/profile/ProfileViewModel.kt +++ b/profile/src/main/java/org/openedx/profile/presentation/profile/ProfileViewModel.kt @@ -1,6 +1,7 @@ package org.openedx.profile.presentation.profile import android.content.Context +import androidx.compose.ui.text.intl.Locale import androidx.fragment.app.FragmentManager import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LiveData @@ -26,6 +27,7 @@ import org.openedx.core.system.notifier.AppUpgradeEvent import org.openedx.core.system.notifier.AppUpgradeNotifier import org.openedx.core.utils.EmailUtil import org.openedx.profile.domain.interactor.ProfileInteractor +import org.openedx.profile.domain.model.Configuration import org.openedx.profile.presentation.ProfileAnalytics import org.openedx.profile.presentation.ProfileRouter import org.openedx.profile.system.notifier.AccountDeactivated @@ -68,6 +70,14 @@ class ProfileViewModel( val isLogistrationEnabled get() = config.isPreLoginExperienceEnabled() + private val configuration + get() = Configuration( + agreementUrls = config.getAgreement(Locale.current.language), + faqUrl = config.getFaqUrl(), + supportEmail = config.getFeedbackEmailAddress(), + versionName = appData.versionName, + ) + init { getAccount() collectAppUpgradeEvent() @@ -96,13 +106,13 @@ class ProfileViewModel( } else { _uiState.value = ProfileUIState.Data( account = cachedAccount, - versionName = appData.versionName + configuration = configuration, ) } val account = interactor.getAccount() _uiState.value = ProfileUIState.Data( account = account, - versionName = appData.versionName + configuration = configuration, ) } catch (e: Exception) { if (e.isInternetError()) { @@ -173,16 +183,38 @@ class ProfileViewModel( router.navigateToWebContent( fm = fragmentManager, title = resourceManager.getString(R.string.core_privacy_policy), - url = appData.privacyPolicyUrl, + url = configuration.agreementUrls.privacyPolicyUrl, ) analytics.privacyPolicyClickedEvent() } + fun cookiePolicyClicked(fragmentManager: FragmentManager) { + router.navigateToWebContent( + fm = fragmentManager, + title = resourceManager.getString(R.string.core_cookie_policy), + url = configuration.agreementUrls.cookiePolicyUrl, + ) + analytics.cookiePolicyClickedEvent() + } + + fun dataSellClicked(fragmentManager: FragmentManager) { + router.navigateToWebContent( + fm = fragmentManager, + title = resourceManager.getString(R.string.core_data_sell), + url = configuration.agreementUrls.dataSellConsentUrl, + ) + analytics.dataSellClickedEvent() + } + + fun faqClicked() { + analytics.faqClickedEvent() + } + fun termsOfUseClicked(fragmentManager: FragmentManager) { router.navigateToWebContent( fm = fragmentManager, title = resourceManager.getString(R.string.core_terms_of_use), - url = appData.tosUrl, + url = configuration.agreementUrls.tosUrl, ) analytics.termsOfUseClickedEvent() } @@ -190,7 +222,7 @@ class ProfileViewModel( fun emailSupportClicked(context: Context) { EmailUtil.showFeedbackScreen( context = context, - feedbackEmailAddress = appData.feedbackEmailAddress, + feedbackEmailAddress = config.getFeedbackEmailAddress(), appVersion = appData.versionName ) analytics.emailSupportClickedEvent() diff --git a/profile/src/main/java/org/openedx/profile/presentation/profile/compose/ProfileView.kt b/profile/src/main/java/org/openedx/profile/presentation/profile/compose/ProfileView.kt index f03a17b18..cd544a7a7 100644 --- a/profile/src/main/java/org/openedx/profile/presentation/profile/compose/ProfileView.kt +++ b/profile/src/main/java/org/openedx/profile/presentation/profile/compose/ProfileView.kt @@ -34,6 +34,7 @@ import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.ArrowForwardIos import androidx.compose.material.icons.filled.Close import androidx.compose.material.icons.filled.ExitToApp +import androidx.compose.material.icons.filled.OpenInNew import androidx.compose.material.pullrefresh.PullRefreshIndicator import androidx.compose.material.pullrefresh.pullRefresh import androidx.compose.material.pullrefresh.rememberPullRefreshState @@ -48,9 +49,11 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalUriHandler import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Devices import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.Dp @@ -58,6 +61,7 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.window.Dialog import org.openedx.core.R import org.openedx.core.UIMessage +import org.openedx.core.domain.model.AgreementUrls import org.openedx.core.domain.model.ProfileImage import org.openedx.core.presentation.global.AppData import org.openedx.core.system.notifier.AppUpgradeEvent @@ -77,6 +81,7 @@ import org.openedx.profile.domain.model.Account import org.openedx.profile.presentation.profile.ProfileUIState import org.openedx.profile.presentation.ui.ProfileInfoSection import org.openedx.profile.presentation.ui.ProfileTopic +import org.openedx.profile.domain.model.Configuration as AppConfiguration @OptIn(ExperimentalMaterialApi::class) @Composable @@ -215,7 +220,7 @@ internal fun ProfileView( Spacer(modifier = Modifier.height(24.dp)) SupportInfoSection( - versionName = uiState.versionName, + uiState = uiState, onAction = onAction, appUpgradeEvent = appUpgradeEvent, ) @@ -257,12 +262,7 @@ private fun SettingsSection(onVideoSettingsClick: () -> Unit) { elevation = 0.dp, backgroundColor = MaterialTheme.appColors.cardViewBackground ) { - Column( - Modifier - .fillMaxWidth() - .padding(20.dp), - verticalArrangement = Arrangement.spacedBy(24.dp) - ) { + Column(Modifier.fillMaxWidth()) { ProfileInfoItem( text = stringResource(id = org.openedx.profile.R.string.profile_video_settings), onClick = onVideoSettingsClick @@ -274,7 +274,7 @@ private fun SettingsSection(onVideoSettingsClick: () -> Unit) { @Composable private fun SupportInfoSection( - versionName: String, + uiState: ProfileUIState.Data, appUpgradeEvent: AppUpgradeEvent?, onAction: (ProfileViewAction) -> Unit ) { @@ -291,35 +291,68 @@ private fun SupportInfoSection( elevation = 0.dp, backgroundColor = MaterialTheme.appColors.cardViewBackground ) { - Column( - Modifier - .fillMaxWidth() - .padding(20.dp), - verticalArrangement = Arrangement.spacedBy(24.dp) - ) { - ProfileInfoItem( - text = stringResource(id = org.openedx.profile.R.string.profile_contact_support), - onClick = { + Column(Modifier.fillMaxWidth()) { + if (uiState.configuration.supportEmail.isNotBlank()) { + ProfileInfoItem(text = stringResource(id = org.openedx.profile.R.string.profile_contact_support)) { onAction(ProfileViewAction.SupportClick) } - ) - Divider(color = MaterialTheme.appColors.divider) - ProfileInfoItem( - text = stringResource(id = R.string.core_terms_of_use), - onClick = { + Divider( + modifier = Modifier.padding(vertical = 4.dp), + color = MaterialTheme.appColors.divider + ) + } + if (uiState.configuration.agreementUrls.tosUrl.isNotBlank()) { + ProfileInfoItem(text = stringResource(id = R.string.core_terms_of_use)) { onAction(ProfileViewAction.TermsClick) } - ) - Divider(color = MaterialTheme.appColors.divider) - ProfileInfoItem( - text = stringResource(id = R.string.core_privacy_policy), - onClick = { + Divider( + modifier = Modifier.padding(vertical = 4.dp), + color = MaterialTheme.appColors.divider + ) + } + if (uiState.configuration.agreementUrls.privacyPolicyUrl.isNotBlank()) { + ProfileInfoItem(text = stringResource(id = R.string.core_privacy_policy)) { onAction(ProfileViewAction.PrivacyPolicyClick) } - ) - Divider(color = MaterialTheme.appColors.divider) + Divider( + modifier = Modifier.padding(vertical = 4.dp), + color = MaterialTheme.appColors.divider + ) + } + if (uiState.configuration.agreementUrls.cookiePolicyUrl.isNotBlank()) { + ProfileInfoItem(text = stringResource(id = R.string.core_cookie_policy)) { + onAction(ProfileViewAction.CookiePolicyClick) + } + Divider( + modifier = Modifier.padding(vertical = 4.dp), + color = MaterialTheme.appColors.divider + ) + } + if (uiState.configuration.agreementUrls.dataSellConsentUrl.isNotBlank()) { + ProfileInfoItem(text = stringResource(id = R.string.core_data_sell)) { + onAction(ProfileViewAction.DataSellClick) + } + Divider( + modifier = Modifier.padding(vertical = 4.dp), + color = MaterialTheme.appColors.divider + ) + } + if (uiState.configuration.faqUrl.isNotBlank()) { + val uriHandler = LocalUriHandler.current + ProfileInfoItem( + text = stringResource(id = R.string.core_faq), + external = true, + ) { + uriHandler.openUri(uiState.configuration.faqUrl) + onAction(ProfileViewAction.FaqClick) + } + Divider( + modifier = Modifier.padding(vertical = 4.dp), + color = MaterialTheme.appColors.divider + ) + } AppVersionItem( - versionName = versionName, + versionName = uiState.configuration.versionName, appUpgradeEvent = appUpgradeEvent, ) { onAction(ProfileViewAction.AppVersionClick) @@ -446,23 +479,35 @@ private fun LogoutDialog( } @Composable -private fun ProfileInfoItem(text: String, onClick: () -> Unit) { +private fun ProfileInfoItem( + text: String, + external: Boolean = false, + onClick: () -> Unit +) { + val icon = if (external) { + Icons.Filled.OpenInNew + } else { + Icons.Filled.ArrowForwardIos + } Row( Modifier .fillMaxWidth() - .clickable { onClick() }, + .clickable { onClick() } + .padding(20.dp), horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically ) { Text( - modifier = Modifier.fillMaxSize(), + modifier = Modifier.weight(1f), text = text, + maxLines = 1, + overflow = TextOverflow.Ellipsis, style = MaterialTheme.appTypography.titleMedium, color = MaterialTheme.appColors.textPrimary ) Icon( modifier = Modifier.size(16.dp), - imageVector = Icons.Filled.ArrowForwardIos, + imageVector = icon, contentDescription = null ) } @@ -474,26 +519,28 @@ private fun AppVersionItem( appUpgradeEvent: AppUpgradeEvent?, onClick: () -> Unit ) { - when (appUpgradeEvent) { - is AppUpgradeEvent.UpgradeRecommendedEvent -> { - AppVersionItemUpgradeRecommended( - versionName = versionName, - appUpgradeEvent = appUpgradeEvent, - onClick = onClick - ) - } + Box(modifier = Modifier.padding(20.dp)) { + when (appUpgradeEvent) { + is AppUpgradeEvent.UpgradeRecommendedEvent -> { + AppVersionItemUpgradeRecommended( + versionName = versionName, + appUpgradeEvent = appUpgradeEvent, + onClick = onClick + ) + } - is AppUpgradeEvent.UpgradeRequiredEvent -> { - AppVersionItemUpgradeRequired( - versionName = versionName, - onClick = onClick - ) - } + is AppUpgradeEvent.UpgradeRequiredEvent -> { + AppVersionItemUpgradeRequired( + versionName = versionName, + onClick = onClick + ) + } - else -> { - AppVersionItemAppToDate( - versionName = versionName - ) + else -> { + AppVersionItemAppToDate( + versionName = versionName + ) + } } } } @@ -670,10 +717,7 @@ private fun ProfileScreenPreview() { OpenEdXTheme { ProfileView( windowSize = WindowSize(WindowType.Compact, WindowType.Compact), - uiState = ProfileUIState.Data( - account = mockAccount, - versionName = mockAppData.versionName, - ), + uiState = mockUiState, uiMessage = null, refreshing = false, onAction = {}, @@ -690,10 +734,7 @@ private fun ProfileScreenTabletPreview() { OpenEdXTheme { ProfileView( windowSize = WindowSize(WindowType.Medium, WindowType.Medium), - uiState = ProfileUIState.Data( - account = mockAccount, - versionName = mockAppData.versionName, - ), + uiState = mockUiState, uiMessage = null, refreshing = false, onAction = {}, @@ -704,9 +745,6 @@ private fun ProfileScreenTabletPreview() { private val mockAppData = AppData( versionName = "1.0.0", - feedbackEmailAddress = "support@example.com", - tosUrl = "https://example.com/tos", - privacyPolicyUrl = "https://example.com/privacy", ) private val mockAccount = Account( @@ -728,12 +766,26 @@ private val mockAccount = Account( accountPrivacy = Account.Privacy.ALL_USERS ) +private val mockConfiguration = AppConfiguration( + agreementUrls = AgreementUrls(), + faqUrl = "https://example.com/faq", + supportEmail = "test@example.com", + versionName = mockAppData.versionName, +) + +private val mockUiState = ProfileUIState.Data( + account = mockAccount, + configuration = mockConfiguration, +) internal interface ProfileViewAction { object AppVersionClick : ProfileViewAction object EditAccountClick : ProfileViewAction object LogoutClick : ProfileViewAction object PrivacyPolicyClick : ProfileViewAction + object CookiePolicyClick : ProfileViewAction + object DataSellClick : ProfileViewAction + object FaqClick : ProfileViewAction object TermsClick : ProfileViewAction object SupportClick : ProfileViewAction object VideoSettingsClick : ProfileViewAction diff --git a/profile/src/test/java/org/openedx/profile/presentation/profile/ProfileViewModelTest.kt b/profile/src/test/java/org/openedx/profile/presentation/profile/ProfileViewModelTest.kt index e997dc037..45d346671 100644 --- a/profile/src/test/java/org/openedx/profile/presentation/profile/ProfileViewModelTest.kt +++ b/profile/src/test/java/org/openedx/profile/presentation/profile/ProfileViewModelTest.kt @@ -1,6 +1,7 @@ package org.openedx.profile.presentation.profile import androidx.arch.core.executor.testing.InstantTaskExecutorRule +import androidx.compose.ui.text.intl.Locale import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LifecycleRegistry @@ -23,6 +24,7 @@ import org.junit.rules.TestRule import org.openedx.core.R import org.openedx.core.UIMessage import org.openedx.core.config.Config +import org.openedx.core.domain.model.AgreementUrls import org.openedx.core.domain.model.ProfileImage import org.openedx.core.module.DownloadWorkerController import org.openedx.core.presentation.global.AppData @@ -56,9 +58,6 @@ class ProfileViewModelTest { private val appData = AppData( versionName = "1.0.0", - feedbackEmailAddress = "support@example.com", - tosUrl = "https://example.com/tos", - privacyPolicyUrl = "https://example.com/privacy", ) private val account = org.openedx.profile.domain.model.Account( @@ -90,6 +89,9 @@ class ProfileViewModelTest { every { resourceManager.getString(R.string.core_error_unknown_error) } returns somethingWrong every { appUpgradeNotifier.notifier } returns emptyFlow() every { config.isPreLoginExperienceEnabled() } returns false + every { config.getFeedbackEmailAddress() } returns "" + every { config.getAgreement(Locale.current.language) } returns AgreementUrls() + every { config.getFaqUrl() } returns "" } @After