Added announcements

pull/210/head
M66B 2 years ago
parent 16baa2a336
commit 244f3e58ee

@ -167,6 +167,7 @@ android {
buildConfigField "String", "GITHUB_LATEST_URI", "\"https://github.com/M66B/FairEmail/releases\""
buildConfigField "String", "BITBUCKET_DOWNLOADS_API", "\"https://api.bitbucket.org/2.0/repositories/M66B/fairemail-test/downloads\""
buildConfigField "String", "BITBUCKET_DOWNLOADS_URI", "\"https://bitbucket.org/M66B/fairemail-test/downloads/\""
buildConfigField "String", "ANNOUNCEMENT_URI", "\"https://gist.githubusercontent.com/M66B/d544192ca56224839d6ba0f2f6314c1f/raw/\""
buildConfigField "String", "TX_URI", localProperties.getProperty("paypal.uri", "\"\"")
buildConfigField "String", "GPA_URI", localProperties.getProperty("gpa.uri", "\"\"")
buildConfigField "String", "INFO_URI", localProperties.getProperty("info.uri", "\"\"")
@ -185,6 +186,7 @@ android {
buildConfigField "String", "GITHUB_LATEST_URI", "\"https://github.com/M66B/FairEmail/releases\""
buildConfigField "String", "BITBUCKET_DOWNLOADS_API", "\"https://api.bitbucket.org/2.0/repositories/M66B/fairemail-test/downloads\""
buildConfigField "String", "BITBUCKET_DOWNLOADS_URI", "\"https://bitbucket.org/M66B/fairemail-test/downloads/\""
buildConfigField "String", "ANNOUNCEMENT_URI", "\"https://gist.githubusercontent.com/M66B/d544192ca56224839d6ba0f2f6314c1f/raw/\""
buildConfigField "String", "TX_URI", "\"\""
buildConfigField "String", "GPA_URI", "\"\""
buildConfigField "String", "INFO_URI", "\"\""
@ -204,6 +206,7 @@ android {
buildConfigField "String", "GITHUB_LATEST_URI", "\"\""
buildConfigField "String", "BITBUCKET_DOWNLOADS_API", "\"\""
buildConfigField "String", "BITBUCKET_DOWNLOADS_URI", "\"\""
buildConfigField "String", "ANNOUNCEMENT_URI", "\"\""
buildConfigField "String", "TX_URI", "\"\""
buildConfigField "String", "GPA_URI", "\"\""
buildConfigField "String", "INFO_URI", "\"\""
@ -223,6 +226,7 @@ android {
buildConfigField "String", "GITHUB_LATEST_URI", "\"\""
buildConfigField "String", "BITBUCKET_DOWNLOADS_API", "\"\""
buildConfigField "String", "BITBUCKET_DOWNLOADS_URI", "\"\""
buildConfigField "String", "ANNOUNCEMENT_URI", "\"\""
buildConfigField "String", "TX_URI", "\"\""
buildConfigField "String", "GPA_URI", "\"\""
buildConfigField "String", "INFO_URI", "\"\""

@ -41,6 +41,7 @@ import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Bundle;
import android.text.Spanned;
import android.text.TextUtils;
import android.util.DisplayMetrics;
import android.util.Pair;
@ -94,9 +95,12 @@ import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URL;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
import java.util.concurrent.Callable;
@ -163,8 +167,9 @@ public class ActivityView extends ActivityBilling implements FragmentManager.OnB
static final int PI_THREAD = 3;
static final int PI_OUTBOX = 4;
static final int PI_UPDATE = 5;
static final int PI_WIDGET = 6;
static final int PI_POWER = 7;
static final int PI_ANNOUNCEMENT = 6;
static final int PI_WIDGET = 7;
static final int PI_POWER = 8;
static final String ACTION_VIEW_FOLDERS = BuildConfig.APPLICATION_ID + ".VIEW_FOLDERS";
static final String ACTION_VIEW_MESSAGES = BuildConfig.APPLICATION_ID + ".VIEW_MESSAGES";
@ -183,6 +188,9 @@ public class ActivityView extends ActivityBilling implements FragmentManager.OnB
static final long UPDATE_DAILY = (BuildConfig.BETA_RELEASE ? 4 : 12) * 3600 * 1000L; // milliseconds
static final long UPDATE_WEEKLY = 7 * 24 * 3600 * 1000L; // milliseconds
private static final int ANNOUNCEMENT_TIMEOUT = 15 * 1000; // milliseconds
private static final long ANNOUNCEMENT_INTERVAL = 4 * 3600 * 1000L; // milliseconds
private static final int REQUEST_RULES_ACCOUNT = 2001;
private static final int REQUEST_RULES_FOLDER = 2002;
@ -939,6 +947,7 @@ public class ActivityView extends ActivityBilling implements FragmentManager.OnB
if (!drawerLayout.isLocked(drawerContainer))
drawerLayout.closeDrawer(drawerContainer);
checkUpdate(true);
checkAnnouncements(true);
}
return !play;
}
@ -1090,6 +1099,7 @@ public class ActivityView extends ActivityBilling implements FragmentManager.OnB
owner.start();
checkUpdate(false);
checkAnnouncements(false);
checkIntent();
}
@ -1734,6 +1744,150 @@ public class ActivityView extends ActivityBilling implements FragmentManager.OnB
}.execute(this, args, "update:check");
}
private void checkAnnouncements(boolean always) {
if (TextUtils.isEmpty(BuildConfig.ANNOUNCEMENT_URI))
return;
long now = new Date().getTime();
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
boolean announcements = prefs.getBoolean("announcements", true);
long last_announcement_check = prefs.getLong("last_announcement_check", 0);
if (!always && !announcements)
return;
if (!always && last_announcement_check + ANNOUNCEMENT_INTERVAL > now)
return;
prefs.edit().putLong("last_announcement_check", now).apply();
Bundle args = new Bundle();
args.putBoolean("always", always);
new SimpleTask<List<Announcement>>() {
@Override
protected List<Announcement> onExecute(Context context, Bundle args) throws Throwable {
StringBuilder response = new StringBuilder();
HttpsURLConnection urlConnection = null;
try {
URL latest = new URL(BuildConfig.ANNOUNCEMENT_URI);
urlConnection = (HttpsURLConnection) latest.openConnection();
urlConnection.setRequestMethod("GET");
urlConnection.setReadTimeout(ANNOUNCEMENT_TIMEOUT);
urlConnection.setConnectTimeout(ANNOUNCEMENT_TIMEOUT);
urlConnection.setDoOutput(false);
ConnectionHelper.setUserAgent(context, urlConnection);
urlConnection.connect();
int status = urlConnection.getResponseCode();
InputStream inputStream = (status == HttpsURLConnection.HTTP_OK
? urlConnection.getInputStream() : urlConnection.getErrorStream());
if (inputStream != null) {
BufferedReader br = new BufferedReader(new InputStreamReader(inputStream));
String line;
while ((line = br.readLine()) != null)
response.append(line);
}
if (status != HttpsURLConnection.HTTP_OK)
throw new IOException("HTTP " + status + ": " + response);
DateFormat DTF = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ", Locale.US);
List<Announcement> announcements = new ArrayList<>();
JSONObject jroot = new JSONObject(response.toString());
JSONArray jannouncements = jroot.getJSONArray("Announcements");
for (int i = 0; i < jannouncements.length(); i++) {
JSONObject jannouncement = jannouncements.getJSONObject(i);
String language = Locale.getDefault().getLanguage();
String title = jannouncement.optString("Title." + language);
if (TextUtils.isEmpty(title))
title = jannouncement.getString("Title");
String text = jannouncement.optString("Text." + language);
if (TextUtils.isEmpty(text))
text = jannouncement.getString("Text");
Announcement announcement = new Announcement();
announcement.id = jannouncement.getInt("ID");
announcement.test = jannouncement.optBoolean("Test");
announcement.title = title;
announcement.text = HtmlHelper.fromHtml(text, context);
if (jannouncement.has("Link"))
announcement.link = Uri.parse(jannouncement.getString("Link"));
announcement.expires = DTF.parse(jannouncement.getString("Expires")
.replace("Z", "+00:00"));
announcements.add(announcement);
}
return announcements;
} finally {
if (urlConnection != null)
urlConnection.disconnect();
}
}
@Override
protected void onExecuted(Bundle args, List<Announcement> announcements) {
boolean always = args.getBoolean("always");
NotificationManager nm =
Helper.getSystemService(ActivityView.this, NotificationManager.class);
if (!NotificationHelper.areNotificationsEnabled(nm))
return;
SharedPreferences.Editor editor = prefs.edit();
for (Announcement announcement : announcements) {
String key = "announcement." + announcement.id;
if (announcement.isExpired()) {
editor.remove(key);
nm.cancel(announcement.id);
} else {
boolean notified = prefs.getBoolean(key, false);
if (notified && !always)
continue;
editor.putBoolean(key, true);
NotificationCompat.Builder builder =
new NotificationCompat.Builder(ActivityView.this, "announcements")
.setSmallIcon(R.drawable.baseline_warning_white_24)
.setContentTitle(announcement.title)
.setContentText(announcement.text)
.setAutoCancel(true)
.setShowWhen(true)
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
.setCategory(NotificationCompat.CATEGORY_REMINDER)
.setVisibility(NotificationCompat.VISIBILITY_SECRET);
if (announcement.link != null) {
Intent update = new Intent(Intent.ACTION_VIEW, announcement.link)
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
PendingIntent piUpdate = PendingIntentCompat.getActivity(
ActivityView.this, PI_ANNOUNCEMENT, update, PendingIntent.FLAG_UPDATE_CURRENT);
builder.setContentIntent(piUpdate);
}
nm.notify(announcement.id, builder.build());
}
}
editor.apply();
}
@Override
protected void onException(Bundle args, Throwable ex) {
if (args.getBoolean("always"))
Log.unexpectedError(getSupportFragmentManager(), ex);
}
}.execute(this, args, "announcements:check");
}
private void checkIntent() {
Intent intent = getIntent();
Log.i("View intent=" + intent +
@ -2288,6 +2442,23 @@ public class ActivityView extends ActivityBilling implements FragmentManager.OnB
String download_url;
}
private class Announcement {
int id;
boolean test;
String title;
Spanned text;
Uri link;
Date expires;
boolean isExpired() {
if (this.test && !BuildConfig.DEBUG)
return true;
if (expires == null)
return true;
return (expires.getTime() < new Date().getTime());
}
}
public static class FragmentDialogFirst extends FragmentDialogBase {
@NonNull
@Override

@ -147,6 +147,8 @@ public class FragmentOptionsMisc extends FragmentBase implements SharedPreferenc
private SwitchCompat swBeta;
private TextView tvBitBucketPrivacy;
private SwitchCompat swChangelog;
private SwitchCompat swAnnouncements;
private TextView tvAnnouncementsPrivacy;
private SwitchCompat swCrashReports;
private TextView tvUuid;
private Button btnReset;
@ -238,6 +240,7 @@ public class FragmentOptionsMisc extends FragmentBase implements SharedPreferenc
private Group grpSend;
private Group grpUpdates;
private Group grpBitbucket;
private Group grpAnnouncements;
private Group grpTest;
private CardView cardDebug;
@ -254,7 +257,7 @@ public class FragmentOptionsMisc extends FragmentBase implements SharedPreferenc
"deepl_enabled",
"vt_enabled", "vt_apikey",
"send_enabled", "send_host",
"updates", "weekly", "beta", "show_changelog",
"updates", "weekly", "beta", "show_changelog", "announcements",
"crash_reports", "cleanup_attachments",
"watchdog", "experiments", "main_log", "main_log_memory", "protocol", "log_level", "debug", "leak_canary",
"test1", "test2", "test3", "test4", "test5",
@ -291,7 +294,8 @@ public class FragmentOptionsMisc extends FragmentBase implements SharedPreferenc
"gmail_checked", "outlook_checked",
"redmi_note",
"accept_space", "accept_unsupported",
"junk_hint"
"junk_hint",
"last_update_check", "last_announcement_check"
};
@Override
@ -361,6 +365,8 @@ public class FragmentOptionsMisc extends FragmentBase implements SharedPreferenc
swBeta = view.findViewById(R.id.swBeta);
tvBitBucketPrivacy = view.findViewById(R.id.tvBitBucketPrivacy);
swChangelog = view.findViewById(R.id.swChangelog);
swAnnouncements = view.findViewById(R.id.swAnnouncements);
tvAnnouncementsPrivacy = view.findViewById(R.id.tvAnnouncementsPrivacy);
swCrashReports = view.findViewById(R.id.swCrashReports);
tvUuid = view.findViewById(R.id.tvUuid);
btnReset = view.findViewById(R.id.btnReset);
@ -452,6 +458,7 @@ public class FragmentOptionsMisc extends FragmentBase implements SharedPreferenc
grpSend = view.findViewById(R.id.grpSend);
grpUpdates = view.findViewById(R.id.grpUpdates);
grpBitbucket = view.findViewById(R.id.grpBitbucket);
grpAnnouncements = view.findViewById(R.id.grpAnnouncements);
grpTest = view.findViewById(R.id.grpTest);
cardDebug = view.findViewById(R.id.cardDebug);
@ -914,6 +921,21 @@ public class FragmentOptionsMisc extends FragmentBase implements SharedPreferenc
}
});
swAnnouncements.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton compoundButton, boolean checked) {
prefs.edit().putBoolean("announcements", checked).apply();
}
});
tvAnnouncementsPrivacy.getPaint().setUnderlineText(true);
tvAnnouncementsPrivacy.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Helper.view(v.getContext(), Uri.parse(Helper.GITHUB_PRIVACY_URI), true);
}
});
swCrashReports.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton compoundButton, boolean checked) {
@ -1890,6 +1912,9 @@ public class FragmentOptionsMisc extends FragmentBase implements SharedPreferenc
(Helper.isPlayStoreInstall() || !Helper.hasValidFingerprint(getContext()))
? View.GONE : View.VISIBLE);
grpBitbucket.setVisibility(View.GONE);
grpAnnouncements.setVisibility(!BuildConfig.DEBUG &&
(Helper.isPlayStoreInstall() || !Helper.hasValidFingerprint(getContext()))
? View.GONE : View.VISIBLE);
grpTest.setVisibility(BuildConfig.TEST_RELEASE ? View.VISIBLE : View.GONE);
setLastCleanup(prefs.getLong("last_cleanup", -1));
@ -2021,12 +2046,16 @@ public class FragmentOptionsMisc extends FragmentBase implements SharedPreferenc
SharedPreferences.Editor editor = prefs.edit();
if (cbGeneral.isChecked())
for (String option : RESET_QUESTIONS)
editor.remove(option);
for (String key : RESET_QUESTIONS)
if (prefs.contains(key)) {
Log.i("Removing option=" + key);
editor.remove(key);
}
for (String key : prefs.getAll().keySet())
if ((!BuildConfig.DEBUG &&
key.startsWith("translated_") && cbGeneral.isChecked()) ||
(key.startsWith("announcement.") && cbGeneral.isChecked()) ||
(key.endsWith(".show_full") && cbFull.isChecked()) ||
(key.endsWith(".show_images") && cbImages.isChecked()) ||
(key.endsWith(".confirm_link") && cbLinks.isChecked())) {
@ -2145,6 +2174,7 @@ public class FragmentOptionsMisc extends FragmentBase implements SharedPreferenc
swBeta.setChecked(prefs.getBoolean("beta", false));
swBeta.setEnabled(swUpdates.isChecked());
swChangelog.setChecked(prefs.getBoolean("show_changelog", !BuildConfig.PLAY_STORE_RELEASE));
swAnnouncements.setChecked(prefs.getBoolean("announcements", true));
swExperiments.setChecked(prefs.getBoolean("experiments", false));
swCrashReports.setChecked(prefs.getBoolean("crash_reports", false));
tvUuid.setText(prefs.getString("uuid", null));

@ -106,14 +106,22 @@ class NotificationHelper {
progress.setLockscreenVisibility(Notification.VISIBILITY_PRIVATE);
nm.createNotificationChannel(progress);
// Update
if (!Helper.isPlayStoreInstall()) {
// Update
NotificationChannel update = new NotificationChannel(
"update", context.getString(R.string.channel_update),
NotificationManager.IMPORTANCE_HIGH);
update.setSound(null, Notification.AUDIO_ATTRIBUTES_DEFAULT);
update.setLockscreenVisibility(Notification.VISIBILITY_PRIVATE);
nm.createNotificationChannel(update);
// Announcements
NotificationChannel announcements = new NotificationChannel(
"announcements", context.getString(R.string.channel_announcements),
NotificationManager.IMPORTANCE_HIGH);
announcements.setSound(null, Notification.AUDIO_ATTRIBUTES_DEFAULT);
announcements.setLockscreenVisibility(Notification.VISIBILITY_PRIVATE);
nm.createNotificationChannel(announcements);
}
// Warnings

@ -653,6 +653,33 @@
app:layout_constraintTop_toBottomOf="@id/tvBitBucketPrivacy"
app:switchPadding="12dp" />
<androidx.appcompat.widget.SwitchCompat
android:id="@+id/swAnnouncements"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:checked="true"
android:text="@string/title_advanced_announcements"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/swChangelog"
app:switchPadding="12dp" />
<TextView
android:id="@+id/tvAnnouncementsPrivacy"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
android:layout_marginTop="12dp"
android:drawableEnd="@drawable/twotone_open_in_new_12"
android:drawablePadding="6dp"
android:text="@string/title_privacy_policy"
android:textAppearance="@style/TextAppearance.AppCompat.Small"
android:textColor="?android:attr/textColorLink"
app:drawableTint="?android:attr/textColorLink"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/swAnnouncements" />
<androidx.appcompat.widget.SwitchCompat
android:id="@+id/swCrashReports"
android:layout_width="0dp"
@ -661,7 +688,7 @@
android:text="@string/title_advanced_crash_reports"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/swChangelog"
app:layout_constraintTop_toBottomOf="@id/tvAnnouncementsPrivacy"
app:switchPadding="12dp" />
<TextView
@ -785,6 +812,12 @@
android:layout_width="0dp"
android:layout_height="0dp"
app:constraint_referenced_ids="swBeta,tvBitBucketPrivacy" />
<androidx.constraintlayout.widget.Group
android:id="@+id/grpAnnouncements"
android:layout_width="0dp"
android:layout_height="0dp"
app:constraint_referenced_ids="swAnnouncements,tvAnnouncementsPrivacy" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.cardview.widget.CardView>

@ -22,6 +22,7 @@
<string name="channel_notification">Email</string>
<string name="channel_progress">Progress</string>
<string name="channel_update">Updates</string>
<string name="channel_announcements">Announcements</string>
<string name="channel_warning">Warnings</string>
<string name="channel_error">Errors</string>
<string name="channel_alert">Server alerts</string>
@ -779,6 +780,7 @@
<string name="title_advanced_check_weekly">Check weekly instead of daily</string>
<string name="title_advanced_beta">Check for test versions on BitBucket</string>
<string name="title_advanced_changelog">Show changelog after update</string>
<string name="title_advanced_announcements">Check for announcements</string>
<string name="title_advanced_experiments">Try experimental features</string>
<string name="title_advanced_crash_reports">Send error reports</string>
<string name="title_advanced_canary" translatable="false">Leak canary</string>

Loading…
Cancel
Save