Skip to content

Commit

Permalink
embed history/privacy policy/license in app (#1346)
Browse files Browse the repository at this point in the history
  • Loading branch information
obfusk authored Aug 29, 2023
1 parent 68a638e commit 8ba860f
Show file tree
Hide file tree
Showing 8 changed files with 875 additions and 15 deletions.
18 changes: 18 additions & 0 deletions PRIVACY.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
**Last updated**
August 30 2023

# Privacy Policy
Catima does not collect or transmit any personal information.

To ensure correct app functionality, we require access to the following:

- Camera: We need access to your camera to be able to scan barcodes. The app can still be used when camera access is denied, but you will have to manually type the barcode information.
- Storage (Android 5 and 6 only): We need access to your device storage to create or import backups. The app can still be used when storage access is denied, but you will not be able to create or import backups.

Catima offers a feature to share cards with other users. All the relevant data is in the generated shareable URLs and never transmitted to our servers. When viewed through catima.app, the data in the URL is rendered using client-side Javascript to further ensure no data is ever transmitted to us.

# Changes
This Privacy Policy may be updated from time to time for any reason. We will notify you of any changes to our Privacy Policy by posting the new Privacy Policy to https://catima.app/privacy-policy/. A snapshot of the Privacy Policy is available within the Catima app, though it may be outdated. When the Privacy Policy on the website and in the app differ, the website should be considered leading. You are advised to consult the Privacy Policy regularly for any changes, as continued use is deemed approval of all changes.

# Contact us
If you have any questions regarding privacy while using the Application, or have questions about our practices, please contact us via email at [email protected].
15 changes: 15 additions & 0 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -132,3 +132,18 @@ tasks.withType(SpotBugsTask) {
html.enabled = true
}
}

tasks.register('copyRawResFiles', Copy) {
from layout.projectDirectory.file("../CHANGELOG.md"),
layout.projectDirectory.file("../PRIVACY.md")
into layout.projectDirectory.dir("src/main/res/raw")
rename { String fileName -> fileName.toLowerCase() }
}

project.afterEvaluate {
tasks.each { task ->
if (task != copyRawResFiles) {
task.dependsOn(copyRawResFiles)
}
}
}
61 changes: 47 additions & 14 deletions app/src/main/java/protect/card_locker/AboutActivity.java
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
package protect.card_locker;

import android.os.Bundle;
import android.text.Spanned;
import android.view.MenuItem;
import android.view.View;
import android.widget.ScrollView;
import android.widget.TextView;

import androidx.annotation.StringRes;

import com.google.android.material.dialog.MaterialAlertDialogBuilder;


Expand Down Expand Up @@ -68,20 +72,14 @@ protected void onDestroy() {
}

private void bindClickListeners() {
View.OnClickListener openExternalBrowser = view -> {
Object tag = view.getTag();
if (tag instanceof String && ((String) tag).startsWith("https://")) {
(new OpenWebLinkHandler()).openBrowser(this, (String) tag);
}
};
binding.versionHistory.setOnClickListener(openExternalBrowser);
binding.translate.setOnClickListener(openExternalBrowser);
binding.license.setOnClickListener(openExternalBrowser);
binding.repo.setOnClickListener(openExternalBrowser);
binding.privacy.setOnClickListener(openExternalBrowser);
binding.reportError.setOnClickListener(openExternalBrowser);
binding.rate.setOnClickListener(openExternalBrowser);
binding.donate.setOnClickListener(openExternalBrowser);
binding.versionHistory.setOnClickListener(this::showHistory);
binding.translate.setOnClickListener(this::openExternalBrowser);
binding.license.setOnClickListener(this::showLicense);
binding.repo.setOnClickListener(this::openExternalBrowser);
binding.privacy.setOnClickListener(this::showPrivacy);
binding.reportError.setOnClickListener(this::openExternalBrowser);
binding.rate.setOnClickListener(this::openExternalBrowser);
binding.donate.setOnClickListener(this::openExternalBrowser);

binding.credits.setOnClickListener(view -> showCredits());
}
Expand All @@ -106,4 +104,39 @@ private void showCredits() {
.setPositiveButton(R.string.ok, null)
.show();
}

private void showHistory(View view) {
showHTML(R.string.version_history, content.getHistoryInfo(), view);
}

private void showLicense(View view) {
showHTML(R.string.license, content.getLicenseInfo(), view);
}

private void showPrivacy(View view) {
showHTML(R.string.privacy_policy, content.getPrivacyInfo(), view);
}

private void showHTML(@StringRes int title, final Spanned text, View view) {
int dialogContentPadding = getResources().getDimensionPixelSize(R.dimen.alert_dialog_content_padding);
TextView textView = new TextView(this);
textView.setText(text);
Utils.makeTextViewLinksClickable(textView, text);
ScrollView scrollView = new ScrollView(this);
scrollView.addView(textView);
scrollView.setPadding(dialogContentPadding, dialogContentPadding / 2, dialogContentPadding, 0);
new MaterialAlertDialogBuilder(this)
.setTitle(title)
.setView(scrollView)
.setPositiveButton(R.string.ok, null)
.setNeutralButton(R.string.view_online, (dialog, which) -> openExternalBrowser(view))
.show();
}

private void openExternalBrowser(View view) {
Object tag = view.getTag();
if (tag instanceof String && ((String) tag).startsWith("https://")) {
(new OpenWebLinkHandler()).openBrowser(this, (String) tag);
}
}
}
45 changes: 45 additions & 0 deletions app/src/main/java/protect/card_locker/AboutContent.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.text.Spanned;
import android.util.Log;

