Renames the app (label, texts, User-Agent, PRODID, APK) and uses the new launcher icon pack (ldpi to xhdpi, master in art/) instead of drawing one at build time. Reminders show a white calendar glyph in the status bar (stat_notify, 18-48 px) instead of the system's colour alarm clock, which didn't fit the Gingerbread bar. The package name, database and signing key stay, so it installs over GingerCal and keeps its data. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
263 lines
11 KiB
Java
263 lines
11 KiB
Java
package org.gingercal;
|
|
|
|
import java.util.List;
|
|
|
|
import android.app.Activity;
|
|
import android.app.AlertDialog;
|
|
import android.app.ProgressDialog;
|
|
import android.content.DialogInterface;
|
|
import android.content.Intent;
|
|
import android.os.AsyncTask;
|
|
import android.os.Bundle;
|
|
import android.view.View;
|
|
import android.widget.Button;
|
|
import android.widget.EditText;
|
|
import android.widget.LinearLayout;
|
|
import android.widget.ScrollView;
|
|
import android.widget.TextView;
|
|
|
|
import org.gingercal.Model.Cal;
|
|
|
|
/** Account, TLS pin, sync interval and reminder settings. */
|
|
public class SettingsActivity extends Activity {
|
|
EditText url, user, pass, pin, ahead;
|
|
Button intervalBtn, remindBtn, langBtn;
|
|
int interval, remind;
|
|
|
|
@Override
|
|
protected void onCreate(Bundle b) {
|
|
super.onCreate(b);
|
|
setTitle(L.t("Calendar Lite settings"));
|
|
LinearLayout f = App.vbox(this);
|
|
int p = App.dp(this, 8);
|
|
f.setPadding(p, p, p, p);
|
|
f.addView(App.text(this, L.t("Nextcloud server: your normal https:// address. This app brings its own modern TLS, so no bridge is needed, at home or away. Use an app password, not your real one."), 12, 0xFFB0BEC5));
|
|
url = field(f, L.t("Server address"), "https://cloud.example.com", 1 | 0x10 /* URI */);
|
|
user = field(f, L.t("User name"), "", 1 | 0x80000 /* no suggestions */);
|
|
pass = field(f, L.t("App password"), "", 1 | 0x80 /* password */);
|
|
pin = field(f, L.t("Pinned certificate SHA-256 (filled in for you)"), L.t("optional"), 1 | 0x80000);
|
|
ahead = field(f, L.t("Days ahead to sync"), "60", 2 /* number */);
|
|
intervalBtn = new Button(this);
|
|
f.addView(intervalBtn, new LinearLayout.LayoutParams(-1, -2));
|
|
remindBtn = new Button(this);
|
|
f.addView(remindBtn, new LinearLayout.LayoutParams(-1, -2));
|
|
langBtn = new Button(this);
|
|
f.addView(langBtn, new LinearLayout.LayoutParams(-1, -2));
|
|
Button test = new Button(this);
|
|
test.setText(L.t("Save and test connection"));
|
|
f.addView(test, new LinearLayout.LayoutParams(-1, -2));
|
|
Button resync = new Button(this);
|
|
resync.setText(L.t("Re-download everything"));
|
|
f.addView(resync, new LinearLayout.LayoutParams(-1, -2));
|
|
ScrollView sv = new ScrollView(this);
|
|
sv.addView(f);
|
|
setContentView(sv);
|
|
|
|
url.setText(App.get(this, "url"));
|
|
user.setText(App.get(this, "user"));
|
|
pass.setText(App.get(this, "pass"));
|
|
pin.setText(App.get(this, "pin"));
|
|
ahead.setText(String.valueOf(App.ahead(this)));
|
|
interval = App.interval(this);
|
|
remind = App.remind(this);
|
|
labels();
|
|
|
|
intervalBtn.setOnClickListener(new View.OnClickListener() {
|
|
public void onClick(View v) {
|
|
final String[] l = new String[App.INTERVALS.length];
|
|
for (int i = 0; i < l.length; i++) l[i] = intervalLabel(App.INTERVALS[i]);
|
|
new AlertDialog.Builder(SettingsActivity.this).setTitle(L.t("Background sync"))
|
|
.setItems(l, new DialogInterface.OnClickListener() {
|
|
public void onClick(DialogInterface d, int w) {
|
|
interval = App.INTERVALS[w];
|
|
labels();
|
|
}
|
|
}).show();
|
|
}
|
|
});
|
|
remindBtn.setOnClickListener(new View.OnClickListener() {
|
|
public void onClick(View v) {
|
|
final int[] opts = {-1, 0, 5, 10, 15, 30, 60};
|
|
String[] l = new String[opts.length];
|
|
for (int i = 0; i < l.length; i++) l[i] = opts[i] < 0 ? L.t("Off") : App.alarmLabel(opts[i]);
|
|
new AlertDialog.Builder(SettingsActivity.this).setTitle(L.t("Phone notification"))
|
|
.setItems(l, new DialogInterface.OnClickListener() {
|
|
public void onClick(DialogInterface d, int w) {
|
|
remind = opts[w];
|
|
labels();
|
|
}
|
|
}).show();
|
|
}
|
|
});
|
|
langBtn.setOnClickListener(new View.OnClickListener() {
|
|
public void onClick(View v) {
|
|
new AlertDialog.Builder(SettingsActivity.this).setTitle("Lingua / Language")
|
|
.setItems(new String[] {"Galego", "English"}, new DialogInterface.OnClickListener() {
|
|
public void onClick(DialogInterface d, int w) {
|
|
boolean toGl = w == 0;
|
|
if (toGl == L.gl) return;
|
|
save();
|
|
App.prefs(SettingsActivity.this).edit().putString("lang", toGl ? "gl" : "en").commit();
|
|
L.gl = toGl;
|
|
GingerApp.tlsTexts();
|
|
finish(); // reopen so every label is redrawn in the new language
|
|
startActivity(new Intent(SettingsActivity.this, SettingsActivity.class));
|
|
}
|
|
}).show();
|
|
}
|
|
});
|
|
test.setOnClickListener(new View.OnClickListener() {
|
|
public void onClick(View v) {
|
|
confirmAndTest();
|
|
}
|
|
});
|
|
resync.setOnClickListener(new View.OnClickListener() {
|
|
public void onClick(View v) {
|
|
save();
|
|
Db.get(SettingsActivity.this).forgetSyncState();
|
|
App.requestSync(SettingsActivity.this, true, true);
|
|
App.toast(SettingsActivity.this, L.t("Re-downloading in the background"));
|
|
finish();
|
|
}
|
|
});
|
|
}
|
|
|
|
EditText field(LinearLayout f, String label, String hint, int type) {
|
|
TextView l = App.text(this, label, 13, 0xFFFFFFFF);
|
|
l.setPadding(App.dp(this, 4), App.dp(this, 8), 0, 0);
|
|
f.addView(l);
|
|
EditText e = new EditText(this);
|
|
e.setHint(hint);
|
|
e.setSingleLine(true);
|
|
e.setInputType(type);
|
|
f.addView(e, new LinearLayout.LayoutParams(-1, -2));
|
|
return e;
|
|
}
|
|
|
|
static String intervalLabel(int h) {
|
|
return h == 0 ? L.t("Manual only") : h == 1 ? L.t("Every hour") : L.f("Every %d hours", h);
|
|
}
|
|
|
|
void labels() {
|
|
intervalBtn.setText(L.f("Background sync: %s", intervalLabel(interval)));
|
|
remindBtn.setText(L.f("Notification: %s", remind < 0 ? L.t("off") : App.alarmLabel(remind)));
|
|
langBtn.setText(L.f("Language: %s", L.gl ? "Galego" : "English"));
|
|
}
|
|
|
|
static String txt(TextView t) {
|
|
return t.getText().toString().trim();
|
|
}
|
|
|
|
@Override
|
|
protected void onPause() {
|
|
super.onPause();
|
|
if (txt(url).length() > 0) save();
|
|
}
|
|
|
|
void save() {
|
|
String oldAccount = App.get(this, "url") + "|" + App.get(this, "user");
|
|
int days = 60;
|
|
try {
|
|
days = Math.max(7, Math.min(365, Integer.parseInt(txt(ahead))));
|
|
} catch (NumberFormatException ignored) {
|
|
}
|
|
String u = txt(url);
|
|
if (u.length() > 0) u = Dav.normalize(u);
|
|
App.prefs(this).edit()
|
|
.putString("url", u)
|
|
.putString("user", txt(user))
|
|
.putString("pass", pass.getText().toString())
|
|
.putString("pin", Tls.normPin(txt(pin)).length() == 64 ? txt(pin) : "")
|
|
.putInt("ahead", days)
|
|
.putInt("interval", interval)
|
|
.putInt("remind", remind)
|
|
.commit();
|
|
if (!oldAccount.equals(u + "|" + txt(user))) {
|
|
Db.get(this).clearAll();
|
|
App.prefs(this).edit().putLong("lastsync", 0).putLong("win_from", 0).putLong("win_to", 0)
|
|
.putString("status", "").commit();
|
|
}
|
|
App.scheduleSync(this);
|
|
Reminders.schedule(this);
|
|
}
|
|
|
|
void confirmAndTest() {
|
|
String u = txt(url);
|
|
if (u.length() == 0 || txt(user).length() == 0) {
|
|
App.toast(this, L.t("Server address and user name are required."));
|
|
return;
|
|
}
|
|
if (Dav.normalize(u).startsWith("http://") && !App.isLan(u)) {
|
|
new AlertDialog.Builder(this).setTitle(L.t("Unencrypted connection"))
|
|
.setMessage(L.t("http:// sends your password in clear text over the internet. Use the https:// address instead, or only use http:// to a LAN address."))
|
|
.setPositiveButton(L.t("Use anyway"), new DialogInterface.OnClickListener() {
|
|
public void onClick(DialogInterface d, int w) {
|
|
save();
|
|
new TestTask().execute();
|
|
}
|
|
})
|
|
.setNegativeButton(L.t("Cancel"), null).show();
|
|
return;
|
|
}
|
|
save();
|
|
new TestTask().execute();
|
|
}
|
|
|
|
class TestTask extends AsyncTask<Void, Void, Object> {
|
|
ProgressDialog pd;
|
|
|
|
@Override
|
|
protected void onPreExecute() {
|
|
pd = ProgressDialog.show(SettingsActivity.this, null, L.t("Connecting\u2026"));
|
|
}
|
|
|
|
@Override
|
|
protected Object doInBackground(Void... v) {
|
|
Dav d = null;
|
|
try {
|
|
d = App.dav(SettingsActivity.this);
|
|
return d.calendars(d.calendarHome());
|
|
} catch (Throwable t) {
|
|
return t;
|
|
} finally {
|
|
if (d != null) d.close();
|
|
}
|
|
}
|
|
|
|
@SuppressWarnings("unchecked")
|
|
@Override
|
|
protected void onPostExecute(Object r) {
|
|
try {
|
|
pd.dismiss();
|
|
} catch (RuntimeException ignored) {
|
|
}
|
|
if (r instanceof DavException.UntrustedCert) {
|
|
App.askTrust(SettingsActivity.this, (DavException.UntrustedCert) r, new App.Trusted() {
|
|
public void trusted(String fp) {
|
|
pin.setText(fp);
|
|
save();
|
|
new TestTask().execute();
|
|
}
|
|
});
|
|
return;
|
|
}
|
|
if (r instanceof Throwable) {
|
|
new AlertDialog.Builder(SettingsActivity.this).setTitle(L.t("Connection failed"))
|
|
.setMessage(App.describe((Throwable) r)).setPositiveButton(L.t("OK"), null).show();
|
|
return;
|
|
}
|
|
List<Cal> cals = (List<Cal>) r;
|
|
StringBuilder s = new StringBuilder(L.f("Connected. Calendars found: %d\n", cals.size()));
|
|
for (Cal c : cals) s.append("\n\u2022 ").append(c.name).append(c.writable ? "" : " " + L.t("(read-only)"));
|
|
new AlertDialog.Builder(SettingsActivity.this).setTitle(L.t("It works"))
|
|
.setMessage(s.toString())
|
|
.setPositiveButton(L.t("Sync now"), new DialogInterface.OnClickListener() {
|
|
public void onClick(DialogInterface d, int w) {
|
|
App.requestSync(SettingsActivity.this, true, false);
|
|
finish();
|
|
}
|
|
}).show();
|
|
}
|
|
}
|
|
}
|