refactor: move ToS / legal links into flavor source sets

The Play-Store-only Terms-of-Use checkbox and the "About & Legal"
Settings section both contain hardcoded GitHub URLs to the published
PRIVACY.md. Previously they were in src/main and gated at call sites
by `BuildConfig.FLAVOR == "play"` — which works at runtime but still
compiles the GitHub URLs into the F-Droid APK.

This commit moves the URL-bearing UI into src/play and adds empty
src/fdroid stubs with the same signatures, so the F-Droid build is
physically free of the URLs.

New composables (same package + signature in both flavor source sets,
so callers in src/main link to whichever flavor is being built):

- com.vitorpamplona.amethyst.ui.screen.loggedOff.legal.TermsGate
  * src/play: renders an AcceptTerms checkbox with the PRIVACY.md link
    and the "acceptance required" error text.
  * src/fdroid: empty body.

- com.vitorpamplona.amethyst.ui.screen.loggedIn.settings.LegalSettingsSection
  * src/play: renders the SettingsSection with Privacy Policy and
    Child Safety Standards rows that open the GitHub-hosted PRIVACY.md.
  * src/fdroid: empty body.

Call-site changes:

- LoginScreen: drop `BuildConfig.FLAVOR == "play"` guard and call
  `TermsGate(...)` inside the existing `isFirstLogin` block. The
  flavor source set picks the right body.
- SignUpScreen: drop `BuildConfig.FLAVOR == "play"` guard, call
  `TermsGate(...)` unconditionally.
- AllSettingsScreen: drop `BuildConfig.FLAVOR == "play"` guard and
  the inline About & Legal SettingsSection, call
  `LegalSettingsSection()` instead. LocalUriHandler and BuildConfig
  imports removed.

The old `src/main/.../loggedOff/AcceptTerms.kt` is deleted; its body
moves into the play-flavor TermsGate as a file-private helper.

