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 cals = new ArrayList(); @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 { 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 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 { 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(); } } }