[#1262][#1083][#1241] Privacy policy link

- Closes #1262
- Closes #1083
- Closes #1241
This commit is contained in:
Honza Rychnovský 2024-03-04 18:01:24 +01:00 committed by GitHub
parent 45ab8ce8c9
commit 5bf7a635ff
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
15 changed files with 168 additions and 121 deletions

View File

@ -14,6 +14,7 @@ directly impact users rather than highlighting other key architectural updates.*
Choose server
- A new Server switching screen was added. Its purpose is to enable switching between predefined and custom
lightwalletd servers in runtime.
- The About screen now contains a link to the new Zashi Privacy Policy website
## [0.2.0 (560)] - 2024-02-27

View File

@ -165,6 +165,7 @@ data class ExtendedTypography(
val buttonTextSmall: TextStyle,
val checkboxText: TextStyle,
val securityWarningText: TextStyle,
val securityWarningFootnote: TextStyle,
val textFieldHint: TextStyle,
val textFieldValue: TextStyle,
val textFieldBirthday: TextStyle,
@ -250,7 +251,13 @@ val LocalExtendedTypography =
),
securityWarningText =
PrimaryTypography.bodySmall.copy(
lineHeight = 22.32.sp
fontSize = 16.sp,
fontWeight = FontWeight.Medium
),
securityWarningFootnote =
PrimaryTypography.bodySmall.copy(
fontSize = 11.sp,
fontWeight = FontWeight.Medium
),
textFieldHint =
PrimaryTypography.bodySmall.copy(

View File

@ -1,4 +1,4 @@
package co.electriccoin.zcash.ui.screen.securitywarning.util
package co.electriccoin.zcash.ui.screen.about.util
import android.content.Intent
import androidx.test.filters.SmallTest

View File

@ -1,5 +1,6 @@
package co.electriccoin.zcash.ui.screen.about.view
import androidx.compose.material3.SnackbarHostState
import androidx.compose.ui.test.junit4.ComposeContentTestRule
import co.electriccoin.zcash.ui.common.model.VersionInfo
import co.electriccoin.zcash.ui.design.theme.ZcashTheme
@ -24,7 +25,9 @@ class AboutViewTestSetup(
About(
onBack = { onBackCount.incrementAndGet() },
versionInfo = versionInfo,
configInfo = configInfo
configInfo = configInfo,
onPrivacyPolicy = {},
snackbarHostState = SnackbarHostState()
)
}
}

View File

@ -44,11 +44,6 @@ class SecurityWarningViewTest : UiTestPrerequisites() {
it.assertHasClickAction()
it.assertIsNotEnabled()
}
composeTestRule.onNodeWithTag(SecurityScreenTag.WARNING_TEXT_TAG).also {
it.assertExists()
it.assertIsDisplayed()
}
}
@Test

View File

