feat: Embed the privacy policy in the app (#139)
Instead of linking to the privacy policy embed it in the app as a string of HTML. The string is created with a new `markdown2resource` plugin, which converts `PRIVACY.md` to HTML and generates a Java class with the HTML content. Create `PrivacyPolicyActivity` to display the HTML in a `WebView`, and link to it from `AboutActivity`.
This commit is contained in:
parent
2cc534f22a
commit
802cdd4c46
|
@ -5,6 +5,8 @@ plugins {
|
|||
alias(libs.plugins.kotlin.kapt)
|
||||
alias(libs.plugins.kotlin.parcelize)
|
||||
alias(libs.plugins.aboutlibraries)
|
||||
|
||||
id "app.pachli.plugins.markdown2resource"
|
||||
}
|
||||
|
||||
apply from: 'gitTools.gradle'
|
||||
|
@ -139,6 +141,10 @@ aboutLibraries {
|
|||
prettyPrint = true
|
||||
}
|
||||
|
||||
markdown2resource {
|
||||
files = [ layout.projectDirectory.file('../PRIVACY.md') ]
|
||||
}
|
||||
|
||||
// library versions are in PROJECT_ROOT/gradle/libs.versions.toml
|
||||
dependencies {
|
||||
implementation libs.kotlinx.coroutines.android
|
||||
|
|
|
@ -47,7 +47,7 @@
|
|||
errorLine2=" ~~~~~~~~~~~~">
|
||||
<location
|
||||
file="build.gradle"
|
||||
line="33"
|
||||
line="35"
|
||||
column="9"/>
|
||||
</issue>
|
||||
|
||||
|
@ -2064,17 +2064,6 @@
|
|||
column="9"/>
|
||||
</issue>
|
||||
|
||||
<issue
|
||||
id="SyntheticAccessor"
|
||||
message="Access to `private` method `setClickableTextWithoutUnderlines` of class `AboutActivityKt` requires synthetic accessor"
|
||||
errorLine1=" binding.aboutPrivacyPolicyTextView.setClickableTextWithoutUnderlines(R.string.about_privacy_policy)"
|
||||
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
|
||||
<location
|
||||
file="src/main/java/app/pachli/AboutActivity.kt"
|
||||
line="76"
|
||||
column="9"/>
|
||||
</issue>
|
||||
|
||||
<issue
|
||||
id="SyntheticAccessor"
|
||||
message="Access to `private` method `isSameDate` of class `Companion` requires synthetic accessor"
|
||||
|
|
|
@ -145,6 +145,7 @@
|
|||
</activity>
|
||||
<activity android:name=".ListsActivity" />
|
||||
<activity android:name=".LicenseActivity" />
|
||||
<activity android:name=".PrivacyPolicyActivity" />
|
||||
<activity android:name=".components.filters.FiltersActivity" />
|
||||
<activity android:name=".components.trending.TrendingActivity" />
|
||||
<activity android:name=".components.followedtags.FollowedTagsActivity" />
|
||||
|
|
|
@ -73,7 +73,11 @@ class AboutActivity : BottomSheetActivity(), Injectable {
|
|||
binding.aboutLicenseInfoTextView.setClickableTextWithoutUnderlines(R.string.about_pachli_license)
|
||||
binding.aboutWebsiteInfoTextView.setClickableTextWithoutUnderlines(R.string.about_project_site)
|
||||
binding.aboutBugsFeaturesInfoTextView.setClickableTextWithoutUnderlines(R.string.about_bug_feature_request_site)
|
||||
binding.aboutPrivacyPolicyTextView.setClickableTextWithoutUnderlines(R.string.about_privacy_policy)
|
||||
|
||||
binding.aboutPrivacyPolicyTextView.setOnClickListener {
|
||||
val intent = Intent(this, PrivacyPolicyActivity::class.java)
|
||||
startActivity(intent)
|
||||
}
|
||||
|
||||
binding.appProfileButton.setOnClickListener {
|
||||
viewUrl(BuildConfig.SUPPORT_ACCOUNT_URL)
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* Copyright 2023 Pachli Association
|
||||
*
|
||||
* This file is a part of Pachli.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* Pachli is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
|
||||
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
|
||||
* Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with Pachli; if not,
|
||||
* see <http://www.gnu.org/licenses>.
|
||||
*/
|
||||
|
||||
package app.pachli
|
||||
|
||||
import android.os.Bundle
|
||||
import android.util.Base64
|
||||
import app.pachli.databinding.ActivityPrivacyPolicyBinding
|
||||
|
||||
class PrivacyPolicyActivity : BaseActivity() {
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
val binding = ActivityPrivacyPolicyBinding.inflate(layoutInflater)
|
||||
setContentView(binding.root)
|
||||
val encoded = Base64.encodeToString(markdownR.html.PRIVACY_md.toByteArray(), Base64.NO_PADDING)
|
||||
binding.policy.loadData(encoded, "text/html", "base64")
|
||||
}
|
||||
}
|
|
@ -21,6 +21,7 @@ import app.pachli.EditProfileActivity
|
|||
import app.pachli.LicenseActivity
|
||||
import app.pachli.ListsActivity
|
||||
import app.pachli.MainActivity
|
||||
import app.pachli.PrivacyPolicyActivity
|
||||
import app.pachli.SplashActivity
|
||||
import app.pachli.StatusListActivity
|
||||
import app.pachli.TabPreferenceActivity
|
||||
|
@ -128,4 +129,7 @@ abstract class ActivitiesModule {
|
|||
|
||||
@ContributesAndroidInjector
|
||||
abstract fun contributesEditFilterActivity(): EditFilterActivity
|
||||
|
||||
@ContributesAndroidInjector
|
||||
abstract fun contributesPrivacyPolicyActivity(): PrivacyPolicyActivity
|
||||
}
|
||||
|
|
|
@ -169,19 +169,18 @@
|
|||
app:layout_constraintStart_toStartOf="@+id/aboutWebsiteInfoTextView"
|
||||
app:layout_constraintTop_toBottomOf="@id/aboutWebsiteInfoTextView" />
|
||||
|
||||
<app.pachli.view.ClickableSpanTextView
|
||||
<TextView
|
||||
android:id="@+id/aboutPrivacyPolicyTextView"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="24dp"
|
||||
android:lineSpacingMultiplier="1.2"
|
||||
android:text="@string/about_privacy_policy"
|
||||
android:textIsSelectable="true"
|
||||
android:textColor="?android:attr/textColorLink"
|
||||
app:layout_constraintEnd_toEndOf="@+id/aboutWebsiteInfoTextView"
|
||||
app:layout_constraintStart_toStartOf="@+id/aboutWebsiteInfoTextView"
|
||||
app:layout_constraintTop_toBottomOf="@id/aboutBugsFeaturesInfoTextView" />
|
||||
|
||||
|
||||
<Button
|
||||
android:id="@+id/appProfileButton"
|
||||
style="@style/AppButton"
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
~ Copyright 2023 Pachli Association
|
||||
~
|
||||
~ This file is a part of Pachli.
|
||||
~
|
||||
~ This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
~ GNU General Public License as published by the Free Software Foundation; either version 3 of the
|
||||
~ License, or (at your option) any later version.
|
||||
~
|
||||
~ Pachli is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
|
||||
~ the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
|
||||
~ Public License for more details.
|
||||
~
|
||||
~ You should have received a copy of the GNU General Public License along with Pachli; if not,
|
||||
~ see <http://www.gnu.org/licenses>.
|
||||
-->
|
||||
|
||||
<WebView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/policy"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" />
|
|
@ -418,7 +418,7 @@
|
|||
* the url can be changed to link to the localized version of the license.
|
||||
-->
|
||||
<string name="about_project_site">Project website: https://pachli.app</string>
|
||||
<string name="about_privacy_policy">Privacy policy: https://github.com/pachli/pachli-android/blob/main/PRIVACY.md</string>
|
||||
<string name="about_privacy_policy">Privacy policy</string>
|
||||
<string name="about_bug_feature_request_site">Bug reports & feature requests:\nhttps://github.com/pachli/pachli-android/issues</string>
|
||||
<string name="about_pachli_account">Pachli\'s Profile</string>
|
||||
|
||||
|
|
|
@ -0,0 +1,56 @@
|
|||
# markdown2resource-plugin
|
||||
|
||||
## Synopsis
|
||||
|
||||
Gradle plugin to convert one or more Markdown files to Java files with static constants where the Markdown has been converted to HTML. Similar (but not quite identical) to Android resources.
|
||||
|
||||
## Example
|
||||
|
||||
In `build.gradle`:
|
||||
|
||||
```groovy
|
||||
// Install the plugin
|
||||
plugins {
|
||||
id "app.pachli.plugins.markdown2resource"
|
||||
}
|
||||
|
||||
// ...
|
||||
|
||||
// Configure the files to be processed
|
||||
markdown2resource {
|
||||
files = [ layout.projectDirectory.file('../PRIVACY.md') ]
|
||||
}
|
||||
```
|
||||
|
||||
In code:
|
||||
|
||||
```kotlin
|
||||
// Assume binding.privacyPolicy references a `WebView`
|
||||
|
||||
// The generated string constant is in the `markdownR.html` package, named
|
||||
// `PRIVACY_md`. To load the content in to a WebView it must be converted to
|
||||
// base64
|
||||
val html = Base64.encodeToString(markdownR.html.PRIVACY_md.toByteArray(), Base64.NO_PADDING)
|
||||
binding.privacyPolicy.loadData(html, "text/html", "base64")
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
The `markdown2resource` block supports the following options.
|
||||
|
||||
`files` - a list of `RegularFile` in Markdown.
|
||||
|
||||
`packageName` - the package name to use for the generated resources. Default is the `android.namespace` of the build variant.
|
||||
|
||||
`resourceClassName` - the outer class name to use for the generated resources. Default is `markdownR`.
|
||||
|
||||
`stringClassName` - the inner class name to use for the generated resources. Default is `html`.
|
||||
|
||||
## Tasks
|
||||
|
||||
The plugin creates N tasks, one for each configured build variant, named `markdown2resource${variant.name.capitalized()}`
|
||||
|
||||
For example:
|
||||
|
||||
1. You have defined `debug` and `release` build types, two tasks will be created, `markdown2resourceDebug` and `markdown2resourceRelease`.
|
||||
2. You have defined `debug` and `release` build types and `demo` and `full` product flavours, four tasks will be created, `markdown2resourceDemoDebug`, `markdown2resourceDemoRelease`, `markdown2resourceFullDebug`, `markdown2resourceFullRelease`.
|
|
@ -0,0 +1,50 @@
|
|||
/*
|
||||
* Copyright 2023 Pachli Association
|
||||
*
|
||||
* This file is a part of Pachli.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* Pachli is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
|
||||
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
|
||||
* Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with Pachli; if not,
|
||||
* see <http://www.gnu.org/licenses>.
|
||||
*/
|
||||
|
||||
@Suppress("DSL_SCOPE_VIOLATION") // TODO: Remove once KTIJ-19369 is fixed
|
||||
plugins {
|
||||
`java-gradle-plugin`
|
||||
`maven-publish`
|
||||
kotlin("jvm") version "1.9.10"
|
||||
}
|
||||
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
group = "app.pachli.plugins"
|
||||
version = "0.0.1"
|
||||
|
||||
gradlePlugin {
|
||||
plugins {
|
||||
create("markdown2resource") {
|
||||
id = "app.pachli.plugins.markdown2resource"
|
||||
implementationClass = "app.pachli.plugins.markdown2resource.Markdown2ResourcePlugin"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation("com.android.tools.build:gradle:8.1.1")
|
||||
implementation("org.jetbrains:markdown:0.5.0")
|
||||
implementation("com.squareup:javapoet:1.12.1")
|
||||
}
|
||||
|
||||
kotlin {
|
||||
jvmToolchain(17)
|
||||
}
|
|
@ -0,0 +1,156 @@
|
|||
/*
|
||||
* Copyright 2023 Pachli Association
|
||||
*
|
||||
* This file is a part of Pachli.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* Pachli is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
|
||||
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
|
||||
* Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with Pachli; if not,
|
||||
* see <http://www.gnu.org/licenses>.
|
||||
*/
|
||||
|
||||
package app.pachli.plugins.markdown2resource
|
||||
|
||||
import com.android.build.gradle.AppExtension
|
||||
import com.squareup.javapoet.FieldSpec
|
||||
import com.squareup.javapoet.JavaFile
|
||||
import com.squareup.javapoet.TypeSpec
|
||||
import org.gradle.api.DefaultTask
|
||||
import org.gradle.api.GradleException
|
||||
import org.gradle.api.Plugin
|
||||
import org.gradle.api.Project
|
||||
import org.gradle.api.file.DirectoryProperty
|
||||
import org.gradle.api.file.RegularFile
|
||||
import org.gradle.api.provider.ListProperty
|
||||
import org.gradle.api.provider.Property
|
||||
import org.gradle.api.tasks.Input
|
||||
import org.gradle.api.tasks.InputFiles
|
||||
import org.gradle.api.tasks.OutputDirectory
|
||||
import org.gradle.api.tasks.TaskAction
|
||||
import org.gradle.configurationcache.extensions.capitalized
|
||||
import org.intellij.markdown.flavours.gfm.GFMFlavourDescriptor
|
||||
import org.intellij.markdown.html.HtmlGenerator
|
||||
import org.intellij.markdown.parser.MarkdownParser
|
||||
import java.io.IOException
|
||||
import javax.lang.model.element.Modifier
|
||||
|
||||
interface Markdown2ResourcePluginExtension {
|
||||
/** List of files */
|
||||
val files: ListProperty<RegularFile>
|
||||
|
||||
/** Class name for the generated resources. Default is "markdownR" */
|
||||
val resourceClassName: Property<String>
|
||||
|
||||
/** Class name for the generated strings. Default is "html". */
|
||||
val stringClassName: Property<String>
|
||||
|
||||
/**
|
||||
* Package name for the generated class. Default is the value of the android.namespace
|
||||
* property.
|
||||
*/
|
||||
val packageName: Property<String>
|
||||
}
|
||||
|
||||
abstract class Markdown2ResourceTask : DefaultTask() {
|
||||
@get:InputFiles
|
||||
abstract val files: ListProperty<RegularFile>
|
||||
|
||||
@get:Input
|
||||
abstract val resourceClassName: Property<String>
|
||||
|
||||
@get:Input
|
||||
abstract val stringClassName: Property<String>
|
||||
|
||||
@get:Input
|
||||
abstract val packageName: Property<String>
|
||||
|
||||
@get:OutputDirectory
|
||||
abstract val outputDir: DirectoryProperty
|
||||
|
||||
@TaskAction
|
||||
fun execute() {
|
||||
logger.info("outputDir: ${outputDir.get()}")
|
||||
|
||||
val resourceClass = createResourceClass(resourceClassName.get())!!
|
||||
val stringClass = createStringClass(stringClassName.get())!!
|
||||
val flavour = GFMFlavourDescriptor()
|
||||
|
||||
files.get().forEach { markdownFile ->
|
||||
logger.info("Processing ${markdownFile.asFile.absoluteFile.name}")
|
||||
|
||||
val f = markdownFile.asFile.readText()
|
||||
val parsedTree = MarkdownParser(flavour).buildMarkdownTreeFromString(f)
|
||||
val html = HtmlGenerator(f, parsedTree, flavour).generateHtml()
|
||||
|
||||
val resourceName = markdownFile.asFile.absoluteFile.name.replace("""[./\\]""".toRegex(), "_", )
|
||||
logger.info(" Resource name: ${resourceClassName.get()}.${stringClassName.get()}.$resourceName")
|
||||
|
||||
stringClass.addField(
|
||||
FieldSpec.builder(String::class.java, resourceName)
|
||||
.addModifiers(Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL)
|
||||
.initializer("\$S", html)
|
||||
.build(),
|
||||
)
|
||||
|
||||
resourceClass.addType(stringClass.build())
|
||||
}
|
||||
|
||||
generateStringResourceFile(packageName.get(), resourceClass)
|
||||
}
|
||||
|
||||
private fun createResourceClass(name: String): TypeSpec.Builder? {
|
||||
return TypeSpec.classBuilder(name).addModifiers(Modifier.PUBLIC, Modifier.FINAL)
|
||||
}
|
||||
|
||||
private fun createStringClass(name: String): TypeSpec.Builder? {
|
||||
return TypeSpec.classBuilder(name).addModifiers(Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL)
|
||||
}
|
||||
|
||||
private fun generateStringResourceFile(packageName: String, classBuilder: TypeSpec.Builder) {
|
||||
val javaFile = JavaFile.builder(packageName, classBuilder.build()).build()
|
||||
try {
|
||||
javaFile.writeTo(outputDir.get().asFile)
|
||||
logger.info(javaFile.toString())
|
||||
} catch (e: IOException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class Markdown2ResourcePlugin : Plugin<Project> {
|
||||
override fun apply(target: Project) {
|
||||
val extension = target.extensions.create(
|
||||
"markdown2resource",
|
||||
Markdown2ResourcePluginExtension::class.java
|
||||
)
|
||||
extension.resourceClassName.convention("markdownR")
|
||||
extension.stringClassName.convention("html")
|
||||
|
||||
target.extensions.findByType(AppExtension::class.java)?.let { appExtension ->
|
||||
appExtension.applicationVariants.all { variant ->
|
||||
val outputDir =
|
||||
target.layout.buildDirectory.dir("generated/source/${variant.name}")
|
||||
val taskName = "markdown2resource${variant.name.capitalized()}"
|
||||
|
||||
extension.packageName.convention(variant.mergeResourcesProvider.get().namespace)
|
||||
|
||||
val task =
|
||||
target.tasks.register(taskName, Markdown2ResourceTask::class.java) { task ->
|
||||
task.files.set(extension.files)
|
||||
task.resourceClassName.set(extension.resourceClassName)
|
||||
task.stringClassName.set(extension.stringClassName)
|
||||
task.packageName.set(extension.packageName)
|
||||
task.outputDir.set(outputDir)
|
||||
}
|
||||
|
||||
variant.registerJavaGeneratingTask(task, outputDir.get().asFile)
|
||||
}
|
||||
} ?: throw GradleException("'android' configuration block not found")
|
||||
}
|
||||
}
|
|
@ -3,6 +3,8 @@ pluginManagement {
|
|||
google()
|
||||
gradlePluginPortal()
|
||||
}
|
||||
|
||||
includeBuild 'plugins/markdown2resource'
|
||||
}
|
||||
|
||||
dependencyResolutionManagement {
|
||||
|
|
Loading…
Reference in New Issue