From 1e73dfbaf8285c303c0fb627cd26fdf660a96bcf Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 16 Jun 2023 09:28:21 +0200 Subject: [PATCH 1/7] Iterate on analytics OptIn screen --- .../analytics/impl/AnalyticsOptInView.kt | 227 ++++++++++-------- .../main/res/drawable/element_logo_stars.xml | 57 ----- .../impl/src/main/res/values/localazy.xml | 8 +- 3 files changed, 125 insertions(+), 167 deletions(-) delete mode 100644 features/analytics/impl/src/main/res/drawable/element_logo_stars.xml diff --git a/features/analytics/impl/src/main/kotlin/io/element/android/features/analytics/impl/AnalyticsOptInView.kt b/features/analytics/impl/src/main/kotlin/io/element/android/features/analytics/impl/AnalyticsOptInView.kt index 899c217e00..1856cd4d3c 100644 --- a/features/analytics/impl/src/main/kotlin/io/element/android/features/analytics/impl/AnalyticsOptInView.kt +++ b/features/analytics/impl/src/main/kotlin/io/element/android/features/analytics/impl/AnalyticsOptInView.kt @@ -22,25 +22,27 @@ import android.text.style.ForegroundColorSpan import android.text.style.StyleSpan import android.text.style.UnderlineSpan import androidx.annotation.StringRes -import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.imePadding import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.systemBarsPadding +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.outlined.CheckCircle +import androidx.compose.material.icons.filled.Poll +import androidx.compose.material.icons.rounded.Check import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment +import androidx.compose.ui.BiasAlignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color -import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.text.SpanStyle @@ -54,9 +56,14 @@ import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import io.element.android.features.analytics.api.AnalyticsOptInEvents +import io.element.android.libraries.designsystem.ElementTextStyles import io.element.android.libraries.designsystem.LinkColor +import io.element.android.libraries.designsystem.atomic.molecules.ButtonColumnMolecule +import io.element.android.libraries.designsystem.atomic.molecules.IconTitleSubtitleMolecule +import io.element.android.libraries.designsystem.atomic.pages.HeaderFooterPage import io.element.android.libraries.designsystem.preview.ElementPreviewDark import io.element.android.libraries.designsystem.preview.ElementPreviewLight +import io.element.android.libraries.designsystem.theme.LocalColors import io.element.android.libraries.designsystem.theme.components.Button import io.element.android.libraries.designsystem.theme.components.Icon import io.element.android.libraries.designsystem.theme.components.Text @@ -71,114 +78,122 @@ fun AnalyticsOptInView( ) { LogCompositions(tag = "Analytics", msg = "Root") val eventSink = state.eventSink - Box( + HeaderFooterPage( modifier = modifier .fillMaxSize() .systemBarsPadding() - .imePadding() + .imePadding(), + header = { AnalyticsOptInHeader(state) }, + content = { AnalyticsOptInContent() }, + footer = { AnalyticsOptInFooter(eventSink) }) +} + +@Composable +fun AnalyticsOptInHeader(state: AnalyticsOptInState) { + Column { + IconTitleSubtitleMolecule( + modifier = Modifier.padding(top = 60.dp), + title = stringResource(id = R.string.screen_analytics_prompt_title, state.applicationName), + subTitle = stringResource(id = R.string.screen_analytics_prompt_help_us_improve), + iconImageVector = Icons.Filled.Poll + ) + Text( + text = buildAnnotatedStringWithColoredPart( + R.string.screen_analytics_prompt_read_terms, + R.string.screen_analytics_prompt_read_terms_content_link + ), + modifier = Modifier + .fillMaxWidth() + .padding(top = 20.dp), + style = ElementTextStyles.Regular.subheadline, + textAlign = TextAlign.Center, + color = MaterialTheme.colorScheme.secondary, + ) + } +} + +@Composable +fun AnalyticsOptInContent() { + Box( + modifier = Modifier.fillMaxSize(), + contentAlignment = BiasAlignment( + horizontalBias = 0f, + verticalBias = -0.4f + ) ) { Column( - modifier = Modifier.padding(horizontal = 16.dp), + verticalArrangement = Arrangement.spacedBy(4.dp) ) { - Column(modifier = Modifier.weight(1f)) { - Image( - painterResource(id = R.drawable.element_logo_stars), - contentDescription = null, - modifier = Modifier - .align(Alignment.CenterHorizontally) - .padding(16.dp) - ) - Text( - text = stringResource(id = R.string.screen_analytics_prompt_title, state.applicationName), - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 16.dp, vertical = 16.dp), - textAlign = TextAlign.Center, - fontWeight = FontWeight.Bold, - fontSize = 24.sp, - color = MaterialTheme.colorScheme.primary, - ) - Text( - text = stringResource(id = R.string.screen_analytics_prompt_help_us_improve, state.applicationName), - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 16.dp, vertical = 16.dp), - textAlign = TextAlign.Center, - fontSize = 16.sp, - color = MaterialTheme.colorScheme.secondary, - ) + AnalyticsOptInContentRow( + text = stringResource(id = R.string.screen_analytics_prompt_data_usage), + idx = 0 + ) + AnalyticsOptInContentRow( + text = stringResource(id = R.string.screen_analytics_prompt_third_party_sharing), + idx = 1 + ) + AnalyticsOptInContentRow( + text = stringResource(id = R.string.screen_analytics_prompt_settings), + idx = 2 + ) + } + } +} - Text( - text = buildAnnotatedStringWithColoredPart( - R.string.screen_analytics_prompt_read_terms, - R.string.screen_analytics_prompt_read_terms_content_link - ), - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 16.dp, vertical = 16.dp), - textAlign = TextAlign.Center, - fontSize = 16.sp, - color = MaterialTheme.colorScheme.secondary, - ) +@Composable +fun AnalyticsOptInContentRow( + text: String, + idx: Int, +) { + val radius = 14.dp + val bgShape = when (idx) { + 0 -> RoundedCornerShape(topStart = radius, topEnd = radius) + 2 -> RoundedCornerShape(bottomStart = radius, bottomEnd = radius) + else -> RoundedCornerShape(0.dp) + } + Row( + modifier = Modifier + .fillMaxWidth() + .background( + color = LocalColors.current.quinary, + shape = bgShape, + ) + .padding(vertical = 12.dp, horizontal = 20.dp), + ) { + Icon( + modifier = Modifier + .size(20.dp) + .background(color = MaterialTheme.colorScheme.background, shape = CircleShape) + .padding(2.dp), + imageVector = Icons.Rounded.Check, + contentDescription = null, + // TODO Compound, this color is not yet in the theme + tint = Color(0xFF007A61) + ) + Text( + modifier = Modifier.padding(start = 16.dp), + text = text, + fontSize = 14.sp, + fontWeight = FontWeight.SemiBold, + color = MaterialTheme.colorScheme.primary, + ) + } +} - Row( - modifier = Modifier - .fillMaxWidth() - .padding(vertical = 16.dp), - verticalAlignment = Alignment.CenterVertically - ) { - Icon(imageVector = Icons.Outlined.CheckCircle, - contentDescription = null, - tint = MaterialTheme.colorScheme.secondary) - Text( - text = stringResource(id = R.string.screen_analytics_prompt_data_usage).toAnnotatedString(), - color = MaterialTheme.colorScheme.secondary, - ) - } - Row( - modifier = Modifier - .fillMaxWidth() - .padding(vertical = 16.dp), - verticalAlignment = Alignment.CenterVertically - ) { - Icon(imageVector = Icons.Outlined.CheckCircle, - contentDescription = null, - tint = MaterialTheme.colorScheme.secondary) - Text( - text = stringResource(id = R.string.screen_analytics_prompt_third_party_sharing).toAnnotatedString(), - color = MaterialTheme.colorScheme.secondary, - ) - } - Row( - modifier = Modifier - .fillMaxWidth() - .padding(vertical = 16.dp), - verticalAlignment = Alignment.CenterVertically - ) { - Icon(imageVector = Icons.Outlined.CheckCircle, - contentDescription = null, - tint = MaterialTheme.colorScheme.secondary) - Text( - text = stringResource(id = R.string.screen_analytics_prompt_settings), - color = MaterialTheme.colorScheme.secondary, - ) - } - } - - Button( - onClick = { eventSink(AnalyticsOptInEvents.EnableAnalytics(true)) }, - modifier = Modifier.fillMaxWidth(), - ) { - Text(text = stringResource(id = StringR.string.action_enable)) - } - Spacer(modifier = Modifier.height(16.dp)) - TextButton( - onClick = { eventSink(AnalyticsOptInEvents.EnableAnalytics(false)) }, - modifier = Modifier.fillMaxWidth(), - ) { - Text(text = stringResource(id = StringR.string.action_not_now)) - } - Spacer(Modifier.height(40.dp)) +@Composable +fun AnalyticsOptInFooter(eventSink: (AnalyticsOptInEvents) -> Unit) { + ButtonColumnMolecule { + Button( + onClick = { eventSink(AnalyticsOptInEvents.EnableAnalytics(true)) }, + modifier = Modifier.fillMaxWidth(), + ) { + Text(text = stringResource(id = StringR.string.action_ok)) + } + TextButton( + onClick = { eventSink(AnalyticsOptInEvents.EnableAnalytics(false)) }, + modifier = Modifier.fillMaxWidth(), + ) { + Text(text = stringResource(id = StringR.string.action_not_now)) } } } diff --git a/features/analytics/impl/src/main/res/drawable/element_logo_stars.xml b/features/analytics/impl/src/main/res/drawable/element_logo_stars.xml deleted file mode 100644 index d982fbedc4..0000000000 --- a/features/analytics/impl/src/main/res/drawable/element_logo_stars.xml +++ /dev/null @@ -1,57 +0,0 @@ - - - - - - - - - - - - - - - diff --git a/features/analytics/impl/src/main/res/values/localazy.xml b/features/analytics/impl/src/main/res/values/localazy.xml index e6b1c6419d..d6083c860d 100644 --- a/features/analytics/impl/src/main/res/values/localazy.xml +++ b/features/analytics/impl/src/main/res/values/localazy.xml @@ -1,10 +1,10 @@ - "We ""don\'t"" record or profile any account data" - "Help us identify issues and improve %1$s by sharing anonymous usage data." + "We won\'t record or profile any personal data" + "Share anonymous usage data to help us identify issues." "You can read all our terms %1$s." "here" - "You can turn this off anytime in settings" - "We ""don\'t"" share information with third parties" + "You can turn this off anytime" + "We won\'t share your data with third parties" "Help improve %1$s" \ No newline at end of file From 9d6946aa206d1e2cf876db60ae905d64d65fb241 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 16 Jun 2023 09:40:00 +0200 Subject: [PATCH 2/7] Extract method about text to the design system module. --- .../analytics/impl/AnalyticsOptInView.kt | 57 ++----------- .../designsystem/text/AnnotatedStrings.kt | 84 +++++++++++++++++++ 2 files changed, 90 insertions(+), 51 deletions(-) create mode 100644 libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/text/AnnotatedStrings.kt diff --git a/features/analytics/impl/src/main/kotlin/io/element/android/features/analytics/impl/AnalyticsOptInView.kt b/features/analytics/impl/src/main/kotlin/io/element/android/features/analytics/impl/AnalyticsOptInView.kt index 1856cd4d3c..aac025ed68 100644 --- a/features/analytics/impl/src/main/kotlin/io/element/android/features/analytics/impl/AnalyticsOptInView.kt +++ b/features/analytics/impl/src/main/kotlin/io/element/android/features/analytics/impl/AnalyticsOptInView.kt @@ -16,12 +16,6 @@ package io.element.android.features.analytics.impl -import android.graphics.Typeface -import android.text.SpannableString -import android.text.style.ForegroundColorSpan -import android.text.style.StyleSpan -import android.text.style.UnderlineSpan -import androidx.annotation.StringRes import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box @@ -44,25 +38,20 @@ import androidx.compose.ui.BiasAlignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.AnnotatedString -import androidx.compose.ui.text.SpanStyle -import androidx.compose.ui.text.buildAnnotatedString -import androidx.compose.ui.text.font.FontStyle import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.text.style.TextDecoration import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import io.element.android.features.analytics.api.AnalyticsOptInEvents import io.element.android.libraries.designsystem.ElementTextStyles -import io.element.android.libraries.designsystem.LinkColor import io.element.android.libraries.designsystem.atomic.molecules.ButtonColumnMolecule import io.element.android.libraries.designsystem.atomic.molecules.IconTitleSubtitleMolecule import io.element.android.libraries.designsystem.atomic.pages.HeaderFooterPage import io.element.android.libraries.designsystem.preview.ElementPreviewDark import io.element.android.libraries.designsystem.preview.ElementPreviewLight +import io.element.android.libraries.designsystem.text.buildAnnotatedStringWithStyledPart import io.element.android.libraries.designsystem.theme.LocalColors import io.element.android.libraries.designsystem.theme.components.Button import io.element.android.libraries.designsystem.theme.components.Icon @@ -98,9 +87,12 @@ fun AnalyticsOptInHeader(state: AnalyticsOptInState) { iconImageVector = Icons.Filled.Poll ) Text( - text = buildAnnotatedStringWithColoredPart( + text = buildAnnotatedStringWithStyledPart( R.string.screen_analytics_prompt_read_terms, - R.string.screen_analytics_prompt_read_terms_content_link + R.string.screen_analytics_prompt_read_terms_content_link, + color = Color.Unspecified, + underline = false, + bold = true, ), modifier = Modifier .fillMaxWidth() @@ -198,43 +190,6 @@ fun AnalyticsOptInFooter(eventSink: (AnalyticsOptInEvents) -> Unit) { } } -fun String.toAnnotatedString(): AnnotatedString = buildAnnotatedString { - append(this@toAnnotatedString) - val spannable = SpannableString(this@toAnnotatedString) - spannable.getSpans(0, spannable.length, Any::class.java).forEach { span -> - val start = spannable.getSpanStart(span) - val end = spannable.getSpanEnd(span) - when (span) { - is StyleSpan -> when (span.style) { - Typeface.BOLD -> addStyle(SpanStyle(fontWeight = FontWeight.Bold), start, end) - Typeface.ITALIC -> addStyle(SpanStyle(fontStyle = FontStyle.Italic), start, end) - Typeface.BOLD_ITALIC -> addStyle(SpanStyle(fontWeight = FontWeight.Bold, fontStyle = FontStyle.Italic), start, end) - } - is UnderlineSpan -> addStyle(SpanStyle(textDecoration = TextDecoration.Underline), start, end) - is ForegroundColorSpan -> addStyle(SpanStyle(color = Color(span.foregroundColor)), start, end) - } - } -} - -@Composable -fun buildAnnotatedStringWithColoredPart( - @StringRes fullTextRes: Int, - @StringRes coloredTextRes: Int, - color: Color = LinkColor, - underline: Boolean = true, -) = buildAnnotatedString { - val coloredPart = stringResource(coloredTextRes) - val fullText = stringResource(fullTextRes, coloredPart) - val startIndex = fullText.indexOf(coloredPart) - append(fullText) - addStyle( - style = SpanStyle( - color = color, - textDecoration = if (underline) TextDecoration.Underline else null - ), start = startIndex, end = startIndex + coloredPart.length - ) -} - @Preview @Composable fun AnalyticsOptInViewLightPreview(@PreviewParameter(AnalyticsOptInStateProvider::class) state: AnalyticsOptInState) = ElementPreviewLight { diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/text/AnnotatedStrings.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/text/AnnotatedStrings.kt new file mode 100644 index 0000000000..7d4de84301 --- /dev/null +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/text/AnnotatedStrings.kt @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2023 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.libraries.designsystem.text + +import android.graphics.Typeface +import android.text.SpannableString +import android.text.style.ForegroundColorSpan +import android.text.style.StyleSpan +import android.text.style.UnderlineSpan +import androidx.annotation.StringRes +import androidx.compose.runtime.Composable +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.AnnotatedString +import androidx.compose.ui.text.SpanStyle +import androidx.compose.ui.text.buildAnnotatedString +import androidx.compose.ui.text.font.FontStyle +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextDecoration +import io.element.android.libraries.designsystem.LinkColor + +fun String.toAnnotatedString(): AnnotatedString = buildAnnotatedString { + append(this@toAnnotatedString) + val spannable = SpannableString(this@toAnnotatedString) + spannable.getSpans(0, spannable.length, Any::class.java).forEach { span -> + val start = spannable.getSpanStart(span) + val end = spannable.getSpanEnd(span) + when (span) { + is StyleSpan -> when (span.style) { + Typeface.BOLD -> addStyle(SpanStyle(fontWeight = FontWeight.Bold), start, end) + Typeface.ITALIC -> addStyle(SpanStyle(fontStyle = FontStyle.Italic), start, end) + Typeface.BOLD_ITALIC -> addStyle(SpanStyle(fontWeight = FontWeight.Bold, fontStyle = FontStyle.Italic), start, end) + } + is UnderlineSpan -> addStyle(SpanStyle(textDecoration = TextDecoration.Underline), start, end) + is ForegroundColorSpan -> addStyle(SpanStyle(color = Color(span.foregroundColor)), start, end) + } + } +} + +/** + * Convert a string to an [AnnotatedString] with styles applied. + * + * @param fullTextRes the string resource to use as the full text. Must contain a single %s + * @param coloredTextRes the string resource to use as the colored part of the string + * @param color the color to apply to the string + * @param underline whether to underline the string + * @param bold whether to bold the string + */ +@Composable +fun buildAnnotatedStringWithStyledPart( + @StringRes fullTextRes: Int, + @StringRes coloredTextRes: Int, + color: Color = LinkColor, + underline: Boolean = true, + bold: Boolean = false, +) = buildAnnotatedString { + val coloredPart = stringResource(coloredTextRes) + val fullText = stringResource(fullTextRes, coloredPart) + val startIndex = fullText.indexOf(coloredPart) + append(fullText) + addStyle( + style = SpanStyle( + color = color, + textDecoration = if (underline) TextDecoration.Underline else null, + fontWeight = if (bold) FontWeight.Bold else null, + ), + start = startIndex, + end = startIndex + coloredPart.length, + ) +} From f70d99938263759d085f789b46945b2adfce0357 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 16 Jun 2023 10:13:26 +0200 Subject: [PATCH 3/7] Move extension `openUrlInChromeCustomTab` to :androidutils module. --- features/login/impl/build.gradle.kts | 1 + .../features/login/impl/oidc/customtab/CustomTabHandler.kt | 1 + libraries/androidutils/build.gradle.kts | 1 + .../android/libraries/androidutils/browser/ChromeCustomTab.kt | 2 +- 4 files changed, 4 insertions(+), 1 deletion(-) rename features/login/impl/src/main/kotlin/io/element/android/features/login/impl/oidc/customtab/Extensions.kt => libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/browser/ChromeCustomTab.kt (97%) diff --git a/features/login/impl/build.gradle.kts b/features/login/impl/build.gradle.kts index aacf4c0aeb..2236438e2a 100644 --- a/features/login/impl/build.gradle.kts +++ b/features/login/impl/build.gradle.kts @@ -40,6 +40,7 @@ dependencies { implementation(projects.anvilannotations) anvil(projects.anvilcodegen) implementation(projects.libraries.core) + implementation(projects.libraries.androidutils) implementation(projects.libraries.architecture) implementation(projects.libraries.matrix.api) implementation(projects.libraries.matrix.api) diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/oidc/customtab/CustomTabHandler.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/oidc/customtab/CustomTabHandler.kt index 407459c5bf..48c674e0a0 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/oidc/customtab/CustomTabHandler.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/oidc/customtab/CustomTabHandler.kt @@ -23,6 +23,7 @@ import android.net.Uri import androidx.browser.customtabs.CustomTabsClient import androidx.browser.customtabs.CustomTabsServiceConnection import androidx.browser.customtabs.CustomTabsSession +import io.element.android.libraries.androidutils.browser.openUrlInChromeCustomTab import io.element.android.libraries.di.ApplicationContext import javax.inject.Inject diff --git a/libraries/androidutils/build.gradle.kts b/libraries/androidutils/build.gradle.kts index e9a8feaa05..f98914e08a 100644 --- a/libraries/androidutils/build.gradle.kts +++ b/libraries/androidutils/build.gradle.kts @@ -28,5 +28,6 @@ dependencies { implementation(libs.androidx.activity.activity) implementation(libs.androidx.exifinterface) implementation(libs.androidx.security.crypto) + implementation(libs.androidx.browser) implementation(projects.libraries.core) } diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/oidc/customtab/Extensions.kt b/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/browser/ChromeCustomTab.kt similarity index 97% rename from features/login/impl/src/main/kotlin/io/element/android/features/login/impl/oidc/customtab/Extensions.kt rename to libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/browser/ChromeCustomTab.kt index be98566e7c..ec0d9662c7 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/oidc/customtab/Extensions.kt +++ b/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/browser/ChromeCustomTab.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.element.android.features.login.impl.oidc.customtab +package io.element.android.libraries.androidutils.browser import android.app.Activity import android.content.ActivityNotFoundException From 11926e80ef3c46ac1e5e6ff1e31ca3921e092fc9 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 16 Jun 2023 10:22:01 +0200 Subject: [PATCH 4/7] Make the link to the policy active. --- .../android/features/analytics/api/Config.kt | 22 ++++++++++++++++ features/analytics/impl/build.gradle.kts | 1 + .../analytics/impl/AnalyticsOptInNode.kt | 12 +++++++++ .../analytics/impl/AnalyticsOptInView.kt | 25 +++++++++++++------ 4 files changed, 53 insertions(+), 7 deletions(-) create mode 100644 features/analytics/api/src/main/kotlin/io/element/android/features/analytics/api/Config.kt diff --git a/features/analytics/api/src/main/kotlin/io/element/android/features/analytics/api/Config.kt b/features/analytics/api/src/main/kotlin/io/element/android/features/analytics/api/Config.kt new file mode 100644 index 0000000000..883e0d1dc3 --- /dev/null +++ b/features/analytics/api/src/main/kotlin/io/element/android/features/analytics/api/Config.kt @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2023 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.analytics.api + +object Config { + const val POLICY_LINK = "https://element.io/cookie-policy" +} + diff --git a/features/analytics/impl/build.gradle.kts b/features/analytics/impl/build.gradle.kts index b72d8dbbec..60b4887a88 100644 --- a/features/analytics/impl/build.gradle.kts +++ b/features/analytics/impl/build.gradle.kts @@ -41,6 +41,7 @@ dependencies { api(projects.features.analytics.api) api(projects.services.analytics.api) implementation(libs.androidx.datastore.preferences) + implementation(libs.androidx.browser) ksp(libs.showkase.processor) testImplementation(libs.test.junit) diff --git a/features/analytics/impl/src/main/kotlin/io/element/android/features/analytics/impl/AnalyticsOptInNode.kt b/features/analytics/impl/src/main/kotlin/io/element/android/features/analytics/impl/AnalyticsOptInNode.kt index aafb3d4490..ab060a51cf 100644 --- a/features/analytics/impl/src/main/kotlin/io/element/android/features/analytics/impl/AnalyticsOptInNode.kt +++ b/features/analytics/impl/src/main/kotlin/io/element/android/features/analytics/impl/AnalyticsOptInNode.kt @@ -16,14 +16,19 @@ package io.element.android.features.analytics.impl +import android.app.Activity +import androidx.compose.material.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext import com.bumble.appyx.core.modality.BuildContext import com.bumble.appyx.core.node.Node import com.bumble.appyx.core.plugin.Plugin import dagger.assisted.Assisted import dagger.assisted.AssistedInject import io.element.android.anvilannotations.ContributesNode +import io.element.android.features.analytics.api.Config +import io.element.android.libraries.androidutils.browser.openUrlInChromeCustomTab import io.element.android.libraries.di.AppScope @ContributesNode(AppScope::class) @@ -33,12 +38,19 @@ class AnalyticsOptInNode @AssistedInject constructor( private val presenter: AnalyticsOptInPresenter, ) : Node(buildContext, plugins = plugins) { + private fun onClickTerms(activity: Activity, darkTheme: Boolean) { + activity.openUrlInChromeCustomTab(null, darkTheme, Config.POLICY_LINK) + } + @Composable override fun View(modifier: Modifier) { + val activity = LocalContext.current as Activity + val isDark = MaterialTheme.colors.isLight.not() val state = presenter.present() AnalyticsOptInView( state = state, modifier = modifier, + onClickTerms = { onClickTerms(activity, isDark) }, ) } } diff --git a/features/analytics/impl/src/main/kotlin/io/element/android/features/analytics/impl/AnalyticsOptInView.kt b/features/analytics/impl/src/main/kotlin/io/element/android/features/analytics/impl/AnalyticsOptInView.kt index aac025ed68..908eb4a329 100644 --- a/features/analytics/impl/src/main/kotlin/io/element/android/features/analytics/impl/AnalyticsOptInView.kt +++ b/features/analytics/impl/src/main/kotlin/io/element/android/features/analytics/impl/AnalyticsOptInView.kt @@ -17,6 +17,7 @@ package io.element.android.features.analytics.impl import androidx.compose.foundation.background +import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column @@ -34,8 +35,10 @@ import androidx.compose.material.icons.filled.Poll import androidx.compose.material.icons.rounded.Check import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment import androidx.compose.ui.BiasAlignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight @@ -63,6 +66,7 @@ import io.element.android.libraries.ui.strings.R as StringR @Composable fun AnalyticsOptInView( state: AnalyticsOptInState, + onClickTerms: () -> Unit, modifier: Modifier = Modifier, ) { LogCompositions(tag = "Analytics", msg = "Root") @@ -72,16 +76,19 @@ fun AnalyticsOptInView( .fillMaxSize() .systemBarsPadding() .imePadding(), - header = { AnalyticsOptInHeader(state) }, + header = { AnalyticsOptInHeader(state, onClickTerms) }, content = { AnalyticsOptInContent() }, footer = { AnalyticsOptInFooter(eventSink) }) } @Composable -fun AnalyticsOptInHeader(state: AnalyticsOptInState) { - Column { +fun AnalyticsOptInHeader( + state: AnalyticsOptInState, + onClickTerms: () -> Unit, +) { + Column(horizontalAlignment = Alignment.CenterHorizontally) { IconTitleSubtitleMolecule( - modifier = Modifier.padding(top = 60.dp), + modifier = Modifier.padding(top = 60.dp, bottom = 12.dp), title = stringResource(id = R.string.screen_analytics_prompt_title, state.applicationName), subTitle = stringResource(id = R.string.screen_analytics_prompt_help_us_improve), iconImageVector = Icons.Filled.Poll @@ -95,8 +102,9 @@ fun AnalyticsOptInHeader(state: AnalyticsOptInState) { bold = true, ), modifier = Modifier - .fillMaxWidth() - .padding(top = 20.dp), + .clip(shape = RoundedCornerShape(8.dp)) + .clickable { onClickTerms() } + .padding(8.dp), style = ElementTextStyles.Regular.subheadline, textAlign = TextAlign.Center, color = MaterialTheme.colorScheme.secondary, @@ -204,5 +212,8 @@ fun AnalyticsOptInViewDarkPreview(@PreviewParameter(AnalyticsOptInStateProvider: @Composable private fun ContentToPreview(state: AnalyticsOptInState) { - AnalyticsOptInView(state = state) + AnalyticsOptInView( + state = state, + onClickTerms = {}, + ) } From cc426d5109bdf315b8c5c2c616052e6d269f20a5 Mon Sep 17 00:00:00 2001 From: ElementBot Date: Fri, 16 Jun 2023 08:56:52 +0000 Subject: [PATCH 5/7] Update screenshots --- ...AnalyticsOptInViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png | 4 ++-- ...nalyticsOptInViewLightPreview_0_null_0,NEXUS_5,1.0,en].png | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.analytics.impl_null_DefaultGroup_AnalyticsOptInViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.analytics.impl_null_DefaultGroup_AnalyticsOptInViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png index eaaa4b3a91..23bd49cb88 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.analytics.impl_null_DefaultGroup_AnalyticsOptInViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.analytics.impl_null_DefaultGroup_AnalyticsOptInViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:88d5d2054bae36664b69a20853e11e4567bd6cb1e6a8f7ea0a6747ec534807f6 -size 47160 +oid sha256:bde1e07ccd740f6f19bc39be2dc0feaff956a6fbf079639b3f0949b7f2d2dc96 +size 51661 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.analytics.impl_null_DefaultGroup_AnalyticsOptInViewLightPreview_0_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.analytics.impl_null_DefaultGroup_AnalyticsOptInViewLightPreview_0_null_0,NEXUS_5,1.0,en].png index cf5be1e386..d44e6c6dce 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.analytics.impl_null_DefaultGroup_AnalyticsOptInViewLightPreview_0_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.analytics.impl_null_DefaultGroup_AnalyticsOptInViewLightPreview_0_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d9774baeb4de35b8fc4f47d4e41eb38f856a098465ffce0cdea66c32e4cebf57 -size 46475 +oid sha256:b5c99a23745883c9b6f9c5112974e9e80f202d7a5255776f66c638af4ed28abc +size 51339 From b61ad40c65a8aa6ad8ae97b99ade41778ecb7cad Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 16 Jun 2023 12:00:05 +0200 Subject: [PATCH 6/7] Delete obsolete translation (also deleted on Localazy) --- features/analytics/impl/src/main/res/values-de/translations.xml | 1 - features/analytics/impl/src/main/res/values-ro/translations.xml | 1 - 2 files changed, 2 deletions(-) diff --git a/features/analytics/impl/src/main/res/values-de/translations.xml b/features/analytics/impl/src/main/res/values-de/translations.xml index b606a6d82e..e0b033f68e 100644 --- a/features/analytics/impl/src/main/res/values-de/translations.xml +++ b/features/analytics/impl/src/main/res/values-de/translations.xml @@ -1,7 +1,6 @@ "Wir erfassen und analysieren ""keine"" Account-Daten" - "Helfen Sie uns, Probleme zu identifizieren und %1$s zu verbessern, indem Sie anonyme Nutzungsdaten weitergeben." "Sie können alle unsere Nutzerbedingungen %1$s lesen." "hier" "Sie können die Analyse jederzeit in den Einstellungen deaktivieren" diff --git a/features/analytics/impl/src/main/res/values-ro/translations.xml b/features/analytics/impl/src/main/res/values-ro/translations.xml index 91beaa7c52..c46378e6da 100644 --- a/features/analytics/impl/src/main/res/values-ro/translations.xml +++ b/features/analytics/impl/src/main/res/values-ro/translations.xml @@ -1,7 +1,6 @@ "Nu"" înregistrăm sau profilăm datele contului" - "Ajutați-ne să identificăm problemele și să îmbunătățim %1$s prin partajarea datelor de utilizare anonime." "Puteți citi toate condițiile noastre %1$s." "aici" "Puteți dezactiva această opțiune oricând din setări" From 7a1416aad4a7d7a5bf50805bdc76827ab1040b3d Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 16 Jun 2023 15:27:09 +0200 Subject: [PATCH 7/7] Add Modifier parameter and make internal Composable private. --- .../analytics/impl/AnalyticsOptInView.kt | 28 +++++++++++++------ 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/features/analytics/impl/src/main/kotlin/io/element/android/features/analytics/impl/AnalyticsOptInView.kt b/features/analytics/impl/src/main/kotlin/io/element/android/features/analytics/impl/AnalyticsOptInView.kt index 908eb4a329..e2af75ceca 100644 --- a/features/analytics/impl/src/main/kotlin/io/element/android/features/analytics/impl/AnalyticsOptInView.kt +++ b/features/analytics/impl/src/main/kotlin/io/element/android/features/analytics/impl/AnalyticsOptInView.kt @@ -82,11 +82,15 @@ fun AnalyticsOptInView( } @Composable -fun AnalyticsOptInHeader( +private fun AnalyticsOptInHeader( state: AnalyticsOptInState, onClickTerms: () -> Unit, + modifier: Modifier = Modifier, ) { - Column(horizontalAlignment = Alignment.CenterHorizontally) { + Column( + modifier = modifier, + horizontalAlignment = Alignment.CenterHorizontally, + ) { IconTitleSubtitleMolecule( modifier = Modifier.padding(top = 60.dp, bottom = 12.dp), title = stringResource(id = R.string.screen_analytics_prompt_title, state.applicationName), @@ -113,9 +117,11 @@ fun AnalyticsOptInHeader( } @Composable -fun AnalyticsOptInContent() { +private fun AnalyticsOptInContent( + modifier: Modifier = Modifier, +) { Box( - modifier = Modifier.fillMaxSize(), + modifier = modifier.fillMaxSize(), contentAlignment = BiasAlignment( horizontalBias = 0f, verticalBias = -0.4f @@ -141,9 +147,10 @@ fun AnalyticsOptInContent() { } @Composable -fun AnalyticsOptInContentRow( +private fun AnalyticsOptInContentRow( text: String, idx: Int, + modifier: Modifier = Modifier, ) { val radius = 14.dp val bgShape = when (idx) { @@ -152,7 +159,7 @@ fun AnalyticsOptInContentRow( else -> RoundedCornerShape(0.dp) } Row( - modifier = Modifier + modifier = modifier .fillMaxWidth() .background( color = LocalColors.current.quinary, @@ -181,8 +188,13 @@ fun AnalyticsOptInContentRow( } @Composable -fun AnalyticsOptInFooter(eventSink: (AnalyticsOptInEvents) -> Unit) { - ButtonColumnMolecule { +private fun AnalyticsOptInFooter( + eventSink: (AnalyticsOptInEvents) -> Unit, + modifier: Modifier = Modifier, +) { + ButtonColumnMolecule( + modifier = modifier, + ) { Button( onClick = { eventSink(AnalyticsOptInEvents.EnableAnalytics(true)) }, modifier = Modifier.fillMaxWidth(),