The LoginViewModel / SignUpViewModel still use `BuildConfig.FLAVOR` to
decide the initial `acceptedTerms` value (so the login/signup button
isn't disabled on F-Droid). That check is logic only, contains no
URLs, and is safe for F-Droid distribution.

Verified: `grep -r "github.com/vitorpamplona/amethyst/blob"` against
src/main + src/fdroid returns zero matches. Both
:amethyst:compileFdroidDebugKotlin and :amethyst:compilePlayDebugKotlin
compile cleanly.
This commit is contained in:
Claude
2026-05-24 16:45:48 +00:00
parent f058662349
commit 2628f45788
7 changed files with 152 additions and 61 deletions
@@ -0,0 +1,29 @@
/*
* Copyright (c) 2025 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.amethyst.ui.screen.loggedIn.settings
import androidx.compose.runtime.Composable
// F-Droid distributes Amethyst as MIT-licensed free software; the build must
// not surface links to external (e.g. GitHub-hosted) policy documents.
@Composable
fun LegalSettingsSection() {
}
@@ -0,0 +1,35 @@
/*
* Copyright (c) 2025 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.amethyst.ui.screen.loggedOff.legal
import androidx.compose.runtime.Composable
// F-Droid distributes Amethyst as MIT-licensed free software; there is no
// terms-of-use acceptance layered on top of the source license, and the build
// must not link to any external (e.g. GitHub) policy document.
@Composable
@Suppress("UNUSED_PARAMETER")
fun TermsGate(
checked: Boolean,
onCheckedChange: (Boolean) -> Unit,
showError: Boolean,
) {
}
@@ -42,11 +42,9 @@ import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalUriHandler
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.vitorpamplona.amethyst.BuildConfig
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.commons.icons.symbols.Icon
import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols
@@ -79,7 +77,6 @@ fun AllSettingsScreen(
nav: INav,
) {
val context = LocalContext.current
val uriHandler = LocalUriHandler.current
val scope = rememberCoroutineScope()
var showResetMarmotDialog by remember { mutableStateOf(false) }
var isResettingMarmot by remember { mutableStateOf(false) }
@@ -264,33 +261,7 @@ fun AllSettingsScreen(
)
}
if (BuildConfig.FLAVOR == "play") {
SettingsSection(R.string.about_legal) {
SettingsItem(
title = R.string.privacy_policy,
icon = MaterialSymbols.Lock,
onClick = {
runCatching {
uriHandler.openUri(
"https://github.com/vitorpamplona/amethyst/blob/main/PRIVACY.md",
)
}
},
)
SettingsDivider()
SettingsItem(
title = R.string.child_safety_standards,
icon = MaterialSymbols.Shield,
onClick = {
runCatching {
uriHandler.openUri(
"https://github.com/vitorpamplona/amethyst/blob/main/PRIVACY.md#child-safety-standards",
)
}
},
)
}
}
LegalSettingsSection()
SettingsSection(R.string.danger_zone, isDanger = true) {
if (hasPrivateKey) {
@@ -66,15 +66,14 @@ import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import com.vitorpamplona.amethyst.Amethyst
import com.vitorpamplona.amethyst.BuildConfig
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.commons.hashtags.Amethyst
import com.vitorpamplona.amethyst.commons.hashtags.CustomHashTagIcons
import com.vitorpamplona.amethyst.commons.icons.symbols.Icon
import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols
import com.vitorpamplona.amethyst.ui.screen.AccountSessionManager
import com.vitorpamplona.amethyst.ui.screen.loggedOff.AcceptTerms
import com.vitorpamplona.amethyst.ui.screen.loggedOff.TorSettingsSetup
import com.vitorpamplona.amethyst.ui.screen.loggedOff.legal.TermsGate
import com.vitorpamplona.amethyst.ui.stringRes
import com.vitorpamplona.amethyst.ui.theme.Size10dp
import com.vitorpamplona.amethyst.ui.theme.Size20dp
@@ -197,19 +196,12 @@ fun LoginPage(
)
}
if (loginViewModel.isFirstLogin && BuildConfig.FLAVOR == "play") {
AcceptTerms(
if (loginViewModel.isFirstLogin) {
TermsGate(
checked = loginViewModel.acceptedTerms,
onCheckedChange = loginViewModel::updateAcceptedTerms,
showError = loginViewModel.termsAcceptanceIsRequiredError,
)
if (loginViewModel.termsAcceptanceIsRequiredError) {
Text(
text = stringRes(R.string.acceptance_of_terms_is_required),
color = MaterialTheme.colorScheme.error,
style = MaterialTheme.typography.bodySmall,
)
}
}
Spacer(modifier = Modifier.height(Size10dp))
@@ -50,13 +50,12 @@ import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import com.vitorpamplona.amethyst.Amethyst
import com.vitorpamplona.amethyst.BuildConfig
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.commons.hashtags.Amethyst
import com.vitorpamplona.amethyst.commons.hashtags.CustomHashTagIcons
import com.vitorpamplona.amethyst.ui.screen.AccountSessionManager
import com.vitorpamplona.amethyst.ui.screen.loggedOff.AcceptTerms
import com.vitorpamplona.amethyst.ui.screen.loggedOff.TorSettingsSetup
import com.vitorpamplona.amethyst.ui.screen.loggedOff.legal.TermsGate
import com.vitorpamplona.amethyst.ui.screen.loggedOff.login.LoginErrorManager
import com.vitorpamplona.amethyst.ui.stringRes
import com.vitorpamplona.amethyst.ui.theme.Size10dp
@@ -188,20 +187,11 @@ fun SignUpPage(
},
)
if (BuildConfig.FLAVOR == "play") {
AcceptTerms(
checked = signUpViewModel.acceptedTerms,
onCheckedChange = signUpViewModel::updateAcceptedTerms,
)
if (signUpViewModel.termsAcceptanceIsRequiredError) {
Text(
text = stringRes(R.string.acceptance_of_terms_is_required),
color = MaterialTheme.colorScheme.error,
style = MaterialTheme.typography.bodySmall,
)
}
}
TermsGate(
checked = signUpViewModel.acceptedTerms,
onCheckedChange = signUpViewModel::updateAcceptedTerms,
showError = signUpViewModel.termsAcceptanceIsRequiredError,
)
Spacer(modifier = Modifier.height(Size10dp))
@@ -0,0 +1,57 @@
/*
* Copyright (c) 2025 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.amethyst.ui.screen.loggedIn.settings
import androidx.compose.runtime.Composable
import androidx.compose.ui.platform.LocalUriHandler
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols
@Composable
fun LegalSettingsSection() {
val uriHandler = LocalUriHandler.current
SettingsSection(R.string.about_legal) {
SettingsItem(
title = R.string.privacy_policy,
icon = MaterialSymbols.Lock,
onClick = {
runCatching {
uriHandler.openUri(
"https://github.com/vitorpamplona/amethyst/blob/main/PRIVACY.md",
)
}
},
)
SettingsDivider()
SettingsItem(
title = R.string.child_safety_standards,
icon = MaterialSymbols.Shield,
onClick = {
runCatching {
uriHandler.openUri(
"https://github.com/vitorpamplona/amethyst/blob/main/PRIVACY.md#child-safety-standards",
)
}
},
)
}
}
@@ -18,7 +18,7 @@
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.amethyst.ui.screen.loggedOff
package com.vitorpamplona.amethyst.ui.screen.loggedOff.legal
import androidx.compose.foundation.layout.Row
import androidx.compose.material3.Checkbox
@@ -33,9 +33,26 @@ import com.vitorpamplona.amethyst.ui.components.appendLink
import com.vitorpamplona.amethyst.ui.stringRes
@Composable
fun AcceptTerms(
fun TermsGate(
checked: Boolean,
onCheckedChange: ((Boolean) -> Unit)?,
onCheckedChange: (Boolean) -> Unit,
showError: Boolean,
) {
AcceptTerms(checked = checked, onCheckedChange = onCheckedChange)
if (showError) {
Text(
text = stringRes(R.string.acceptance_of_terms_is_required),
color = MaterialTheme.colorScheme.error,
style = MaterialTheme.typography.bodySmall,
)
}
}
@Composable
private fun AcceptTerms(
checked: Boolean,
onCheckedChange: (Boolean) -> Unit,
) {
Row(verticalAlignment = Alignment.CenterVertically) {
Checkbox(