import androidx.core.text.HtmlCompat;
Expand Down Expand Up @@ -64,6 +65,38 @@ public String getContributors() {
return contributors.replace("\n", "<br />");
}

public String getHistory() {
String versionHistory;
try {
versionHistory = Utils.readTextFile(context, R.raw.changelog)
.replace("# Changelog\n\n", "");
} catch (IOException ignored) {
return "";
}
return Utils.linkify(Utils.basicMDToHTML(versionHistory))
.replace("\n", "<br />");
}

public String getLicense() {
try {
return Utils.readTextFile(context, R.raw.license);
} catch (IOException ignored) {
return "";
}
}

public String getPrivacy() {
String privacyPolicy;
try {
privacyPolicy = Utils.readTextFile(context, R.raw.privacy)
.replace("# Privacy Policy\n", "");
} catch (IOException ignored) {
return "";
}
return Utils.linkify(Utils.basicMDToHTML(privacyPolicy))
.replace("\n", "<br />");
}

public String getThirdPartyLibraries() {
final List<ThirdPartyInfo> usedLibraries = new ArrayList<>();
usedLibraries.add(new ThirdPartyInfo("Color Picker", "https://github.com/jaredrummler/ColorPicker", "Apache 2.0"));
Expand Down Expand Up @@ -111,6 +144,18 @@ public String getContributorInfo() {
return contributorInfo.toString();
}

public Spanned getHistoryInfo() {
return HtmlCompat.fromHtml(getHistory(), HtmlCompat.FROM_HTML_MODE_COMPACT);
}

public Spanned getLicenseInfo() {
return HtmlCompat.fromHtml(getLicense(), HtmlCompat.FROM_HTML_MODE_LEGACY);
}

public Spanned getPrivacyInfo() {
return HtmlCompat.fromHtml(getPrivacy(), HtmlCompat.FROM_HTML_MODE_COMPACT);
}

public String getVersionHistory() {
return String.format(context.getString(R.string.debug_version_fmt), getAppVersion());
}
Expand Down
52 changes: 51 additions & 1 deletion app/src/main/java/protect/card_locker/Utils.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package protect.card_locker;

import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
Expand All @@ -14,8 +15,12 @@
import android.net.Uri;
import android.os.Build;
import android.provider.MediaStore;
import android.text.Layout;
import android.text.Spanned;
import android.text.style.ClickableSpan;
import android.util.Log;
import android.util.TypedValue;
import android.view.MotionEvent;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
Expand Down Expand Up @@ -688,7 +693,7 @@ public static String readTextFile(Context context, @RawRes int resourceId) throw
while (true) {
String nextLine = reader.readLine();

if (nextLine == null || nextLine.isEmpty()) {
if (nextLine == null) {
reader.close();
break;
}
Expand All @@ -700,6 +705,28 @@ public static String readTextFile(Context context, @RawRes int resourceId) throw
return result.toString();
}

// Very crude Markdown to HTML conversion.
// Only supports what's currently being used in CHANGELOG.md and PRIVACY.md.
// May break easily.
public static String basicMDToHTML(final String input) {
return input
.replaceAll("(?m)^#\\s+(.*)", "<h1>$1</h1>")
.replaceAll("(?m)^##\\s+(.*)", "<h2>$1</h2>")
.replaceAll("\\[([^]]+)\\]\\((https?://[\\w@#%&+=:?/.-]+)\\)", "<a href=\"$2\">$1</a>")
.replaceAll("\\*\\*([^*]+)\\*\\*", "<b>$1</b>")
.replaceAll("(?m)^-\\s+(.*)", "<ul><li>&nbsp;$1</li></ul>")
.replace("</ul>\n<ul>", "");
}

// Very crude autolinking.
// Only supports what's currently being used in CHANGELOG.md and PRIVACY.md.
// May break easily.
public static String linkify(final String input) {
return input
.replaceAll("([\\w.-]+@[\\w-]+(\\.[\\w-]+)+)", "<a href=\"mailto:$1\">$1</a>")
.replaceAll("(?<!href=\")\\b(https?://[\\w@#%&+=:?/.-]*[\\w@#%&+=:?/-])", "<a href=\"$1\">$1</a>");
}

public static void setIconOrTextWithBackground(Context context, LoyaltyCard loyaltyCard, Bitmap icon, ImageView backgroundOrIcon, TextView textWhenNoImage) {
if (icon != null) {
Log.d("onResume", "setting icon image");
Expand Down Expand Up @@ -764,4 +791,27 @@ public static boolean equals(final Object a, final Object b) {
}
return a.equals(b);
}

@SuppressLint("ClickableViewAccessibility")
public static void makeTextViewLinksClickable(final TextView textView, final Spanned text) {
textView.setOnTouchListener((v, event) -> {
int action = event.getAction();
if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_DOWN) {
int x = (int) event.getX() - textView.getTotalPaddingLeft() + textView.getScrollX();
int y = (int) event.getY() - textView.getTotalPaddingTop() + textView.getScrollY();
Layout layout = textView.getLayout();
int line = layout.getLineForVertical(y);
int off = layout.getOffsetForHorizontal(line, x);
ClickableSpan[] links = text.getSpans(off, off, ClickableSpan.class);
if (links.length != 0) {
ClickableSpan link = links[0];
if (action == MotionEvent.ACTION_UP) {
link.onClick(textView);
}
return true;
}
}
return false;
});
}
}
2 changes: 2 additions & 0 deletions app/src/main/res/raw/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
changelog.md
privacy.md
Loading

0 comments on commit 8ba860f

Please sign in to comment.