@ -1,6 +1,5 @@
package co.electriccoin.zcash.ui.screen.securitywarning.view
import androidx.compose.material3.SnackbarHostState
import androidx.compose.runtime.Composable
import androidx.compose.ui.test.junit4.ComposeContentTestRule
import co.electriccoin.zcash.ui.design.theme.ZcashTheme
@ -34,13 +33,9 @@ class SecurityWarningViewTestSetup(private val composeTestRule: ComposeContentTe
@Suppress("TestFunctionName")
fun DefaultContent() {
SecurityWarning(
SnackbarHostState(),
onBack = {
onBackCount.incrementAndGet()
},
onPrivacyPolicy = {
// Not tested yet. UI testing of clicking on an AnnotatedString Text part is complicated.
},
onAcknowledged = {
onAcknowledged.getAndSet(it)
},

View File

@ -2,14 +2,22 @@
package co.electriccoin.zcash.ui.screen.about
import android.content.Context
import androidx.activity.ComponentActivity
import androidx.compose.material3.SnackbarHostState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import co.electriccoin.zcash.configuration.AndroidConfigurationFactory
import co.electriccoin.zcash.ui.MainActivity
import co.electriccoin.zcash.ui.R
import co.electriccoin.zcash.ui.common.model.VersionInfo
import co.electriccoin.zcash.ui.screen.about.util.WebBrowserUtil
import co.electriccoin.zcash.ui.screen.about.view.About
import co.electriccoin.zcash.ui.screen.support.model.ConfigInfo
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
@Composable
internal fun MainActivity.WrapAbout(goBack: () -> Unit) {
@ -29,9 +37,37 @@ internal fun WrapAbout(
AndroidConfigurationFactory.getInstance(activity.applicationContext).hintToRefresh()
}
val snackbarHostState = remember { SnackbarHostState() }
val scope = rememberCoroutineScope()
About(
onBack = goBack,
versionInfo = versionInfo,
configInfo = configInfo
configInfo = configInfo,
onPrivacyPolicy = {
openPrivacyPolicyInWebBrowser(
activity.applicationContext,
snackbarHostState,
scope
)
},
snackbarHostState = snackbarHostState,
)
}
fun openPrivacyPolicyInWebBrowser(
context: Context,
snackbarHostState: SnackbarHostState,
scope: CoroutineScope
) {
val storeIntent = WebBrowserUtil.newActivityIntent(WebBrowserUtil.ZCASH_PRIVACY_POLICY_URI)
runCatching {
context.startActivity(storeIntent)
}.onFailure {
scope.launch {
snackbarHostState.showSnackbar(
message = context.getString(R.string.about_unable_to_web_browser)
)
}
}
}

View File

@ -1,4 +1,4 @@
package co.electriccoin.zcash.ui.screen.securitywarning.util
package co.electriccoin.zcash.ui.screen.about.util
import android.content.Intent
import android.net.Uri
@ -9,7 +9,7 @@ object WebBrowserUtil {
Intent.FLAG_ACTIVITY_NEW_TASK or
Intent.FLAG_ACTIVITY_MULTIPLE_TASK
const val ZCASH_PRIVACY_POLICY_URI = "https://z.cash/privacy-policy/" // NON-NLS
const val ZCASH_PRIVACY_POLICY_URI = "https://electriccoin.co/zashi-privacy-policy/" // NON-NLS
/**
* Returns new action view app intent. We assume the a web browser app is installed.

View File

@ -5,4 +5,5 @@ package co.electriccoin.zcash.ui.screen.about.view
*/
object AboutTag {
const val DEBUG_MENU_TAG = "debug_menu"
const val PP_TEXT_TAG = "pp_text"
}

View File

@ -5,10 +5,12 @@ import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.text.ClickableText
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.MoreVert
@ -17,6 +19,8 @@ import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.Scaffold
import androidx.compose.material3.SnackbarHost
import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
@ -30,6 +34,10 @@ import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.semantics.contentDescription
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.style.TextDecoration
import androidx.compose.ui.text.withStyle
import androidx.compose.ui.tooling.preview.Preview
import co.electriccoin.zcash.ui.R
import co.electriccoin.zcash.ui.common.model.VersionInfo
@ -48,7 +56,9 @@ private fun AboutPreview() {
About(
onBack = {},
versionInfo = VersionInfoFixture.new(),
configInfo = ConfigInfoFixture.new()
configInfo = ConfigInfoFixture.new(),
snackbarHostState = SnackbarHostState(),
onPrivacyPolicy = {},
)
}
}
@ -57,18 +67,24 @@ private fun AboutPreview() {
@Composable
fun About(
onBack: () -> Unit,
configInfo: ConfigInfo,
onPrivacyPolicy: () -> Unit,
snackbarHostState: SnackbarHostState,
versionInfo: VersionInfo,
configInfo: ConfigInfo
) {
Scaffold(topBar = {
AboutTopAppBar(
onBack = onBack,
versionInfo = versionInfo,
configInfo = configInfo
)
}) { paddingValues ->
Scaffold(
topBar = {
AboutTopAppBar(
onBack = onBack,
versionInfo = versionInfo,
configInfo = configInfo
)
},
snackbarHost = { SnackbarHost(snackbarHostState) },
) { paddingValues ->
AboutMainContent(
versionInfo = versionInfo,
onPrivacyPolicy = onPrivacyPolicy,
modifier =
Modifier
.fillMaxHeight()
@ -143,6 +159,7 @@ private fun DebugMenu(
@Composable
fun AboutMainContent(
onPrivacyPolicy: () -> Unit,
versionInfo: VersionInfo,
modifier: Modifier = Modifier
) {
@ -188,5 +205,49 @@ fun AboutMainContent(
color = ZcashTheme.colors.aboutTextColor,
style = ZcashTheme.extendedTypography.aboutText
)
PrivacyPolicyLink(onPrivacyPolicy)
}
}
@Composable
fun PrivacyPolicyLink(onPrivacyPolicy: () -> Unit) {
Column {
Spacer(modifier = Modifier.height(ZcashTheme.dimens.spacingLarge))
val textPart1 = stringResource(R.string.about_pp_part_1)
val textPart2 = stringResource(R.string.about_pp_part_2)
val textPart3 = stringResource(R.string.about_pp_part_3)
ClickableText(
text =
buildAnnotatedString {
withStyle(SpanStyle(color = ZcashTheme.colors.aboutTextColor)) {
append(textPart1)
}
withStyle(
SpanStyle(
textDecoration = TextDecoration.Underline,
color = ZcashTheme.colors.aboutTextColor,
)
) {
append(textPart2)
}
withStyle(SpanStyle(color = ZcashTheme.colors.aboutTextColor)) {
append(textPart3)
}
},
style = ZcashTheme.extendedTypography.aboutText,
modifier =
Modifier
.fillMaxWidth()
.testTag(AboutTag.PP_TEXT_TAG),
onClick = { letterOffset ->
// Call the callback only if user clicked the underlined part
if (letterOffset >= textPart1.length && letterOffset <= (textPart1.length + textPart2.length)) {
onPrivacyPolicy()
}
}
)
}
}

View File

@ -1,20 +1,12 @@
package co.electriccoin.zcash.ui.screen.securitywarning
import android.content.Context
import androidx.activity.ComponentActivity
import androidx.compose.material3.SnackbarHostState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import co.electriccoin.zcash.configuration.AndroidConfigurationFactory
import co.electriccoin.zcash.ui.MainActivity
import co.electriccoin.zcash.ui.R
import co.electriccoin.zcash.ui.common.model.VersionInfo
import co.electriccoin.zcash.ui.screen.securitywarning.util.WebBrowserUtil
import co.electriccoin.zcash.ui.screen.securitywarning.view.SecurityWarning
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
@Composable
internal fun MainActivity.WrapSecurityWarning(
@ -34,23 +26,12 @@ internal fun WrapSecurityWarning(
onBack: () -> Unit,
onConfirm: () -> Unit
) {
val snackbarHostState = remember { SnackbarHostState() }
val scope = rememberCoroutineScope()
SecurityWarning(
snackbarHostState = snackbarHostState,
versionInfo = VersionInfo.new(activity.applicationContext),
onBack = onBack,
onAcknowledged = {
// Needed for UI testing only
},
onPrivacyPolicy = {
openPrivacyPolicyInWebBrowser(
activity.applicationContext,
snackbarHostState,
scope
)
},
onConfirm = onConfirm
)
@ -58,20 +39,3 @@ internal fun WrapSecurityWarning(
AndroidConfigurationFactory.getInstance(activity.applicationContext).hintToRefresh()
}
}
fun openPrivacyPolicyInWebBrowser(
context: Context,
snackbarHostState: SnackbarHostState,
scope: CoroutineScope
) {
val storeIntent = WebBrowserUtil.newActivityIntent(WebBrowserUtil.ZCASH_PRIVACY_POLICY_URI)
runCatching {
context.startActivity(storeIntent)
}.onFailure {
scope.launch {
snackbarHostState.showSnackbar(
message = context.getString(R.string.security_warning_unable_to_web_browser)
)
}
}
}

View File

@ -5,5 +5,4 @@ package co.electriccoin.zcash.ui.screen.securitywarning.view
*/
object SecurityScreenTag {
const val ACKNOWLEDGE_CHECKBOX_TAG = "acknowledge_checkbox"
const val WARNING_TEXT_TAG = "warning_text"
}

View File

@ -8,22 +8,18 @@ import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.text.ClickableText
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.Scaffold
import androidx.compose.material3.SnackbarHost
import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextDecoration
import androidx.compose.ui.text.withStyle
import androidx.compose.ui.tooling.preview.Preview
import co.electriccoin.zcash.ui.R
@ -43,11 +39,9 @@ private fun SecurityWarningPreview() {
ZcashTheme(forceDarkMode = false) {
GradientSurface {
SecurityWarning(
snackbarHostState = SnackbarHostState(),
versionInfo = VersionInfoFixture.new(),
onBack = {},
onAcknowledged = {},
onPrivacyPolicy = {},
onConfirm = {},
)
}
@ -60,20 +54,16 @@ private fun SecurityWarningPreview() {
@Composable
@Suppress("LongParameterList")
fun SecurityWarning(
snackbarHostState: SnackbarHostState,
versionInfo: VersionInfo,
onBack: () -> Unit,
onAcknowledged: (Boolean) -> Unit,
onPrivacyPolicy: () -> Unit,
onConfirm: () -> Unit,
) {
Scaffold(
topBar = { SecurityWarningTopAppBar(onBack = onBack) },
snackbarHost = { SnackbarHost(snackbarHostState) },
) { paddingValues ->
SecurityWarningContent(
versionInfo = versionInfo,
onPrivacyPolicy = onPrivacyPolicy,
onAcknowledged = onAcknowledged,
onConfirm = onConfirm,
modifier =
@ -102,7 +92,6 @@ private fun SecurityWarningTopAppBar(onBack: () -> Unit) {
@Composable
private fun SecurityWarningContent(
versionInfo: VersionInfo,
onPrivacyPolicy: () -> Unit,
onAcknowledged: (Boolean) -> Unit,
onConfirm: () -> Unit,
modifier: Modifier = Modifier,
@ -119,11 +108,10 @@ private fun SecurityWarningContent(
Spacer(Modifier.height(ZcashTheme.dimens.spacingLarge))
SecurityWarningContentText(
versionInfo = versionInfo,
onPrivacyPolicy = onPrivacyPolicy
versionInfo = versionInfo
)
Spacer(Modifier.height(ZcashTheme.dimens.spacingDefault))
Spacer(Modifier.height(ZcashTheme.dimens.spacingLarge))
val checkedState = rememberSaveable { mutableStateOf(false) }
CheckBox(
@ -158,35 +146,28 @@ private fun SecurityWarningContent(
}
@Composable
fun SecurityWarningContentText(
versionInfo: VersionInfo,
onPrivacyPolicy: () -> Unit,
) {
val textPart1 = stringResource(R.string.security_warning_text_part_1, versionInfo.versionName)
val textPart2 = stringResource(R.string.security_warning_text_part_2)
ClickableText(
text =
buildAnnotatedString {
append(textPart1)
withStyle(SpanStyle(textDecoration = TextDecoration.Underline)) {
fun SecurityWarningContentText(versionInfo: VersionInfo) {
Column {
Text(
text = stringResource(id = R.string.security_warning_text, versionInfo.versionName),
style = ZcashTheme.extendedTypography.securityWarningText
)
Spacer(modifier = Modifier.height(ZcashTheme.dimens.spacingLarge))
val textPart1 = stringResource(R.string.security_warning_text_footnote_part_1)
val textPart2 = stringResource(R.string.security_warning_text_footnote_part_2)
Text(
text =
buildAnnotatedString {
withStyle(SpanStyle(fontWeight = FontWeight.Bold)) {
append(textPart1)
}
append(textPart2)
}
append(stringResource(R.string.security_warning_text_part_3))
withStyle(SpanStyle(fontWeight = FontWeight.Bold)) {
append(stringResource(R.string.security_warning_text_part_4))
}
append(stringResource(R.string.security_warning_text_part_5))
},
style = ZcashTheme.extendedTypography.securityWarningText,
modifier =
Modifier
.fillMaxWidth()
.testTag(SecurityScreenTag.WARNING_TEXT_TAG),
onClick = { letterOffset ->
// Call the callback only if user clicked the underlined part
if (letterOffset >= textPart1.length && letterOffset <= (textPart1.length + textPart2.length)) {
onPrivacyPolicy()
}
}
)
},
style = ZcashTheme.extendedTypography.securityWarningFootnote,
modifier = Modifier.fillMaxWidth()
)
}
}

View File

@ -12,4 +12,9 @@
shielded wallet that keeps your transaction history and wallet balance private. Built by Zcashers, for
Zcashers. Developed and maintained by Electric Coin Co., the inventor of Zcash, Zashi features a built-in
user-feedback mechanism to enable more features, more quickly.</string>
<string name="about_pp_part_1">See our Privacy Policy\u0020</string>
<string name="about_pp_part_2">here</string>
<string name="about_pp_part_3">.</string>
<string name="about_unable_to_web_browser">Unable to find a web browser app.</string>
</resources>

View File

@ -1,18 +1,17 @@
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="security_warning_text_part_1" formatted="true">Zashi <xliff:g example="0.2.0" id="version_name">%1$s
</xliff:g>is a Zcash-only shielded wallet, built by Zcashers for Zcashers. The purpose of this release is
primarily to test functionality and collect feedback. While Zashi has been engineered for your privacy and
safety (read the privacy policy\u0020</string>
<string name="security_warning_text_part_2">here</string>
<string name="security_warning_text_part_3">), this release has not yet been security audited.</string>
<string name="security_warning_text_part_4">\u0020Users are cautioned to deposit, send, and receive only small
amounts
of ZEC.</string>
<string name="security_warning_text_part_5">\u0020Please click below to proceed.</string>
<string name="security_warning_confirm">confirm</string>
<string name="security_warning_acknowledge">I acknowledge</string>
<string name="security_warning_back_content_description">Back</string>
<string name="security_warning_header">Security warning:</string>
<string name="security_warning_back">Back</string>
<string name="security_warning_unable_to_web_browser">Unable to find a web browser app.</string>
<string name="security_warning_back_content_description">Back</string>
<string name="security_warning_text" formatted="true">Zashi <xliff:g example="0.2.0" id="version_name">%1$s
</xliff:g>is a Zcash-only, shielded wallet — built by Zcashers for Zcashers. Zashi has been engineered for your
privacy and safety. By installing and using Zashi, you consent to share crash reports with Electric Coin Co.
(the wallet developer), which will help us improve the Zashi user experience.*\n\nPlease acknowledge and
confirm below to proceed.</string>
<string name="security_warning_text_footnote_part_1">*Note:</string>
<string name="security_warning_text_footnote_part_2">\u0020Crash reports might reveal the timing of the crash and
what events occurred, but it would not reveal spending or viewing keys.</string>
<string name="security_warning_confirm">Confirm</string>
<string name="security_warning_acknowledge">I acknowledge</string>
</resources>