Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package de.tabmates.core.domain.legal

/**
* Public legal pages on the marketing site. Deliberately not derived from
* `BuildKonfig.BASE_URL_PUBLIC`: that points at the app host (app.tabmates.de) and, via the
* environment switcher, can point at a custom backend — the privacy policy must always resolve to
* the production site regardless of which server the app talks to.
*/
object LegalUrls {
const val PRIVACY_POLICY = "https://tabmates.de/privacy/"
}
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,12 @@
<!-- Guest Register -->
<string name="register_guest_desc">Ein Gastkonto existiert nur auf diesem Gerät — wenn du dich abmeldest oder das Gerät wechselst, verlierst du den Zugriff auf deine Gruppen. Du kannst es jederzeit im Profil in ein vollwertiges Konto umwandeln und alles behalten.</string>

<!-- Privacy notice -->
<string name="privacy_notice_register_prefix">Mit dem Erstellen eines Kontos stimmst du unserer </string>
<string name="privacy_notice_guest_prefix">Wenn du fortfährst, stimmst du unserer </string>
<string name="privacy_policy_link">Datenschutzerklärung</string>
<string name="privacy_notice_suffix"> zu.</string>

<!-- Environment -->
<string name="welcome_button_environment">Umgebung: %1$s</string>
<string name="environment_title">Server-Umgebung</string>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,12 @@
<!-- Guest Register -->
<string name="register_guest_desc">A guest account lives only on this device — sign out or switch devices and you lose access to your groups. You can turn it into a full account any time under Profile, and keep everything.</string>

<!-- Privacy notice -->
<string name="privacy_notice_register_prefix">By creating an account you agree to our </string>
<string name="privacy_notice_guest_prefix">By continuing you agree to our </string>
<string name="privacy_policy_link">Privacy Policy</string>
<string name="privacy_notice_suffix">.</string>

<!-- Environment -->
<string name="welcome_button_environment">Environment: %1$s</string>
<string name="environment_title">Server environment</string>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package de.tabmates.features.authentication.presentation.components

import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalUriHandler
import androidx.compose.ui.text.style.TextAlign
import de.tabmates.core.designsystem.preview.PreviewThemes
import de.tabmates.core.designsystem.text.TabMatesInlineLinkText
import de.tabmates.core.designsystem.theme.TabMatesTheme
import de.tabmates.core.domain.legal.LegalUrls
import org.jetbrains.compose.resources.stringResource
import tabmatesapp.features.authentication.presentation.generated.resources.Res
import tabmatesapp.features.authentication.presentation.generated.resources.privacy_notice_register_prefix
import tabmatesapp.features.authentication.presentation.generated.resources.privacy_notice_suffix
import tabmatesapp.features.authentication.presentation.generated.resources.privacy_policy_link

/**
* Fine print shown wherever an account is created, so the privacy policy is one tap away at the
* moment data is actually collected. Informational only — no consent checkbox, because sign-up
* data is processed to perform the contract, not on the basis of consent.
*
* [prefix] is the sentence up to the link; the trailing part comes from `privacy_notice_suffix`,
* which German needs for the separable verb particle.
*/
@Composable
fun PrivacyNotice(
prefix: String,
modifier: Modifier = Modifier,
) {
val uriHandler = LocalUriHandler.current
TabMatesInlineLinkText(
modifier = modifier,
textBeforeLink = prefix,
linkText = stringResource(Res.string.privacy_policy_link),
textAfterLink = stringResource(Res.string.privacy_notice_suffix),
onLinkClick = { uriHandler.openUri(LegalUrls.PRIVACY_POLICY) },
textStyle = MaterialTheme.typography.bodySmall.copy(textAlign = TextAlign.Center),
textColor = MaterialTheme.colorScheme.onSurfaceVariant,
)
}

