GingerCal 1.3: month view, Galician interface, Nginx Proxy Manager bridge option
This commit is contained in:
1 parent
f44ecf4871
commit
42c3d6da91
39 files changed
+1409
-220
No files matched your search
@@ -32,6 +32,9 @@ import org.gingercal.Model.Ev;
|
||||
public final class App {
|
||||
private App() {}
|
||||
|
||||
/** Days of past events kept locally, so this and last month's grid work offline. */
|
||||
public static final int BACK_DAYS = 35;
|
||||
|
||||
public static final int[] INTERVALS = {0, 1, 3, 6, 12, 24};
|
||||
public static final int[] ALARMS = {-1, 0, 5, 10, 15, 30, 60, 120, 1440};
|
||||
|
||||
@@ -121,14 +124,14 @@ public final class App {
|
||||
public static String describe(Throwable t) {
|
||||
if (t instanceof DavException || t instanceof DavException.UntrustedCert) return t.getMessage();
|
||||
if (t instanceof UnknownHostException) {
|
||||
return "Can't resolve " + t.getMessage() + ". No connection, or a typo in the server address.";
|
||||
return L.f("Can't resolve %s. No connection, or a typo in the server address.", t.getMessage());
|
||||
}
|
||||
if (t instanceof ConnectException) return "Can't connect to the server (" + t.getMessage() + "). Wrong port, or the bridge is down?";
|
||||
if (t instanceof SocketTimeoutException) return "The server took too long to answer.";
|
||||
if (t instanceof SSLException) return "TLS error: " + t.getMessage();
|
||||
if (t instanceof IOException) return "Network error: " + t;
|
||||
if (t instanceof OutOfMemoryError) return "Out of memory. Lower 'Days ahead' in Settings.";
|
||||
return "Error: " + t;
|
||||
if (t instanceof ConnectException) return L.f("Can't connect to the server (%s). Wrong port, or the bridge is down?", t.getMessage());
|
||||
if (t instanceof SocketTimeoutException) return L.t("The server took too long to answer.");
|
||||
if (t instanceof SSLException) return L.f("TLS error: %s", t.getMessage());
|
||||
if (t instanceof IOException) return L.f("Network error: %s", t);
|
||||
if (t instanceof OutOfMemoryError) return L.t("Out of memory. Lower 'Days ahead' in Settings.");
|
||||
return L.f("Error: %s", t);
|
||||
}
|
||||
|
||||
public static void toast(Context c, String s) {
|
||||
@@ -141,22 +144,19 @@ public final class App {
|
||||
|
||||
public static void askTrust(Activity a, final DavException.UntrustedCert u, final Trusted cb) {
|
||||
String msg = (u.pinMismatch
|
||||
? "WARNING: the server certificate changed since you trusted it. That happens when you "
|
||||
+ "regenerate the bridge certificate - or when someone is intercepting the connection.\n\n"
|
||||
: "This phone doesn't trust the server certificate (normal for the self-signed legacy bridge, "
|
||||
+ "or for Let's Encrypt on Android 2.3).\n\n")
|
||||
+ "Subject: " + u.subject + "\nIssuer: " + u.issuer + "\nExpires: " + u.expires
|
||||
+ "\n\nSHA-256:\n" + u.fingerprint
|
||||
+ "\n\nOnly trust it if this matches the fingerprint printed on your server.";
|
||||
? L.t("WARNING: the server certificate changed since you trusted it. That happens when you regenerate the bridge certificate - or when someone is intercepting the connection.\n\n")
|
||||
: L.t("This phone doesn't trust the server certificate (normal for the self-signed legacy bridge, or for Let's Encrypt on Android 2.3).\n\n"))
|
||||
+ L.f("Subject: %s\nIssuer: %s\nExpires: %s\n\nSHA-256:\n%s\n\nOnly trust it if this matches the fingerprint printed on your server.",
|
||||
u.subject, u.issuer, u.expires, u.fingerprint);
|
||||
new AlertDialog.Builder(a)
|
||||
.setTitle(u.pinMismatch ? "Certificate changed!" : "Trust this certificate?")
|
||||
.setTitle(u.pinMismatch ? L.t("Certificate changed!") : L.t("Trust this certificate?"))
|
||||
.setMessage(msg)
|
||||
.setPositiveButton("Trust", new DialogInterface.OnClickListener() {
|
||||
.setPositiveButton(L.t("Trust"), new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface d, int w) {
|
||||
cb.trusted(u.fingerprint);
|
||||
}
|
||||
})
|
||||
.setNegativeButton("Cancel", null)
|
||||
.setNegativeButton(L.t("Cancel"), null)
|
||||
.show();
|
||||
}
|
||||
|
||||
@@ -164,12 +164,12 @@ public final class App {
|
||||
|
||||
public static String time(Context c, long t) {
|
||||
String p = android.text.format.DateFormat.is24HourFormat(c) ? "HH:mm" : "h:mma";
|
||||
return new SimpleDateFormat(p, Locale.getDefault()).format(new Date(t));
|
||||
return L.df(p).format(new Date(t));
|
||||
}
|
||||
|
||||
public static String day(long t) {
|
||||
boolean thisYear = year(t) == year(System.currentTimeMillis());
|
||||
return new SimpleDateFormat(thisYear ? "EEE d MMM" : "EEE d MMM yyyy", Locale.getDefault()).format(new Date(t));
|
||||
return L.df(thisYear ? "EEE d MMM" : "EEE d MMM yyyy").format(new Date(t));
|
||||
}
|
||||
|
||||
static int year(long t) {
|
||||
@@ -180,16 +180,16 @@ public final class App {
|
||||
|
||||
public static String dayHeader(long dayStart) {
|
||||
long today = ICal.startOfDay(System.currentTimeMillis());
|
||||
if (dayStart == today) return "Today · " + day(dayStart);
|
||||
if (dayStart == ICal.addDays(today, 1)) return "Tomorrow · " + day(dayStart);
|
||||
if (dayStart == today) return L.f("Today \u00b7 %s", day(dayStart));
|
||||
if (dayStart == ICal.addDays(today, 1)) return L.f("Tomorrow \u00b7 %s", day(dayStart));
|
||||
return day(dayStart);
|
||||
}
|
||||
|
||||
public static String when(Context c, Ev e) {
|
||||
if (e.allDay) {
|
||||
long lastDay = ICal.addDays(e.end, -1);
|
||||
if (lastDay <= e.start) return "All day";
|
||||
return "All day, until " + day(lastDay);
|
||||
if (lastDay <= e.start) return L.t("All day");
|
||||
return L.f("All day, until %s", day(lastDay));
|
||||
}
|
||||
String s = time(c, e.start);
|
||||
if (e.end <= e.start) return s;
|
||||
@@ -200,11 +200,11 @@ public final class App {
|
||||
}
|
||||
|
||||
public static String alarmLabel(int m) {
|
||||
if (m < 0) return "No reminder";
|
||||
if (m == 0) return "At start time";
|
||||
if (m < 60) return m + " min before";
|
||||
if (m < 1440) return (m / 60) + " h before";
|
||||
return (m / 1440) + " day before";
|
||||
if (m < 0) return L.t("No reminder");
|
||||
if (m == 0) return L.t("At start time");
|
||||
if (m < 60) return L.f("%d min before", m);
|
||||
if (m < 1440) return L.f("%d h before", m / 60);
|
||||
return L.f("%d day before", m / 1440);
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------- views
|
||||
|
||||
@@ -24,13 +24,13 @@ public class CalendarsActivity extends Activity {
|
||||
@Override
|
||||
protected void onCreate(Bundle b) {
|
||||
super.onCreate(b);
|
||||
setTitle("Calendars");
|
||||
setTitle(L.t("Calendars"));
|
||||
cals.addAll(Db.get(this).calendarList());
|
||||
LinearLayout f = App.vbox(this);
|
||||
int p = App.dp(this, 8);
|
||||
f.setPadding(p, p, p, p);
|
||||
if (cals.isEmpty()) {
|
||||
f.addView(App.text(this, "No calendars yet. Set up the account in Settings and sync.", 14, 0xFFFFFFFF));
|
||||
f.addView(App.text(this, L.t("No calendars yet. Set up the account in Settings and sync."), 14, 0xFFFFFFFF));
|
||||
}
|
||||
for (Cal c : cals) {
|
||||
LinearLayout row = new LinearLayout(this);
|
||||
@@ -41,7 +41,7 @@ public class CalendarsActivity extends Activity {
|
||||
lp.setMargins(0, App.dp(this, 6), App.dp(this, 4), App.dp(this, 6));
|
||||
row.addView(bar, lp);
|
||||
CheckBox cb = new CheckBox(this);
|
||||
cb.setText(c.name + (c.writable ? "" : " (read-only)"));
|
||||
cb.setText(c.name + (c.writable ? "" : " " + L.t("(read-only)")));
|
||||
cb.setChecked(c.enabled);
|
||||
row.addView(cb, new LinearLayout.LayoutParams(-1, -2));
|
||||
boxes.add(cb);
|
||||
@@ -62,9 +62,9 @@ public class CalendarsActivity extends Activity {
|
||||
|
||||
void label() {
|
||||
String def = App.get(this, "defcal");
|
||||
String name = "first writable";
|
||||
String name = L.t("first writable");
|
||||
for (Cal c : cals) if (c.href.equals(def)) name = c.name;
|
||||
defBtn.setText("New events go to: " + name);
|
||||
defBtn.setText(L.f("New events go to: %s", name));
|
||||
}
|
||||
|
||||
void pickDefault() {
|
||||
@@ -72,7 +72,7 @@ public class CalendarsActivity extends Activity {
|
||||
for (Cal c : cals) if (c.writable) w.add(c);
|
||||
String[] names = new String[w.size()];
|
||||
for (int i = 0; i < names.length; i++) names[i] = w.get(i).name;
|
||||
new AlertDialog.Builder(this).setTitle("Default calendar")
|
||||
new AlertDialog.Builder(this).setTitle(L.t("Default calendar"))
|
||||
.setItems(names, new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface d, int which) {
|
||||
App.prefs(CalendarsActivity.this).edit().putString("defcal", w.get(which).href).commit();
|
||||
|
||||
@@ -10,7 +10,9 @@ import android.app.AlertDialog;
|
||||
import android.app.DatePickerDialog;
|
||||
import android.app.ProgressDialog;
|
||||
import android.app.TimePickerDialog;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
@@ -41,39 +43,51 @@ public class EditActivity extends Activity {
|
||||
@Override
|
||||
protected void onCreate(Bundle b) {
|
||||
super.onCreate(b);
|
||||
long id = getIntent().getLongExtra("id", -1);
|
||||
if (id >= 0) editing = Db.get(this).event(id);
|
||||
setTitle(editing == null ? "New event" : "Edit event");
|
||||
// Events come by value: month view can show events fetched live that aren't in the DB.
|
||||
String href = getIntent().getStringExtra("href");
|
||||
if (href != null) {
|
||||
editing = new Ev();
|
||||
editing.href = href;
|
||||
editing.cal = str("cal");
|
||||
editing.etag = str("etag");
|
||||
editing.title = str("title");
|
||||
editing.location = str("location");
|
||||
editing.descr = str("descr");
|
||||
editing.start = getIntent().getLongExtra("start", 0);
|
||||
editing.end = getIntent().getLongExtra("end", 0);
|
||||
editing.allDay = getIntent().getBooleanExtra("allDay", false);
|
||||
}
|
||||
setTitle(editing == null ? L.t("New event") : L.t("Edit event"));
|
||||
for (Cal c : Db.get(this).calendarList()) if (c.enabled && c.writable) cals.add(c);
|
||||
|
||||
LinearLayout f = App.vbox(this);
|
||||
int p = App.dp(this, 8);
|
||||
f.setPadding(p, p, p, p);
|
||||
title = field(f, "Title", false);
|
||||
title = field(f, L.t("Title"), false);
|
||||
allDay = new CheckBox(this);
|
||||
allDay.setText("All day");
|
||||
allDay.setText(L.t("All day"));
|
||||
allDay.setOnClickListener(new View.OnClickListener() {
|
||||
public void onClick(View v) {
|
||||
refresh();
|
||||
}
|
||||
});
|
||||
f.addView(allDay);
|
||||
f.addView(label("Starts"));
|
||||
f.addView(label(L.t("Starts")));
|
||||
startDate = new Button(this);
|
||||
startTime = new Button(this);
|
||||
f.addView(pair(startDate, startTime));
|
||||
f.addView(label("Ends"));
|
||||
f.addView(label(L.t("Ends")));
|
||||
endDate = new Button(this);
|
||||
endTime = new Button(this);
|
||||
f.addView(pair(endDate, endTime));
|
||||
location = field(f, "Location", false);
|
||||
notes = field(f, "Notes", true);
|
||||
location = field(f, L.t("Location"), false);
|
||||
notes = field(f, L.t("Notes"), true);
|
||||
alarmBtn = new Button(this);
|
||||
f.addView(alarmBtn, new LinearLayout.LayoutParams(-1, -2));
|
||||
calBtn = new Button(this);
|
||||
f.addView(calBtn, new LinearLayout.LayoutParams(-1, -2));
|
||||
save = new Button(this);
|
||||
save.setText("Save");
|
||||
save.setText(L.t("Save"));
|
||||
f.addView(save, new LinearLayout.LayoutParams(-1, -2));
|
||||
ScrollView sv = new ScrollView(this);
|
||||
sv.addView(f);
|
||||
@@ -117,7 +131,15 @@ public class EditActivity extends Activity {
|
||||
|
||||
if (editing == null) {
|
||||
long now = System.currentTimeMillis();
|
||||
start.setTimeInMillis(now - now % ICal.HOUR + ICal.HOUR);
|
||||
long day = getIntent().getLongExtra("day", -1);
|
||||
if (day >= 0 && day != ICal.startOfDay(now)) {
|
||||
start.setTimeInMillis(day); // a day picked in the month view: 09:00 there
|
||||
start.set(Calendar.HOUR_OF_DAY, 9);
|
||||
} else {
|
||||
start.setTimeInMillis(now); // next full hour
|
||||
start.add(Calendar.HOUR_OF_DAY, 1);
|
||||
}
|
||||
start.set(Calendar.MINUTE, 0);
|
||||
start.set(Calendar.SECOND, 0);
|
||||
start.set(Calendar.MILLISECOND, 0);
|
||||
end.setTimeInMillis(start.getTimeInMillis() + ICal.HOUR);
|
||||
@@ -134,6 +156,25 @@ public class EditActivity extends Activity {
|
||||
}
|
||||
}
|
||||
|
||||
String str(String k) {
|
||||
String v = getIntent().getStringExtra(k);
|
||||
return v == null ? "" : v;
|
||||
}
|
||||
|
||||
static Intent editIntent(Context c, Ev e) {
|
||||
Intent i = new Intent(c, EditActivity.class);
|
||||
i.putExtra("href", e.href);
|
||||
i.putExtra("cal", e.cal);
|
||||
i.putExtra("etag", e.etag);
|
||||
i.putExtra("title", e.title);
|
||||
i.putExtra("location", e.location);
|
||||
i.putExtra("descr", e.descr);
|
||||
i.putExtra("start", e.start);
|
||||
i.putExtra("end", e.end);
|
||||
i.putExtra("allDay", e.allDay);
|
||||
return i;
|
||||
}
|
||||
|
||||
EditText field(LinearLayout f, String hint, boolean multi) {
|
||||
EditText e = new EditText(this);
|
||||
e.setHint(hint);
|
||||
@@ -181,10 +222,10 @@ public class EditActivity extends Activity {
|
||||
endTime.setText(App.time(this, end.getTimeInMillis()));
|
||||
startTime.setVisibility(ad ? View.GONE : View.VISIBLE);
|
||||
endTime.setVisibility(ad ? View.GONE : View.VISIBLE);
|
||||
alarmBtn.setText("Alarm: " + App.alarmLabel(alarm));
|
||||
alarmBtn.setText(L.f("Alarm: %s", App.alarmLabel(alarm)));
|
||||
String name = "?";
|
||||
for (Cal c : Db.get(this).calendarList()) if (c.href.equals(calHref)) name = c.name;
|
||||
calBtn.setText("Calendar: " + name);
|
||||
calBtn.setText(L.f("Calendar: %s", name));
|
||||
}
|
||||
|
||||
void pickDate(final Calendar c, final boolean isStart) {
|
||||
@@ -214,7 +255,7 @@ public class EditActivity extends Activity {
|
||||
void pickAlarm() {
|
||||
String[] labels = new String[App.ALARMS.length];
|
||||
for (int i = 0; i < labels.length; i++) labels[i] = App.alarmLabel(App.ALARMS[i]);
|
||||
new AlertDialog.Builder(this).setTitle("Alarm (saved in the event)")
|
||||
new AlertDialog.Builder(this).setTitle(L.t("Alarm (saved in the event)"))
|
||||
.setItems(labels, new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface d, int w) {
|
||||
alarm = App.ALARMS[w];
|
||||
@@ -226,7 +267,7 @@ public class EditActivity extends Activity {
|
||||
void pickCalendar() {
|
||||
String[] names = new String[cals.size()];
|
||||
for (int i = 0; i < names.length; i++) names[i] = cals.get(i).name;
|
||||
new AlertDialog.Builder(this).setTitle("Calendar")
|
||||
new AlertDialog.Builder(this).setTitle(L.t("Calendar"))
|
||||
.setItems(names, new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface d, int w) {
|
||||
calHref = cals.get(w).href;
|
||||
@@ -250,15 +291,15 @@ public class EditActivity extends Activity {
|
||||
e = end.getTimeInMillis() - end.getTimeInMillis() % 60000L;
|
||||
}
|
||||
if (e < s || (ad && e <= s)) {
|
||||
App.toast(this, "The end is before the start.");
|
||||
App.toast(this, L.t("The end is before the start."));
|
||||
return;
|
||||
}
|
||||
if (calHref == null) {
|
||||
App.toast(this, "No writable calendar. Sync first.");
|
||||
App.toast(this, L.t("No writable calendar. Sync first."));
|
||||
return;
|
||||
}
|
||||
String t = txt(title);
|
||||
new SaveTask(t.length() > 0 ? t : "(no title)", txt(location), notes.getText().toString().trim(), s, e, ad).execute();
|
||||
new SaveTask(t.length() > 0 ? t : L.t("(no title)"), txt(location), notes.getText().toString().trim(), s, e, ad).execute();
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------- network
|
||||
@@ -268,7 +309,7 @@ public class EditActivity extends Activity {
|
||||
|
||||
@Override
|
||||
protected void onPreExecute() {
|
||||
pd = ProgressDialog.show(EditActivity.this, null, "Loading…");
|
||||
pd = ProgressDialog.show(EditActivity.this, null, L.t("Loading\u2026"));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -291,19 +332,19 @@ public class EditActivity extends Activity {
|
||||
} catch (RuntimeException ignored) {
|
||||
}
|
||||
if (r instanceof Throwable) {
|
||||
fail("Can't load event", App.describe((Throwable) r));
|
||||
fail(L.t("Can't load event"), App.describe((Throwable) r));
|
||||
return;
|
||||
}
|
||||
String[] got = (String[]) r;
|
||||
ics = got[0];
|
||||
etag = got[1] != null ? got[1] : editing.etag;
|
||||
if (ICal.isRecurring(ics)) {
|
||||
fail("Repeating event", "Repeating events can't be edited on the phone yet. Edit the series in Nextcloud.");
|
||||
fail(L.t("Repeating event"), L.t("Repeating events can't be edited on the phone yet. Edit the series in Nextcloud."));
|
||||
return;
|
||||
}
|
||||
List<ICal.VEvent> vs = ICal.parse(ics);
|
||||
if (vs.isEmpty()) {
|
||||
fail("Can't edit", "The server returned no event data.");
|
||||
fail(L.t("Can't edit"), L.t("The server returned no event data."));
|
||||
return;
|
||||
}
|
||||
ICal.VEvent v = vs.get(0);
|
||||
@@ -316,7 +357,7 @@ public class EditActivity extends Activity {
|
||||
|
||||
void fail(String t, String msg) {
|
||||
new AlertDialog.Builder(this).setTitle(t).setMessage(msg)
|
||||
.setPositiveButton("OK", new DialogInterface.OnClickListener() {
|
||||
.setPositiveButton(L.t("OK"), new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface d, int w) {
|
||||
finish();
|
||||
}
|
||||
@@ -340,7 +381,7 @@ public class EditActivity extends Activity {
|
||||
|
||||
@Override
|
||||
protected void onPreExecute() {
|
||||
pd = ProgressDialog.show(EditActivity.this, null, "Saving…");
|
||||
pd = ProgressDialog.show(EditActivity.this, null, L.t("Saving\u2026"));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -370,13 +411,13 @@ public class EditActivity extends Activity {
|
||||
} catch (RuntimeException ignored) {
|
||||
}
|
||||
if (x != null) {
|
||||
new AlertDialog.Builder(EditActivity.this).setTitle("Not saved")
|
||||
.setMessage(App.describe(x)).setPositiveButton("OK", null).show();
|
||||
new AlertDialog.Builder(EditActivity.this).setTitle(L.t("Not saved"))
|
||||
.setMessage(App.describe(x)).setPositiveButton(L.t("OK"), null).show();
|
||||
return;
|
||||
}
|
||||
if (editing == null) App.prefs(EditActivity.this).edit().putString("defcal", calHref).commit();
|
||||
App.requestSync(EditActivity.this, true, false);
|
||||
App.toast(EditActivity.this, "Saved");
|
||||
App.toast(EditActivity.this, L.t("Saved"));
|
||||
finish();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
package org.gingercal;
|
||||
|
||||
import android.app.Application;
|
||||
|
||||
/** Runs before any screen, service or alarm: sets the interface language (Galician by default). */
|
||||
public class GingerApp extends Application {
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
L.gl = !"en".equals(getSharedPreferences("gingercal", 0).getString("lang", "gl"));
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,12 @@
|
||||
package org.gingercal;
|
||||
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
@@ -11,6 +16,7 @@ import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.res.Configuration;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Bundle;
|
||||
import android.view.Menu;
|
||||
@@ -26,15 +32,23 @@ import android.widget.TextView;
|
||||
import org.gingercal.Model.Cal;
|
||||
import org.gingercal.Model.Ev;
|
||||
|
||||
/** Agenda: upcoming events grouped by day. */
|
||||
public class MainActivity extends Activity {
|
||||
static final int M_SYNC = 1, M_NEW = 2, M_CALS = 3, M_SETTINGS = 4;
|
||||
/** Main screen: a month grid with the selected day's events, or the upcoming-events agenda. */
|
||||
public class MainActivity extends Activity implements MonthView.Listener {
|
||||
static final int M_SYNC = 1, M_NEW = 2, M_CALS = 3, M_SETTINGS = 4, M_VIEW = 5, M_TODAY = 6;
|
||||
|
||||
TextView status;
|
||||
ListView list;
|
||||
final List<Object> rows = new ArrayList<Object>();
|
||||
final Agenda adapter = new Agenda();
|
||||
boolean askedSetup;
|
||||
LinearLayout root, body;
|
||||
TextView status, monthTitle;
|
||||
ListView agendaList, dayList;
|
||||
MonthView monthView;
|
||||
final List<Object> agendaRows = new ArrayList<Object>(), dayRows = new ArrayList<Object>();
|
||||
final Rows agendaAdapter = new Rows(agendaRows), dayAdapter = new Rows(dayRows);
|
||||
boolean askedSetup, monthMode, uiGl;
|
||||
long selectedDay;
|
||||
List<Ev> monthEvents = new ArrayList<Ev>();
|
||||
/** Months outside the synced window, fetched from the server on demand (key: grid start). */
|
||||
final Map<Long, List<Ev>> fetched = new HashMap<Long, List<Ev>>();
|
||||
long fetching = -1;
|
||||
String fetchError;
|
||||
|
||||
final BroadcastReceiver rx = new BroadcastReceiver() {
|
||||
@Override
|
||||
@@ -44,7 +58,7 @@ public class MainActivity extends Activity {
|
||||
showStatus();
|
||||
return;
|
||||
}
|
||||
reload();
|
||||
reload(true);
|
||||
String fp = i.getStringExtra("fp");
|
||||
if (fp != null) {
|
||||
DavException.UntrustedCert u = new DavException.UntrustedCert(fp, i.getStringExtra("subject"),
|
||||
@@ -62,29 +76,115 @@ public class MainActivity extends Activity {
|
||||
@Override
|
||||
protected void onCreate(Bundle b) {
|
||||
super.onCreate(b);
|
||||
setTitle("GingerCal");
|
||||
LinearLayout root = App.vbox(this);
|
||||
status = App.text(this, "", 12, 0xFFAAAAAA);
|
||||
int p = App.dp(this, 6);
|
||||
requestWindowFeature(1 /* Window.FEATURE_NO_TITLE: every pixel counts at 240x320 */);
|
||||
uiGl = L.gl;
|
||||
monthMode = !"agenda".equals(App.get(this, "view"));
|
||||
selectedDay = ICal.startOfDay(System.currentTimeMillis());
|
||||
|
||||
root = App.vbox(this);
|
||||
status = App.text(this, "", 11, 0xFFAAAAAA);
|
||||
int p = App.dp(this, 4);
|
||||
status.setPadding(p * 2, p, p * 2, p);
|
||||
status.setBackgroundColor(0xFF1C1C1C);
|
||||
root.addView(status, new LinearLayout.LayoutParams(-1, -2));
|
||||
list = new ListView(this);
|
||||
list.setAdapter(adapter);
|
||||
list.setOnItemClickListener(new AdapterView.OnItemClickListener() {
|
||||
|
||||
agendaList = list(agendaAdapter, agendaRows);
|
||||
dayList = list(dayAdapter, dayRows);
|
||||
monthView = new MonthView(this);
|
||||
monthView.listener = this;
|
||||
monthView.setMonth(selectedDay);
|
||||
monthView.setSelected(selectedDay);
|
||||
monthTitle = App.text(this, "", 15, 0xFFFFFFFF);
|
||||
monthTitle.setGravity(17 /* Gravity.CENTER */);
|
||||
monthTitle.setOnClickListener(new View.OnClickListener() {
|
||||
public void onClick(View v) {
|
||||
goToday();
|
||||
}
|
||||
});
|
||||
buildBody();
|
||||
setContentView(root);
|
||||
App.scheduleSync(this);
|
||||
}
|
||||
|
||||
ListView list(Rows adapter, final List<Object> rows) {
|
||||
ListView l = new ListView(this);
|
||||
l.setAdapter(adapter);
|
||||
l.setOnItemClickListener(new AdapterView.OnItemClickListener() {
|
||||
public void onItemClick(AdapterView<?> parent, View v, int pos, long id) {
|
||||
Object o = rows.get(pos);
|
||||
if (o instanceof Ev) showEvent((Ev) o);
|
||||
}
|
||||
});
|
||||
root.addView(list, new LinearLayout.LayoutParams(-1, -1));
|
||||
setContentView(root);
|
||||
App.scheduleSync(this);
|
||||
return l;
|
||||
}
|
||||
|
||||
static void detach(View v) {
|
||||
// Views can only have one parent; LinearLayout is the only container we use.
|
||||
Object parent = v.getParent();
|
||||
if (parent instanceof LinearLayout) ((LinearLayout) parent).removeView(v);
|
||||
}
|
||||
|
||||
/** (Re)builds the area under the status line for the current mode and orientation. */
|
||||
void buildBody() {
|
||||
if (body != null) root.removeView(body);
|
||||
detach(agendaList);
|
||||
detach(dayList);
|
||||
detach(monthView);
|
||||
detach(monthTitle);
|
||||
body = App.vbox(this);
|
||||
if (!monthMode) {
|
||||
body.addView(agendaList, new LinearLayout.LayoutParams(-1, -1));
|
||||
} else {
|
||||
LinearLayout nav = new LinearLayout(this);
|
||||
nav.setOrientation(LinearLayout.HORIZONTAL);
|
||||
nav.setBackgroundColor(0xFF1C2329);
|
||||
nav.addView(navArrow("\u00ab", -1), new LinearLayout.LayoutParams(App.dp(this, 56), App.dp(this, 34)));
|
||||
nav.addView(monthTitle, new LinearLayout.LayoutParams(0, App.dp(this, 34), 1f));
|
||||
nav.addView(navArrow("\u00bb", 1), new LinearLayout.LayoutParams(App.dp(this, 56), App.dp(this, 34)));
|
||||
body.addView(nav, new LinearLayout.LayoutParams(-1, -2));
|
||||
boolean landscape = getResources().getDisplayMetrics().widthPixels
|
||||
> getResources().getDisplayMetrics().heightPixels;
|
||||
if (landscape) {
|
||||
LinearLayout row = new LinearLayout(this);
|
||||
row.setOrientation(LinearLayout.HORIZONTAL);
|
||||
row.addView(monthView, new LinearLayout.LayoutParams(0, -1, 1.25f));
|
||||
row.addView(dayList, new LinearLayout.LayoutParams(0, -1, 1f));
|
||||
body.addView(row, new LinearLayout.LayoutParams(-1, -1));
|
||||
} else {
|
||||
body.addView(monthView, new LinearLayout.LayoutParams(-1, -2));
|
||||
body.addView(dayList, new LinearLayout.LayoutParams(-1, -1));
|
||||
}
|
||||
}
|
||||
root.addView(body, new LinearLayout.LayoutParams(-1, -1));
|
||||
}
|
||||
|
||||
TextView navArrow(String label, final int delta) {
|
||||
TextView b = App.text(this, label, 20, 0xFFD66E2A); // « »: Latin-1, always in the 2.3 font
|
||||
b.setGravity(17 /* Gravity.CENTER */);
|
||||
int sel = App.sysDrawable(this, "list_selector_background");
|
||||
if (sel != 0) b.setBackgroundResource(sel); // press highlight
|
||||
b.setOnClickListener(new View.OnClickListener() {
|
||||
public void onClick(View v) {
|
||||
monthSwiped(delta);
|
||||
}
|
||||
});
|
||||
return b;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConfigurationChanged(Configuration c) {
|
||||
super.onConfigurationChanged(c);
|
||||
buildBody();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
if (uiGl != L.gl) { // language switched in Settings: rebuild every label
|
||||
finish();
|
||||
startActivity(new Intent(this, MainActivity.class));
|
||||
return;
|
||||
}
|
||||
registerReceiver(rx, new IntentFilter(SyncService.STATE));
|
||||
if (!App.configured(this)) {
|
||||
showStatus();
|
||||
@@ -94,7 +194,9 @@ public class MainActivity extends Activity {
|
||||
}
|
||||
return;
|
||||
}
|
||||
reload();
|
||||
long now = ICal.startOfDay(System.currentTimeMillis());
|
||||
if (now != monthView.today) monthView.setMonth(monthView.days[15]); // date rolled over
|
||||
reload(true);
|
||||
long last = App.prefs(this).getLong("lastsync", 0);
|
||||
if (!SyncService.running && System.currentTimeMillis() - last > 15 * ICal.MIN) {
|
||||
App.requestSync(this, true, false);
|
||||
@@ -109,74 +211,264 @@ public class MainActivity extends Activity {
|
||||
|
||||
void showStatus() {
|
||||
if (!App.configured(this)) {
|
||||
status.setText("Not set up yet: press Menu > Settings.");
|
||||
status.setText(L.t("Not set up yet: press Menu > Settings."));
|
||||
return;
|
||||
}
|
||||
if (SyncService.running) {
|
||||
status.setText("Syncing…");
|
||||
status.setText(L.t("Syncing\u2026"));
|
||||
status.setTextColor(0xFFAAAAAA);
|
||||
return;
|
||||
}
|
||||
if (monthMode && fetching >= 0) {
|
||||
status.setText(L.f("Loading %s from the server\u2026", monthTitle.getText()));
|
||||
status.setTextColor(0xFFAAAAAA);
|
||||
return;
|
||||
}
|
||||
if (monthMode && fetchError != null) {
|
||||
status.setText(L.f("Couldn't load this month: %s", fetchError));
|
||||
status.setTextColor(0xFFFF8A80);
|
||||
return;
|
||||
}
|
||||
long last = App.prefs(this).getLong("lastsync", 0);
|
||||
boolean err = App.prefs(this).getBoolean("error", false);
|
||||
String s = App.get(this, "status");
|
||||
String when = last == 0 ? "never" : App.day(last) + " " + App.time(this, last);
|
||||
String when = last == 0 ? L.t("never") : App.day(last) + " " + App.time(this, last);
|
||||
if (err) {
|
||||
status.setText("Sync failed: " + s + "\nLast good sync: " + when);
|
||||
status.setText(L.f("Sync failed: %s\nLast good sync: %s", s, when));
|
||||
status.setTextColor(0xFFFF8A80);
|
||||
} else {
|
||||
status.setText("Synced " + when + (s.length() > 0 ? " · " + s : ""));
|
||||
status.setText(L.f("Synced %s", when) + (s.length() > 0 ? " \u00b7 " + s : ""));
|
||||
status.setTextColor(0xFFAAAAAA);
|
||||
}
|
||||
}
|
||||
|
||||
void reload() {
|
||||
rows.clear();
|
||||
void reload(boolean allowFetch) {
|
||||
if (monthMode) loadMonth(allowFetch);
|
||||
else loadAgenda();
|
||||
showStatus();
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------- agenda
|
||||
|
||||
void loadAgenda() {
|
||||
agendaRows.clear();
|
||||
long today = ICal.startOfDay(System.currentTimeMillis());
|
||||
List<Ev> evs = Db.get(this).agenda(today, ICal.addDays(today, App.ahead(this) + 1));
|
||||
long lastDay = -1;
|
||||
for (Ev e : evs) {
|
||||
long d = Math.max(ICal.startOfDay(e.start), today);
|
||||
if (d != lastDay) {
|
||||
rows.add(Long.valueOf(d));
|
||||
agendaRows.add(Long.valueOf(d));
|
||||
lastDay = d;
|
||||
}
|
||||
rows.add(e);
|
||||
agendaRows.add(e);
|
||||
}
|
||||
if (agendaRows.isEmpty() && App.configured(this)) {
|
||||
agendaRows.add(L.t("No upcoming events. Menu > New event, or Menu > Sync."));
|
||||
}
|
||||
agendaAdapter.notifyDataSetChanged();
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------- month
|
||||
|
||||
void loadMonth(boolean allowFetch) {
|
||||
long gs = monthView.gridStart(), ge = monthView.gridEnd();
|
||||
monthTitle.setText(L.monthTitle(monthView.days[15]));
|
||||
long wf = App.prefs(this).getLong("win_from", 0), wt = App.prefs(this).getLong("win_to", 0);
|
||||
if (wf > 0 && gs >= wf && ge <= wt) {
|
||||
fetchError = null;
|
||||
showMonthEvents(Db.get(this).agenda(gs, ge));
|
||||
} else {
|
||||
List<Ev> cached = fetched.get(Long.valueOf(gs));
|
||||
showMonthEvents(cached != null ? cached : Db.get(this).agenda(gs, ge));
|
||||
if (allowFetch && fetching != gs && App.configured(this)) new FetchTask(gs, ge).execute();
|
||||
}
|
||||
}
|
||||
|
||||
void showMonthEvents(List<Ev> evs) {
|
||||
monthEvents = evs;
|
||||
Map<Long, List<Integer>> marks = new HashMap<Long, List<Integer>>();
|
||||
long gs = monthView.gridStart(), ge = monthView.gridEnd();
|
||||
for (Ev e : evs) {
|
||||
long d = Math.max(ICal.startOfDay(e.start), gs);
|
||||
for (int guard = 0; d < ge && guard < 42; guard++) {
|
||||
if (!(occupies(e, d))) break;
|
||||
List<Integer> l = marks.get(Long.valueOf(d));
|
||||
if (l == null) {
|
||||
l = new ArrayList<Integer>();
|
||||
marks.put(Long.valueOf(d), l);
|
||||
}
|
||||
l.add(Integer.valueOf(e.color));
|
||||
d = ICal.addDays(d, 1);
|
||||
}
|
||||
}
|
||||
monthView.setMarks(marks);
|
||||
showDay();
|
||||
}
|
||||
|
||||
static boolean occupies(Ev e, long d) {
|
||||
return ICal.onDay(e.start, e.end, d);
|
||||
}
|
||||
|
||||
void showDay() {
|
||||
dayRows.clear();
|
||||
int n = 0;
|
||||
for (Ev e : monthEvents) {
|
||||
if (occupies(e, selectedDay)) {
|
||||
dayRows.add(e);
|
||||
n++;
|
||||
}
|
||||
}
|
||||
if (n == 0) dayRows.add(L.f("Nothing on %s. Long-press a day to add an event.", App.day(selectedDay)));
|
||||
dayAdapter.notifyDataSetChanged();
|
||||
}
|
||||
|
||||
public void daySelected(long day) {
|
||||
selectedDay = day;
|
||||
monthView.setSelected(day);
|
||||
if (!monthView.inMonth(day)) {
|
||||
monthView.setMonth(day);
|
||||
reload(true);
|
||||
} else {
|
||||
showDay();
|
||||
}
|
||||
}
|
||||
|
||||
public void dayLongPressed(long day) {
|
||||
daySelected(day);
|
||||
newEvent(day);
|
||||
}
|
||||
|
||||
public void monthSwiped(int delta) {
|
||||
java.util.Calendar c = java.util.Calendar.getInstance();
|
||||
c.setTimeInMillis(monthView.days[15]);
|
||||
c.set(java.util.Calendar.DAY_OF_MONTH, 1);
|
||||
c.add(java.util.Calendar.MONTH, delta);
|
||||
long first = c.getTimeInMillis();
|
||||
monthView.setMonth(first);
|
||||
long today = ICal.startOfDay(System.currentTimeMillis());
|
||||
selectedDay = monthView.inMonth(today) ? today : ICal.startOfDay(first);
|
||||
monthView.setSelected(selectedDay);
|
||||
fetchError = null;
|
||||
reload(true);
|
||||
}
|
||||
|
||||
void goToday() {
|
||||
selectedDay = ICal.startOfDay(System.currentTimeMillis());
|
||||
monthView.setMonth(selectedDay);
|
||||
monthView.setSelected(selectedDay);
|
||||
reload(true);
|
||||
}
|
||||
|
||||
/** Loads a month outside the synced window straight from the server. */
|
||||
class FetchTask extends AsyncTask<Void, Void, Object> {
|
||||
final long gs, ge;
|
||||
|
||||
FetchTask(long gs, long ge) {
|
||||
this.gs = gs;
|
||||
this.ge = ge;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPreExecute() {
|
||||
fetching = gs;
|
||||
fetchError = null;
|
||||
showStatus();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object doInBackground(Void... v) {
|
||||
Dav d = null;
|
||||
try {
|
||||
d = App.dav(MainActivity.this);
|
||||
List<Ev> out = new ArrayList<Ev>();
|
||||
for (Cal c : Db.get(MainActivity.this).calendarList()) {
|
||||
if (!c.enabled) continue;
|
||||
for (Ev e : d.events(c, gs, ge)) {
|
||||
e.color = c.color;
|
||||
e.writable = c.writable;
|
||||
out.add(e);
|
||||
}
|
||||
}
|
||||
java.util.Collections.sort(out, new java.util.Comparator<Ev>() {
|
||||
public int compare(Ev a, Ev b) {
|
||||
return a.start < b.start ? -1 : a.start > b.start ? 1 : 0;
|
||||
}
|
||||
});
|
||||
return out;
|
||||
} catch (Throwable t) {
|
||||
return t;
|
||||
} finally {
|
||||
if (d != null) d.close();
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
protected void onPostExecute(Object r) {
|
||||
if (fetching == gs) fetching = -1;
|
||||
if (r instanceof Throwable) {
|
||||
fetchError = App.describe((Throwable) r);
|
||||
} else {
|
||||
fetched.put(Long.valueOf(gs), (List<Ev>) r);
|
||||
if (monthMode && monthView.gridStart() == gs) showMonthEvents((List<Ev>) r);
|
||||
}
|
||||
showStatus();
|
||||
}
|
||||
if (rows.isEmpty() && App.configured(this)) rows.add("No upcoming events. Menu > New event, or Menu > Sync.");
|
||||
adapter.notifyDataSetChanged();
|
||||
showStatus();
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------- menu
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu m) {
|
||||
icon(m.add(0, M_SYNC, 0, "Sync"), "ic_menu_refresh");
|
||||
icon(m.add(0, M_NEW, 1, "New event"), "ic_menu_add");
|
||||
icon(m.add(0, M_CALS, 2, "Calendars"), "ic_menu_agenda");
|
||||
icon(m.add(0, M_SETTINGS, 3, "Settings"), "ic_menu_preferences");
|
||||
icon(m.add(0, M_VIEW, 0, L.t("View")), "ic_menu_month");
|
||||
icon(m.add(0, M_NEW, 1, L.t("New event")), "ic_menu_add");
|
||||
icon(m.add(0, M_SYNC, 2, L.t("Sync")), "ic_menu_refresh");
|
||||
icon(m.add(0, M_TODAY, 3, L.t("Today")), "ic_menu_today");
|
||||
icon(m.add(0, M_CALS, 4, L.t("Calendars")), "ic_menu_agenda");
|
||||
icon(m.add(0, M_SETTINGS, 5, L.t("Settings")), "ic_menu_preferences");
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onPrepareOptionsMenu(Menu m) {
|
||||
MenuItem v = m.findItem(M_VIEW);
|
||||
if (v != null) {
|
||||
v.setTitle(monthMode ? L.t("Agenda") : L.t("Month"));
|
||||
icon(v, monthMode ? "ic_menu_agenda" : "ic_menu_month");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void icon(MenuItem i, String name) {
|
||||
int id = App.sysDrawable(this, name);
|
||||
if (id == 0) id = App.sysDrawable(this, "ic_menu_my_calendar");
|
||||
if (id != 0) i.setIcon(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case M_VIEW:
|
||||
monthMode = !monthMode;
|
||||
App.prefs(this).edit().putString("view", monthMode ? "month" : "agenda").commit();
|
||||
buildBody();
|
||||
reload(true);
|
||||
return true;
|
||||
case M_TODAY:
|
||||
if (monthMode) goToday();
|
||||
else agendaList.setSelection(0);
|
||||
return true;
|
||||
case M_SYNC:
|
||||
if (App.configured(this)) App.requestSync(this, true, false);
|
||||
else startActivity(new Intent(this, SettingsActivity.class));
|
||||
if (App.configured(this)) {
|
||||
fetched.clear();
|
||||
App.requestSync(this, true, false);
|
||||
} else {
|
||||
startActivity(new Intent(this, SettingsActivity.class));
|
||||
}
|
||||
return true;
|
||||
case M_NEW:
|
||||
if (writableCalendars().isEmpty()) {
|
||||
App.toast(this, "Sync first: no writable calendar yet.");
|
||||
} else {
|
||||
startActivity(new Intent(this, EditActivity.class));
|
||||
}
|
||||
newEvent(monthMode ? selectedDay : -1);
|
||||
return true;
|
||||
case M_CALS:
|
||||
startActivity(new Intent(this, CalendarsActivity.class));
|
||||
@@ -188,6 +480,17 @@ public class MainActivity extends Activity {
|
||||
return false;
|
||||
}
|
||||
|
||||
void newEvent(long day) {
|
||||
if (writableCalendars().isEmpty()) {
|
||||
App.toast(this, L.t("Sync first: no writable calendar yet."));
|
||||
return;
|
||||
}
|
||||
Intent i = new Intent(this, EditActivity.class);
|
||||
if (day >= 0) i.putExtra("day", day);
|
||||
fetched.clear();
|
||||
startActivity(i);
|
||||
}
|
||||
|
||||
List<Cal> writableCalendars() {
|
||||
List<Cal> out = new ArrayList<Cal>();
|
||||
for (Cal c : Db.get(this).calendarList()) if (c.enabled && c.writable) out.add(c);
|
||||
@@ -202,23 +505,22 @@ public class MainActivity extends Activity {
|
||||
if (e.location.length() > 0) m.append("\n\n").append(e.location);
|
||||
if (e.descr.length() > 0) m.append("\n\n").append(e.descr);
|
||||
Cal cal = Db.get(this).calendars().get(e.cal);
|
||||
if (cal != null) m.append("\n\nCalendar: ").append(cal.name);
|
||||
if (e.recurring) m.append("\n\nRepeating event: edit the series in Nextcloud. Delete removes the whole series.");
|
||||
if (cal != null) m.append("\n\n").append(L.f("Calendar: %s", cal.name));
|
||||
if (e.recurring) m.append("\n\n").append(L.t("Repeating event: edit the series in Nextcloud. Delete removes the whole series."));
|
||||
AlertDialog.Builder b = new AlertDialog.Builder(this)
|
||||
.setTitle(e.title.length() > 0 ? e.title : "(no title)")
|
||||
.setTitle(e.title.length() > 0 ? e.title : L.t("(no title)"))
|
||||
.setMessage(m.toString())
|
||||
.setNegativeButton("Close", null);
|
||||
.setNegativeButton(L.t("Close"), null);
|
||||
if (e.writable && !e.recurring) {
|
||||
b.setPositiveButton("Edit", new DialogInterface.OnClickListener() {
|
||||
b.setPositiveButton(L.t("Edit"), new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface d, int w) {
|
||||
Intent i = new Intent(MainActivity.this, EditActivity.class);
|
||||
i.putExtra("id", e.id);
|
||||
startActivity(i);
|
||||
fetched.clear();
|
||||
startActivity(EditActivity.editIntent(MainActivity.this, e));
|
||||
}
|
||||
});
|
||||
}
|
||||
if (e.writable) {
|
||||
b.setNeutralButton("Delete", new DialogInterface.OnClickListener() {
|
||||
b.setNeutralButton(L.t("Delete"), new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface d, int w) {
|
||||
confirmDelete(e);
|
||||
}
|
||||
@@ -229,14 +531,14 @@ public class MainActivity extends Activity {
|
||||
|
||||
void confirmDelete(final Ev e) {
|
||||
new AlertDialog.Builder(this)
|
||||
.setTitle("Delete event?")
|
||||
.setMessage(e.recurring ? "This deletes EVERY occurrence of \"" + e.title + "\"." : "\"" + e.title + "\" will be deleted on the server.")
|
||||
.setPositiveButton("Delete", new DialogInterface.OnClickListener() {
|
||||
.setTitle(L.t("Delete event?"))
|
||||
.setMessage(e.recurring ? L.f("This deletes EVERY occurrence of \"%s\".", e.title) : L.f("\"%s\" will be deleted on the server.", e.title))
|
||||
.setPositiveButton(L.t("Delete"), new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface d, int w) {
|
||||
new DeleteTask(e).execute();
|
||||
}
|
||||
})
|
||||
.setNegativeButton("Cancel", null)
|
||||
.setNegativeButton(L.t("Cancel"), null)
|
||||
.show();
|
||||
}
|
||||
|
||||
@@ -250,7 +552,7 @@ public class MainActivity extends Activity {
|
||||
|
||||
@Override
|
||||
protected void onPreExecute() {
|
||||
pd = ProgressDialog.show(MainActivity.this, null, "Deleting…");
|
||||
pd = ProgressDialog.show(MainActivity.this, null, L.t("Deleting\u2026"));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -274,16 +576,21 @@ public class MainActivity extends Activity {
|
||||
} catch (RuntimeException ignored) {
|
||||
}
|
||||
if (t != null) {
|
||||
new AlertDialog.Builder(MainActivity.this).setTitle("Delete failed")
|
||||
.setMessage(App.describe(t)).setPositiveButton("OK", null).show();
|
||||
new AlertDialog.Builder(MainActivity.this).setTitle(L.t("Delete failed"))
|
||||
.setMessage(App.describe(t)).setPositiveButton(L.t("OK"), null).show();
|
||||
return;
|
||||
}
|
||||
App.requestSync(MainActivity.this, true, false);
|
||||
for (int i = rows.size() - 1; i >= 0; i--) {
|
||||
Object o = rows.get(i);
|
||||
if (o instanceof Ev && ((Ev) o).href.equals(e.href)) rows.remove(i);
|
||||
// Hide it right away; the sync that follows brings the cache in line.
|
||||
List<Ev> keep = new ArrayList<Ev>();
|
||||
for (Ev x : monthEvents) if (!x.href.equals(e.href)) keep.add(x);
|
||||
fetched.clear();
|
||||
for (int i = agendaRows.size() - 1; i >= 0; i--) {
|
||||
Object o = agendaRows.get(i);
|
||||
if (o instanceof Ev && ((Ev) o).href.equals(e.href)) agendaRows.remove(i);
|
||||
}
|
||||
adapter.notifyDataSetChanged();
|
||||
agendaAdapter.notifyDataSetChanged();
|
||||
if (monthMode) showMonthEvents(keep);
|
||||
App.requestSync(MainActivity.this, true, false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -294,7 +601,14 @@ public class MainActivity extends Activity {
|
||||
TextView time, title, where;
|
||||
}
|
||||
|
||||
class Agenda extends BaseAdapter {
|
||||
/** Day headers (Long), events (Ev) and hint lines (String). */
|
||||
class Rows extends BaseAdapter {
|
||||
final List<Object> rows;
|
||||
|
||||
Rows(List<Object> rows) {
|
||||
this.rows = rows;
|
||||
}
|
||||
|
||||
public int getCount() {
|
||||
return rows.size();
|
||||
}
|
||||
@@ -332,10 +646,12 @@ public class MainActivity extends Activity {
|
||||
Context c = MainActivity.this;
|
||||
if (!(o instanceof Ev)) {
|
||||
TextView h = convert instanceof TextView ? (TextView) convert : App.text(c, "", 13, 0xFFFFFFFF);
|
||||
int pad = App.dp(c, 6);
|
||||
int pad = App.dp(c, 5);
|
||||
h.setPadding(pad * 2, pad, pad, pad);
|
||||
h.setBackgroundColor(0xFF2E3B44);
|
||||
h.setText(o instanceof Long ? App.dayHeader(((Long) o).longValue()) : String.valueOf(o));
|
||||
boolean header = o instanceof Long;
|
||||
h.setBackgroundColor(header ? 0xFF2E3B44 : 0x00000000);
|
||||
h.setTextColor(header ? 0xFFFFFFFF : 0xFF90A4AE);
|
||||
h.setText(header ? App.dayHeader(((Long) o).longValue()) : String.valueOf(o));
|
||||
return h;
|
||||
}
|
||||
Ev e = (Ev) o;
|
||||
@@ -348,7 +664,7 @@ public class MainActivity extends Activity {
|
||||
hd.bar = new View(c);
|
||||
row.addView(hd.bar, new LinearLayout.LayoutParams(App.dp(c, 5), -1));
|
||||
LinearLayout col = App.vbox(c);
|
||||
int pad = App.dp(c, 6);
|
||||
int pad = App.dp(c, 5);
|
||||
col.setPadding(pad * 2, pad, pad, pad);
|
||||
hd.time = App.text(c, "", 12, 0xFFB0BEC5);
|
||||
hd.title = App.text(c, "", 16, 0xFFFFFFFF);
|
||||
@@ -363,8 +679,8 @@ public class MainActivity extends Activity {
|
||||
hd = (Holder) v.getTag();
|
||||
}
|
||||
hd.bar.setBackgroundColor(e.color);
|
||||
hd.time.setText(App.when(c, e) + (e.recurring ? " (repeats)" : ""));
|
||||
hd.title.setText(e.title.length() > 0 ? e.title : "(no title)");
|
||||
hd.time.setText(App.when(c, e) + (e.recurring ? " " + L.t("(repeats)") : ""));
|
||||
hd.title.setText(e.title.length() > 0 ? e.title : L.t("(no title)"));
|
||||
hd.where.setText(e.location);
|
||||
hd.where.setVisibility(e.location.length() > 0 ? View.VISIBLE : View.GONE);
|
||||
return v;
|
||||
|
||||
@@ -0,0 +1,215 @@
|
||||
package org.gingercal;
|
||||
|
||||
import java.util.Calendar;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Paint;
|
||||
import android.view.GestureDetector;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
|
||||
/**
|
||||
* Month grid drawn by hand (Android 2.3 has no calendar widget). Six weeks of days, each with
|
||||
* up to four coloured marks for its events. Tap selects a day, long-press asks to create an
|
||||
* event on it, a sideways swipe changes month.
|
||||
*/
|
||||
public class MonthView extends View {
|
||||
|
||||
public interface Listener {
|
||||
void daySelected(long day);
|
||||
|
||||
void dayLongPressed(long day);
|
||||
|
||||
void monthSwiped(int delta);
|
||||
}
|
||||
|
||||
static final int BG = 0xFF101418, GRID = 0xFF2A3036, WEEKDAY = 0xFF90A4AE, NUM = 0xFFECEFF1,
|
||||
OTHER = 0xFF5C666E, TODAY = 0xFFD66E2A, SELECTED = 0xFF2E3B44;
|
||||
|
||||
final float density;
|
||||
final Paint fill = new Paint(1 /* ANTI_ALIAS_FLAG */), text = new Paint(1), line = new Paint();
|
||||
final GestureDetector gestures;
|
||||
final String[] weekdays = new String[7];
|
||||
final long[] days = new long[42];
|
||||
Listener listener;
|
||||
int month = -1; // Calendar.MONTH of the displayed month
|
||||
long selected = -1, today;
|
||||
Map<Long, List<Integer>> marks = new HashMap<Long, List<Integer>>();
|
||||
|
||||
public MonthView(Context c) {
|
||||
super(c);
|
||||
density = c.getResources().getDisplayMetrics().density;
|
||||
line.setColor(GRID);
|
||||
String[] shortNames = L.symbols().getShortWeekdays(); // index 1 = Sunday
|
||||
int first = L.firstDayOfWeek();
|
||||
for (int i = 0; i < 7; i++) {
|
||||
String n = shortNames[(first - 1 + i) % 7 + 1];
|
||||
weekdays[i] = n.length() > 3 ? n.substring(0, 3) : n;
|
||||
}
|
||||
gestures = new GestureDetector(c, new GestureDetector.SimpleOnGestureListener() {
|
||||
@Override
|
||||
public boolean onDown(MotionEvent e) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onSingleTapUp(MotionEvent e) {
|
||||
long d = dayAt(e.getX(), e.getY());
|
||||
if (d >= 0 && listener != null) listener.daySelected(d);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLongPress(MotionEvent e) {
|
||||
long d = dayAt(e.getX(), e.getY());
|
||||
if (d >= 0 && listener != null) {
|
||||
performHapticFeedback(0 /* HapticFeedbackConstants.LONG_PRESS */);
|
||||
listener.dayLongPressed(d);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onFling(MotionEvent a, MotionEvent b, float vx, float vy) {
|
||||
if (listener != null && Math.abs(vx) > Math.abs(vy) * 1.5f && Math.abs(vx) > 250 * density) {
|
||||
listener.monthSwiped(vx < 0 ? 1 : -1);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/** Shows the month containing `anyDay`, weeks starting on the locale's first weekday. */
|
||||
public void setMonth(long anyDay) {
|
||||
Calendar c = Calendar.getInstance();
|
||||
c.setTimeInMillis(ICal.startOfDay(anyDay));
|
||||
c.set(Calendar.DAY_OF_MONTH, 1);
|
||||
month = c.get(Calendar.MONTH);
|
||||
int back = (c.get(Calendar.DAY_OF_WEEK) - L.firstDayOfWeek() + 7) % 7;
|
||||
c.add(Calendar.DAY_OF_MONTH, -back);
|
||||
for (int i = 0; i < 42; i++) {
|
||||
days[i] = c.getTimeInMillis();
|
||||
c.add(Calendar.DAY_OF_MONTH, 1);
|
||||
}
|
||||
today = ICal.startOfDay(System.currentTimeMillis());
|
||||
invalidate();
|
||||
}
|
||||
|
||||
public long gridStart() {
|
||||
return days[0];
|
||||
}
|
||||
|
||||
public long gridEnd() {
|
||||
return ICal.addDays(days[41], 1);
|
||||
}
|
||||
|
||||
public boolean inMonth(long day) {
|
||||
Calendar c = Calendar.getInstance();
|
||||
c.setTimeInMillis(day);
|
||||
return c.get(Calendar.MONTH) == month;
|
||||
}
|
||||
|
||||
public void setSelected(long day) {
|
||||
selected = day;
|
||||
invalidate();
|
||||
}
|
||||
|
||||
public void setMarks(Map<Long, List<Integer>> m) {
|
||||
marks = m;
|
||||
invalidate();
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------- layout
|
||||
|
||||
float headerH() {
|
||||
return 16 * density;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onMeasure(int wSpec, int hSpec) {
|
||||
int w = MeasureSpec.getSize(wSpec);
|
||||
int h;
|
||||
if (MeasureSpec.getMode(hSpec) == MeasureSpec.EXACTLY) {
|
||||
h = MeasureSpec.getSize(hSpec);
|
||||
} else {
|
||||
float cell = Math.max(22 * density, Math.min(w / 7f * 0.66f, 44 * density));
|
||||
h = (int) (headerH() + 6 * cell);
|
||||
if (MeasureSpec.getMode(hSpec) == MeasureSpec.AT_MOST) h = Math.min(h, MeasureSpec.getSize(hSpec));
|
||||
}
|
||||
setMeasuredDimension(w, h);
|
||||
}
|
||||
|
||||
long dayAt(float x, float y) {
|
||||
float cw = getWidth() / 7f, ch = (getHeight() - headerH()) / 6f;
|
||||
if (y < headerH() || cw <= 0 || ch <= 0) return -1;
|
||||
int col = Math.min(6, Math.max(0, (int) (x / cw)));
|
||||
int row = Math.min(5, Math.max(0, (int) ((y - headerH()) / ch)));
|
||||
return days[row * 7 + col];
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onTouchEvent(MotionEvent e) {
|
||||
return gestures.onTouchEvent(e);
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------- drawing
|
||||
|
||||
@Override
|
||||
protected void onDraw(Canvas canvas) {
|
||||
if (month < 0) return;
|
||||
float w = getWidth(), cw = w / 7f, top = headerH(), ch = (getHeight() - top) / 6f;
|
||||
fill.setStyle(Paint.Style.FILL);
|
||||
fill.setColor(BG);
|
||||
canvas.drawRect(0, 0, w, getHeight(), fill);
|
||||
|
||||
text.setTextAlign(Paint.Align.CENTER);
|
||||
text.setFakeBoldText(false);
|
||||
text.setTextSize(11 * density);
|
||||
text.setColor(WEEKDAY);
|
||||
for (int i = 0; i < 7; i++) canvas.drawText(weekdays[i], cw * i + cw / 2, top - 4 * density, text);
|
||||
|
||||
float numSize = Math.min(14 * density, ch * 0.45f);
|
||||
float mark = Math.max(2, 3 * density);
|
||||
for (int i = 0; i < 42; i++) {
|
||||
float x0 = cw * (i % 7), y0 = top + ch * (i / 7);
|
||||
long d = days[i];
|
||||
if (d == selected) {
|
||||
fill.setColor(SELECTED);
|
||||
canvas.drawRect(x0, y0, x0 + cw, y0 + ch, fill);
|
||||
}
|
||||
boolean isToday = d == today, here = inMonth(d);
|
||||
Calendar c = Calendar.getInstance();
|
||||
c.setTimeInMillis(d);
|
||||
String num = String.valueOf(c.get(Calendar.DAY_OF_MONTH));
|
||||
text.setTextSize(numSize);
|
||||
text.setFakeBoldText(isToday);
|
||||
text.setColor(isToday ? TODAY : here ? NUM : OTHER);
|
||||
float ty = y0 + ch * 0.12f - text.ascent();
|
||||
canvas.drawText(num, x0 + cw / 2, ty, text);
|
||||
if (isToday) {
|
||||
fill.setStyle(Paint.Style.STROKE);
|
||||
fill.setStrokeWidth(Math.max(1, 1.5f * density));
|
||||
fill.setColor(TODAY);
|
||||
canvas.drawRect(x0 + 1, y0 + 1, x0 + cw - 1, y0 + ch - 1, fill);
|
||||
fill.setStyle(Paint.Style.FILL);
|
||||
}
|
||||
List<Integer> m = marks.get(Long.valueOf(d));
|
||||
if (m != null && !m.isEmpty()) {
|
||||
int n = Math.min(4, m.size());
|
||||
float gap = 2 * density, mw = Math.min(6 * density, (cw - gap * 5) / 4);
|
||||
float total = n * mw + (n - 1) * gap, mx = x0 + (cw - total) / 2;
|
||||
float my = Math.min(y0 + ch - mark - 3 * density, ty + text.descent() + 2 * density);
|
||||
for (int k = 0; k < n; k++) {
|
||||
fill.setColor(here ? m.get(k).intValue() : (m.get(k).intValue() & 0x00FFFFFF) | 0x77000000);
|
||||
canvas.drawRect(mx, my, mx + mw, my + mark, fill);
|
||||
mx += mw + gap;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (int r = 0; r <= 6; r++) canvas.drawLine(0, top + ch * r, w, top + ch * r, line);
|
||||
}
|
||||
}
|
||||
@@ -51,7 +51,7 @@ public final class Reminders {
|
||||
PendingIntent pi = PendingIntent.getActivity(c, 0, open, 0);
|
||||
Notification n = new Notification(icon, e.title, e.start);
|
||||
String body = App.when(c, e) + (e.location.length() > 0 ? " · " + e.location : "");
|
||||
n.setLatestEventInfo(c, e.title.length() > 0 ? e.title : "(no title)", body, pi);
|
||||
n.setLatestEventInfo(c, e.title.length() > 0 ? e.title : L.t("(no title)"), body, pi);
|
||||
n.flags |= 16; // FLAG_AUTO_CANCEL
|
||||
n.defaults = -1; // DEFAULT_ALL: sound, vibrate, lights
|
||||
nm.notify((e.href + e.start).hashCode(), n);
|
||||
|
||||
@@ -6,6 +6,7 @@ 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;
|
||||
@@ -20,33 +21,33 @@ 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;
|
||||
Button intervalBtn, remindBtn, langBtn;
|
||||
int interval, remind;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle b) {
|
||||
super.onCreate(b);
|
||||
setTitle("GingerCal settings");
|
||||
setTitle(L.t("GingerCal settings"));
|
||||
LinearLayout f = App.vbox(this);
|
||||
int p = App.dp(this, 8);
|
||||
f.setPadding(p, p, p, p);
|
||||
f.addView(App.text(this, "Nextcloud server. Android 2.3 can't speak TLS 1.2, so use either your "
|
||||
+ "legacy-TLS bridge (https://cloud.example.com:8443) or http:// to the server on your home "
|
||||
+ "Wi-Fi. Use an app password, not your real one.", 12, 0xFFB0BEC5));
|
||||
url = field(f, "Server address", "https://cloud.example.com:8443", 1 | 0x10 /* URI */);
|
||||
user = field(f, "User name", "", 1 | 0x80000 /* no suggestions */);
|
||||
pass = field(f, "App password", "", 1 | 0x80 /* password */);
|
||||
pin = field(f, "Pinned certificate SHA-256 (filled in for you)", "optional", 1 | 0x80000);
|
||||
ahead = field(f, "Days ahead to sync", "60", 2 /* number */);
|
||||
f.addView(App.text(this, L.t("Nextcloud server. Android 2.3 can't speak TLS 1.2, so use either your legacy-TLS bridge (https://cloud.example.com:8443) or http:// to the server on your home Wi-Fi. Use an app password, not your real one."), 12, 0xFFB0BEC5));
|
||||
url = field(f, L.t("Server address"), "https://cloud.example.com:8443", 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("Save and test connection");
|
||||
test.setText(L.t("Save and test connection"));
|
||||
f.addView(test, new LinearLayout.LayoutParams(-1, -2));
|
||||
Button resync = new Button(this);
|
||||
resync.setText("Re-download everything");
|
||||
resync.setText(L.t("Re-download everything"));
|
||||
f.addView(resync, new LinearLayout.LayoutParams(-1, -2));
|
||||
ScrollView sv = new ScrollView(this);
|
||||
sv.addView(f);
|
||||
@@ -65,7 +66,7 @@ public class SettingsActivity extends Activity {
|
||||
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("Background sync")
|
||||
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];
|
||||
@@ -78,8 +79,8 @@ public class SettingsActivity extends Activity {
|
||||
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 ? "Off" : App.alarmLabel(opts[i]);
|
||||
new AlertDialog.Builder(SettingsActivity.this).setTitle("Phone notification")
|
||||
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];
|
||||
@@ -88,6 +89,22 @@ public class SettingsActivity extends Activity {
|
||||
}).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;
|
||||
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();
|
||||
@@ -98,7 +115,7 @@ public class SettingsActivity extends Activity {
|
||||
save();
|
||||
Db.get(SettingsActivity.this).forgetSyncState();
|
||||
App.requestSync(SettingsActivity.this, true, true);
|
||||
App.toast(SettingsActivity.this, "Re-downloading in the background");
|
||||
App.toast(SettingsActivity.this, L.t("Re-downloading in the background"));
|
||||
finish();
|
||||
}
|
||||
});
|
||||
@@ -117,12 +134,13 @@ public class SettingsActivity extends Activity {
|
||||
}
|
||||
|
||||
static String intervalLabel(int h) {
|
||||
return h == 0 ? "Manual only" : h == 1 ? "Every hour" : "Every " + h + " hours";
|
||||
return h == 0 ? L.t("Manual only") : h == 1 ? L.t("Every hour") : L.f("Every %d hours", h);
|
||||
}
|
||||
|
||||
void labels() {
|
||||
intervalBtn.setText("Background sync: " + intervalLabel(interval));
|
||||
remindBtn.setText("Notify: " + (remind < 0 ? "off" : App.alarmLabel(remind)));
|
||||
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) {
|
||||
@@ -155,7 +173,8 @@ public class SettingsActivity extends Activity {
|
||||
.commit();
|
||||
if (!oldAccount.equals(u + "|" + txt(user))) {
|
||||
Db.get(this).clearAll();
|
||||
App.prefs(this).edit().putLong("lastsync", 0).putString("status", "").commit();
|
||||
App.prefs(this).edit().putLong("lastsync", 0).putLong("win_from", 0).putLong("win_to", 0)
|
||||
.putString("status", "").commit();
|
||||
}
|
||||
App.scheduleSync(this);
|
||||
Reminders.schedule(this);
|
||||
@@ -164,20 +183,19 @@ public class SettingsActivity extends Activity {
|
||||
void confirmAndTest() {
|
||||
String u = txt(url);
|
||||
if (u.length() == 0 || txt(user).length() == 0) {
|
||||
App.toast(this, "Server address and user name are required.");
|
||||
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("Unencrypted connection")
|
||||
.setMessage("http:// sends your password in clear text over the internet. Use the legacy-TLS "
|
||||
+ "bridge instead, or only use http:// to a LAN address.")
|
||||
.setPositiveButton("Use anyway", new DialogInterface.OnClickListener() {
|
||||
new AlertDialog.Builder(this).setTitle(L.t("Unencrypted connection"))
|
||||
.setMessage(L.t("http:// sends your password in clear text over the internet. Use the legacy-TLS bridge 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("Cancel", null).show();
|
||||
.setNegativeButton(L.t("Cancel"), null).show();
|
||||
return;
|
||||
}
|
||||
save();
|
||||
@@ -189,7 +207,7 @@ public class SettingsActivity extends Activity {
|
||||
|
||||
@Override
|
||||
protected void onPreExecute() {
|
||||
pd = ProgressDialog.show(SettingsActivity.this, null, "Connecting…");
|
||||
pd = ProgressDialog.show(SettingsActivity.this, null, L.t("Connecting\u2026"));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -223,16 +241,16 @@ public class SettingsActivity extends Activity {
|
||||
return;
|
||||
}
|
||||
if (r instanceof Throwable) {
|
||||
new AlertDialog.Builder(SettingsActivity.this).setTitle("Connection failed")
|
||||
.setMessage(App.describe((Throwable) r)).setPositiveButton("OK", null).show();
|
||||
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("Connected. Found " + cals.size() + " calendar(s):\n");
|
||||
for (Cal c : cals) s.append("\n• ").append(c.name).append(c.writable ? "" : " (read-only)");
|
||||
new AlertDialog.Builder(SettingsActivity.this).setTitle("It works")
|
||||
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("Sync now", new DialogInterface.OnClickListener() {
|
||||
.setPositiveButton(L.t("Sync now"), new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface d, int w) {
|
||||
App.requestSync(SettingsActivity.this, true, false);
|
||||
finish();
|
||||
|
||||
@@ -26,8 +26,9 @@ public class SyncService extends IntentService {
|
||||
send("start", null, null);
|
||||
dav = App.dav(this);
|
||||
long now = System.currentTimeMillis();
|
||||
Syncer.Result r = Syncer.sync(dav, Db.get(this), now, 1, App.ahead(this), force);
|
||||
p.edit().putLong("lastsync", now).putString("status", r.summary()).putBoolean("error", false).commit();
|
||||
Syncer.Result r = Syncer.sync(dav, Db.get(this), now, App.BACK_DAYS, App.ahead(this), force);
|
||||
p.edit().putLong("lastsync", now).putString("status", r.summary()).putBoolean("error", false)
|
||||
.putLong("win_from", r.from).putLong("win_to", r.to).commit();
|
||||
send("done", r.summary(), null);
|
||||
} catch (DavException.UntrustedCert u) {
|
||||
p.edit().putString("status", u.getMessage()).putBoolean("error", true).commit();
|
||||
|
||||
Reference in new issue
Block a user