Files
calendar-lite/android/org/gingercal/EditActivity.java
T
2026-10-15 16:35:05 +02:00

384 lines
14 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.DialogInterface;
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);
long id = getIntent().getLongExtra("id", -1);
if (id >= 0) editing = Db.get(this).event(id);
setTitle(editing == null ? "New event" : "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);
allDay = new CheckBox(this);
allDay.setText("All day");
allDay.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
refresh();
}
});
f.addView(allDay);
f.addView(label("Starts"));
startDate = new Button(this);
startTime = new Button(this);
f.addView(pair(startDate, startTime));
f.addView(label("Ends"));
endDate = new Button(this);
endTime = new Button(this);
f.addView(pair(endDate, endTime));
location = field(f, "Location", false);
notes = field(f, "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");
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();
start.setTimeInMillis(now - now % ICal.HOUR + ICal.HOUR);
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();
}
}
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("Alarm: " + App.alarmLabel(alarm));
String name = "?";
for (Cal c : Db.get(this).calendarList()) if (c.href.equals(calHref)) name = c.name;
calBtn.setText("Calendar: " + 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("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("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, "The end is before the start.");
return;
}
if (calHref == null) {
App.toast(this, "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();
}
// ----------------------------------------------------------- network
class LoadTask extends AsyncTask<Void, Void, Object> {
ProgressDialog pd;
@Override
protected void onPreExecute() {
pd = ProgressDialog.show(EditActivity.this, null, "Loading…");
}
@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("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.");
return;
}
List<ICal.VEvent> vs = ICal.parse(ics);
if (vs.isEmpty()) {
fail("Can't edit", "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("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, "Saving…");
}
@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("Not saved")
.setMessage(App.describe(x)).setPositiveButton("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");
finish();
}
}
}