@PreviewThemes
@Composable
private fun PrivacyNoticePreview() {
TabMatesTheme {
Surface {
PrivacyNotice(prefix = stringResource(Res.string.privacy_notice_register_prefix))
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import de.tabmates.core.designsystem.textfields.TabMatesTextField
import de.tabmates.core.designsystem.theme.TabMatesTheme
import de.tabmates.core.designsystem.theme.headlineLargeBold
import de.tabmates.core.presentation.util.ObserveAsEvents
import de.tabmates.features.authentication.presentation.components.PrivacyNotice
import de.tabmates.features.authentication.presentation.di.AuthPresentationModule
import de.tabmates.features.authentication.presentation.navigation.Login
import de.tabmates.features.authentication.presentation.navigation.Register
Expand All @@ -46,6 +47,7 @@ import org.koin.compose.KoinApplicationPreview
import org.koin.compose.viewmodel.koinViewModel
import org.koin.plugin.module.dsl.modules
import tabmatesapp.features.authentication.presentation.generated.resources.Res
import tabmatesapp.features.authentication.presentation.generated.resources.privacy_notice_register_prefix
import tabmatesapp.features.authentication.presentation.generated.resources.register_already_have_account_prefix
import tabmatesapp.features.authentication.presentation.generated.resources.register_confirm_password_hint
import tabmatesapp.features.authentication.presentation.generated.resources.register_email_hint
Expand Down Expand Up @@ -201,6 +203,11 @@ private fun RegisterScreen(
enabled = !state.isRegistering,
isLoading = state.isRegistering,
)
VerticalSpacer(12.dp)
PrivacyNotice(
prefix = stringResource(Res.string.privacy_notice_register_prefix),
modifier = Modifier.widthIn(max = 300.dp),
)
VerticalSpacer(16.dp)
TabMatesInlineLinkText(
textBeforeLink = stringResource(Res.string.register_already_have_account_prefix),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,11 @@ import de.tabmates.core.designsystem.textfields.TabMatesTextField
import de.tabmates.core.designsystem.theme.TabMatesTheme
import de.tabmates.core.presentation.util.ObserveAsEvents
import de.tabmates.core.presentation.util.UiText
import de.tabmates.features.authentication.presentation.components.PrivacyNotice
import org.jetbrains.compose.resources.stringResource
import org.koin.compose.viewmodel.koinViewModel
import tabmatesapp.features.authentication.presentation.generated.resources.Res
import tabmatesapp.features.authentication.presentation.generated.resources.privacy_notice_guest_prefix
import tabmatesapp.features.authentication.presentation.generated.resources.register_guest_desc
import tabmatesapp.features.authentication.presentation.generated.resources.register_username_hint
import tabmatesapp.features.authentication.presentation.generated.resources.welcome_button_guest
Expand Down Expand Up @@ -112,6 +114,11 @@ private fun RegisterGuestScreen(
onClick = onGuestClick,
isLoading = isRegistering,
)
VerticalSpacer(12.dp)
PrivacyNotice(
prefix = stringResource(Res.string.privacy_notice_guest_prefix),
modifier = Modifier.widthIn(max = 300.dp),
)
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960">
<group android:translateY="960">
<path
android:fillColor="#FFFFFF"
android:pathData="M480-80q-139-35-229.5-159.5T160-516v-244l320-120 320 120v244q0 152-90.5 276.5T480-80Zm0-84q104-33 172-132t68-220v-189l-240-90-240 90v189q0 121 68 220t172 132Zm0-316Z" />
</group>
</vector>
Original file line number Diff line number Diff line change
Expand Up @@ -318,6 +318,8 @@
<string name="profile_section_appearance">ERSCHEINUNGSBILD</string>
<string name="profile_section_about">ÜBER</string>
<string name="profile_app_version">Version</string>
<string name="profile_privacy_policy">Datenschutzerklärung</string>
<string name="profile_privacy_policy_caption">Öffnet tabmates.de in deinem Browser</string>
<string name="profile_oss_licenses">Open-Source-Lizenzen</string>
<string name="profile_oss_licenses_caption">Bibliotheken von Drittanbietern</string>
<string name="oss_licenses_title">Open-Source-Lizenzen</string>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -319,6 +319,8 @@
<string name="profile_section_appearance">APPEARANCE</string>
<string name="profile_section_about">ABOUT</string>
<string name="profile_app_version">Version</string>
<string name="profile_privacy_policy">Privacy policy</string>
<string name="profile_privacy_policy_caption">Opens tabmates.de in your browser</string>
<string name="profile_oss_licenses">Open-source licenses</string>
<string name="profile_oss_licenses_caption">Third-party libraries</string>
<string name="oss_licenses_title">Open-source licenses</string>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalUriHandler
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
Expand All @@ -39,6 +40,7 @@ import de.tabmates.core.data.AppBuildInfo
import de.tabmates.core.designsystem.spacer.HorizontalSpacer
import de.tabmates.core.designsystem.spacer.VerticalSpacer
import de.tabmates.core.designsystem.text.SectionLabel
import de.tabmates.core.domain.legal.LegalUrls
import de.tabmates.core.domain.preferences.ThemeMode
import de.tabmates.core.presentation.util.ObserveAsEvents
import de.tabmates.features.tabgroup.presentation.navigation.groupoverview.UserAvatar
Expand All @@ -59,6 +61,7 @@ import tabmatesapp.features.tabgroup.presentation.generated.resources.ic_notific
import tabmatesapp.features.tabgroup.presentation.generated.resources.ic_palette
import tabmatesapp.features.tabgroup.presentation.generated.resources.ic_person_add
import tabmatesapp.features.tabgroup.presentation.generated.resources.ic_settings
import tabmatesapp.features.tabgroup.presentation.generated.resources.ic_shield
import tabmatesapp.features.tabgroup.presentation.generated.resources.profile_account_email
import tabmatesapp.features.tabgroup.presentation.generated.resources.profile_account_password
import tabmatesapp.features.tabgroup.presentation.generated.resources.profile_account_username
Expand All @@ -75,6 +78,8 @@ import tabmatesapp.features.tabgroup.presentation.generated.resources.profile_op
import tabmatesapp.features.tabgroup.presentation.generated.resources.profile_oss_licenses
import tabmatesapp.features.tabgroup.presentation.generated.resources.profile_oss_licenses_caption
import tabmatesapp.features.tabgroup.presentation.generated.resources.profile_password_subtitle
import tabmatesapp.features.tabgroup.presentation.generated.resources.profile_privacy_policy
import tabmatesapp.features.tabgroup.presentation.generated.resources.profile_privacy_policy_caption
import tabmatesapp.features.tabgroup.presentation.generated.resources.profile_section_about
import tabmatesapp.features.tabgroup.presentation.generated.resources.profile_section_account
import tabmatesapp.features.tabgroup.presentation.generated.resources.profile_section_appearance
Expand Down Expand Up @@ -421,6 +426,7 @@ private fun SettingsTwoPane(
SettingsSection.ABOUT -> {
SectionLabel(stringResource(Res.string.profile_section_about))
AppVersionRow()
PrivacyPolicyRow()
AccountRow(
iconRes = Res.drawable.ic_info,
title = stringResource(Res.string.profile_oss_licenses),
Expand Down Expand Up @@ -629,6 +635,7 @@ private fun ProfileAccountAndAppearance(
VerticalSpacer(4.dp)
SectionLabel(stringResource(Res.string.profile_section_about))
AppVersionRow()
PrivacyPolicyRow()
AccountRow(
iconRes = Res.drawable.ic_info,
title = stringResource(Res.string.profile_oss_licenses),
Expand All @@ -651,6 +658,23 @@ private fun AppVersionRow() {
)
}

/**
* The policy is hosted on the marketing site, so this row leaves the app — which the chevron alone
* cannot say. The subtitle does, so the row still looks like its siblings without misleading.
*/
@Composable
private fun PrivacyPolicyRow() {
val uriHandler = LocalUriHandler.current
AccountRow(
iconRes = Res.drawable.ic_shield,
title = stringResource(Res.string.profile_privacy_policy),
subtitle = stringResource(Res.string.profile_privacy_policy_caption),
onClick = { uriHandler.openUri(LegalUrls.PRIVACY_POLICY) },
showChevron = true,
modifier = Modifier.fillMaxWidth(),
)
}

@Composable
private fun ProfileHeaderCard(state: ProfileState) {
Surface(
Expand Down