425 lines
15 KiB
Java
425 lines
15 KiB
Java
package org.gingercal;
|
|
|
|
import java.util.ArrayList;
|
|
import java.util.Calendar;
|
|
import java.util.List;
|
|
import java.util.UUID;
|
|
|
|
import android.app.Activity;
|
|
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;
|
|
import android.widget.Button;
|
|
import android.widget.CheckBox;
|
|
import android.widget.DatePicker;
|
|
import android.widget.EditText;
|
|
import android.widget.LinearLayout;
|
|
import android.widget.ScrollView;
|
|
import android.widget.TextView;
|
|
import android.widget.TimePicker;
|
|
|
|
import org.gingercal.Model.Cal;
|
|
import org.gingercal.Model.Ev;
|
|
|
|
/** Create or edit a single (non-repeating) event. */
|
|
public class EditActivity extends Activity {
|
|
EditText title, location, notes;
|
|
CheckBox allDay;
|
|
Button startDate, startTime, endDate, endTime, alarmBtn, calBtn, save;
|
|
final Calendar start = Calendar.getInstance(), end = Calendar.getInstance();
|
|
int alarm = 15, originalAlarm = -3;
|
|
String calHref;
|
|
Ev editing;
|
|
String ics, etag;
|
|
List<Cal> cals = new ArrayList<Cal>();
|
|
|
|
@Override
|
|
protected void onCreate(Bundle b) {
|
|
super.onCreate(b);
|
|
// 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, L.t("Title"), false);
|
|
allDay = new CheckBox(this);
|
|
allDay.setText(L.t("All day"));
|
|
allDay.setOnClickListener(new View.OnClickListener() {
|
|
public void onClick(View v) {
|
|
refresh();
|
|
}
|
|
});
|
|
f.addView(allDay);
|
|
f.addView(label(L.t("Starts")));
|
|
startDate = new Button(this);
|
|
startTime = new Button(this);
|
|
f.addView(pair(startDate, startTime));
|
|
f.addView(label(L.t("Ends")));
|
|
endDate = new Button(this);
|
|
endTime = new Button(this);
|
|
f.addView(pair(endDate, endTime));
|
|
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(L.t("Save"));
|
|
f.addView(save, new LinearLayout.LayoutParams(-1, -2));
|
|
ScrollView sv = new ScrollView(this);
|
|
sv.addView(f);
|
|
setContentView(sv);
|
|
|
|
startDate.setOnClickListener(new View.OnClickListener() {
|
|
public void onClick(View v) {
|
|
pickDate(start, true);
|
|
}
|
|
});
|
|
endDate.setOnClickListener(new View.OnClickListener() {
|
|
public void onClick(View v) {
|
|
pickDate(end, false);
|
|
}
|
|
});
|
|
startTime.setOnClickListener(new View.OnClickListener() {
|
|
public void onClick(View v) {
|
|
pickTime(start, true);
|
|
}
|
|
});
|
|
endTime.setOnClickListener(new View.OnClickListener() {
|
|
public void onClick(View v) {
|
|
pickTime(end, false);
|
|
}
|
|
});
|
|
alarmBtn.setOnClickListener(new View.OnClickListener() {
|
|
public void onClick(View v) {
|
|
pickAlarm();
|
|
}
|
|
});
|
|
calBtn.setOnClickListener(new View.OnClickListener() {
|
|
public void onClick(View v) {
|
|
pickCalendar();
|
|
}
|
|
});
|
|
save.setOnClickListener(new View.OnClickListener() {
|
|
public void onClick(View v) {
|
|
save();
|
|
}
|
|
});
|
|
|
|
if (editing == null) {
|
|
long now = System.currentTimeMillis();
|
|
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);
|
|
String def = App.get(this, "defcal");
|
|
calHref = cals.isEmpty() ? null : cals.get(0).href;
|
|
for (Cal c : cals) if (c.href.equals(def)) calHref = def;
|
|
refresh();
|
|
} else {
|
|
calHref = editing.cal;
|
|
calBtn.setEnabled(false);
|
|
fill(editing.title, editing.location, editing.descr, editing.start, editing.end, editing.allDay);
|
|
save.setEnabled(false);
|
|
new LoadTask().execute();
|
|
}
|
|
}
|
|
|
|
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);
|
|
if (multi) {
|
|
e.setInputType(1 | 0x4000 | 0x20000); // text | cap sentences | multi-line
|
|
e.setMinLines(3);
|
|
} else {
|
|
e.setSingleLine(true);
|
|
e.setInputType(1 | 0x4000);
|
|
}
|
|
f.addView(e, new LinearLayout.LayoutParams(-1, -2));
|
|
return e;
|
|
}
|
|
|
|
TextView label(String s) {
|
|
TextView t = App.text(this, s, 13, 0xFFB0BEC5);
|
|
t.setPadding(App.dp(this, 4), App.dp(this, 6), 0, 0);
|
|
return t;
|
|
}
|
|
|
|
LinearLayout pair(Button a, Button b) {
|
|
LinearLayout l = new LinearLayout(this);
|
|
l.setOrientation(LinearLayout.HORIZONTAL);
|
|
l.addView(a, new LinearLayout.LayoutParams(0, -2, 3f));
|
|
l.addView(b, new LinearLayout.LayoutParams(0, -2, 2f));
|
|
return l;
|
|
}
|
|
|
|
void fill(String t, String loc, String desc, long s, long e, boolean ad) {
|
|
title.setText(t);
|
|
location.setText(loc);
|
|
notes.setText(desc);
|
|
allDay.setChecked(ad);
|
|
start.setTimeInMillis(s);
|
|
// all-day DTEND is exclusive; show the last day instead
|
|
end.setTimeInMillis(ad ? Math.max(s, ICal.addDays(e, -1)) : e);
|
|
refresh();
|
|
}
|
|
|
|
void refresh() {
|
|
boolean ad = allDay.isChecked();
|
|
startDate.setText(App.day(start.getTimeInMillis()));
|
|
endDate.setText(App.day(end.getTimeInMillis()));
|
|
startTime.setText(App.time(this, start.getTimeInMillis()));
|
|
endTime.setText(App.time(this, end.getTimeInMillis()));
|
|
startTime.setVisibility(ad ? View.GONE : View.VISIBLE);
|
|
endTime.setVisibility(ad ? View.GONE : View.VISIBLE);
|
|
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(L.f("Calendar: %s", name));
|
|
}
|
|
|
|
void pickDate(final Calendar c, final boolean isStart) {
|
|
new DatePickerDialog(this, new DatePickerDialog.OnDateSetListener() {
|
|
public void onDateSet(DatePicker v, int y, int m, int d) {
|
|
long dur = end.getTimeInMillis() - start.getTimeInMillis();
|
|
c.set(y, m, d);
|
|
if (isStart) end.setTimeInMillis(start.getTimeInMillis() + Math.max(0, dur));
|
|
refresh();
|
|
}
|
|
}, c.get(Calendar.YEAR), c.get(Calendar.MONTH), c.get(Calendar.DAY_OF_MONTH)).show();
|
|
}
|
|
|
|
void pickTime(final Calendar c, final boolean isStart) {
|
|
new TimePickerDialog(this, new TimePickerDialog.OnTimeSetListener() {
|
|
public void onTimeSet(TimePicker v, int h, int m) {
|
|
long dur = end.getTimeInMillis() - start.getTimeInMillis();
|
|
c.set(Calendar.HOUR_OF_DAY, h);
|
|
c.set(Calendar.MINUTE, m);
|
|
if (isStart) end.setTimeInMillis(start.getTimeInMillis() + Math.max(0, dur));
|
|
refresh();
|
|
}
|
|
}, c.get(Calendar.HOUR_OF_DAY), c.get(Calendar.MINUTE),
|
|
android.text.format.DateFormat.is24HourFormat(this)).show();
|
|
}
|
|
|
|
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(L.t("Alarm (saved in the event)"))
|
|
.setItems(labels, new DialogInterface.OnClickListener() {
|
|
public void onClick(DialogInterface d, int w) {
|
|
alarm = App.ALARMS[w];
|
|
refresh();
|
|
}
|
|
}).show();
|
|
}
|
|
|
|
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(L.t("Calendar"))
|
|
.setItems(names, new DialogInterface.OnClickListener() {
|
|
public void onClick(DialogInterface d, int w) {
|
|
calHref = cals.get(w).href;
|
|
refresh();
|
|
}
|
|
}).show();
|
|
}
|
|
|
|
static String txt(TextView t) {
|
|
return t.getText().toString().trim();
|
|
}
|
|
|
|
void save() {
|
|
boolean ad = allDay.isChecked();
|
|
long s, e;
|
|
if (ad) {
|
|
s = ICal.startOfDay(start.getTimeInMillis());
|
|
e = ICal.addDays(ICal.startOfDay(end.getTimeInMillis()), 1);
|
|
} else {
|
|
s = start.getTimeInMillis() - start.getTimeInMillis() % 60000L;
|
|
e = end.getTimeInMillis() - end.getTimeInMillis() % 60000L;
|
|
}
|
|
if (e < s || (ad && e <= s)) {
|
|
App.toast(this, L.t("The end is before the start."));
|
|
return;
|
|
}
|
|
if (calHref == null) {
|
|
App.toast(this, L.t("No writable calendar. Sync first."));
|
|
return;
|
|
}
|
|
String t = txt(title);
|
|
new SaveTask(t.length() > 0 ? t : L.t("(no title)"), txt(location), notes.getText().toString().trim(), s, e, ad).execute();
|
|
}
|
|
|
|
// ----------------------------------------------------------- network
|
|
|
|
class LoadTask extends AsyncTask<Void, Void, Object> {
|
|
ProgressDialog pd;
|
|
|
|
@Override
|
|
protected void onPreExecute() {
|
|
pd = ProgressDialog.show(EditActivity.this, null, L.t("Loading\u2026"));
|
|
}
|
|
|
|
@Override
|
|
protected Object doInBackground(Void... v) {
|
|
Dav d = null;
|
|
try {
|
|
d = App.dav(EditActivity.this);
|
|
return d.get(editing.href);
|
|
} catch (Throwable t) {
|
|
return t;
|
|
} finally {
|
|
if (d != null) d.close();
|
|
}
|
|
}
|
|
|
|
@Override
|
|
protected void onPostExecute(Object r) {
|
|
try {
|
|
pd.dismiss();
|
|
} catch (RuntimeException ignored) {
|
|
}
|
|
if (r instanceof Throwable) {
|
|
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(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(L.t("Can't edit"), L.t("The server returned no event data."));
|
|
return;
|
|
}
|
|
ICal.VEvent v = vs.get(0);
|
|
alarm = v.alarmMin;
|
|
originalAlarm = v.alarmMin;
|
|
fill(v.summary, v.location, v.description, v.start, v.end, v.allDay);
|
|
save.setEnabled(true);
|
|
}
|
|
}
|
|
|
|
void fail(String t, String msg) {
|
|
new AlertDialog.Builder(this).setTitle(t).setMessage(msg)
|
|
.setPositiveButton(L.t("OK"), new DialogInterface.OnClickListener() {
|
|
public void onClick(DialogInterface d, int w) {
|
|
finish();
|
|
}
|
|
}).show();
|
|
}
|
|
|
|
class SaveTask extends AsyncTask<Void, Void, Throwable> {
|
|
final String t, loc, desc;
|
|
final long s, e;
|
|
final boolean ad;
|
|
ProgressDialog pd;
|
|
|
|
SaveTask(String t, String loc, String desc, long s, long e, boolean ad) {
|
|
this.t = t;
|
|
this.loc = loc;
|
|
this.desc = desc;
|
|
this.s = s;
|
|
this.e = e;
|
|
this.ad = ad;
|
|
}
|
|
|
|
@Override
|
|
protected void onPreExecute() {
|
|
pd = ProgressDialog.show(EditActivity.this, null, L.t("Saving\u2026"));
|
|
}
|
|
|
|
@Override
|
|
protected Throwable doInBackground(Void... v) {
|
|
Dav d = null;
|
|
try {
|
|
d = App.dav(EditActivity.this);
|
|
if (editing == null) {
|
|
String uid = UUID.randomUUID().toString().toUpperCase();
|
|
d.create(calHref, uid, ICal.newEvent(uid, t, loc, desc, s, e, ad, alarm));
|
|
} else {
|
|
int a = alarm == originalAlarm ? -2 : alarm;
|
|
d.update(editing.href, etag, ICal.modify(ics, t, loc, desc, s, e, ad, a));
|
|
}
|
|
return null;
|
|
} catch (Throwable x) {
|
|
return x;
|
|
} finally {
|
|
if (d != null) d.close();
|
|
}
|
|
}
|
|
|
|
@Override
|
|
protected void onPostExecute(Throwable x) {
|
|
try {
|
|
pd.dismiss();
|
|
} catch (RuntimeException ignored) {
|
|
}
|
|
if (x != null) {
|
|
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, L.t("Saved"));
|
|
finish();
|
|
}
|
|
}
|
|
}
|