package org.gingercal; import java.util.ArrayList; import java.util.List; import android.app.Activity; import android.app.AlertDialog; import android.app.ProgressDialog; import android.content.BroadcastReceiver; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.IntentFilter; import android.os.AsyncTask; import android.os.Bundle; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; import android.widget.AdapterView; import android.widget.BaseAdapter; import android.widget.LinearLayout; import android.widget.ListView; 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; TextView status; ListView list; final List rows = new ArrayList(); final Agenda adapter = new Agenda(); boolean askedSetup; final BroadcastReceiver rx = new BroadcastReceiver() { @Override public void onReceive(Context c, Intent i) { String state = i.getStringExtra("state"); if ("start".equals(state)) { showStatus(); return; } reload(); String fp = i.getStringExtra("fp"); if (fp != null) { DavException.UntrustedCert u = new DavException.UntrustedCert(fp, i.getStringExtra("subject"), i.getStringExtra("issuer"), i.getStringExtra("expires"), i.getBooleanExtra("mismatch", false)); App.askTrust(MainActivity.this, u, new App.Trusted() { public void trusted(String f) { App.prefs(MainActivity.this).edit().putString("pin", f).commit(); App.requestSync(MainActivity.this, true, false); } }); } } }; @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); 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() { 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); } @Override protected void onResume() { super.onResume(); registerReceiver(rx, new IntentFilter(SyncService.STATE)); if (!App.configured(this)) { showStatus(); if (!askedSetup) { askedSetup = true; startActivity(new Intent(this, SettingsActivity.class)); } return; } reload(); long last = App.prefs(this).getLong("lastsync", 0); if (!SyncService.running && System.currentTimeMillis() - last > 15 * ICal.MIN) { App.requestSync(this, true, false); } } @Override protected void onPause() { super.onPause(); unregisterReceiver(rx); } void showStatus() { if (!App.configured(this)) { status.setText("Not set up yet: press Menu > Settings."); return; } if (SyncService.running) { status.setText("Syncing…"); status.setTextColor(0xFFAAAAAA); 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); if (err) { status.setText("Sync failed: " + s + "\nLast good sync: " + when); status.setTextColor(0xFFFF8A80); } else { status.setText("Synced " + when + (s.length() > 0 ? " · " + s : "")); status.setTextColor(0xFFAAAAAA); } } void reload() { rows.clear(); long today = ICal.startOfDay(System.currentTimeMillis()); List 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)); lastDay = d; } rows.add(e); } 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"); return true; } void icon(MenuItem i, String name) { int id = App.sysDrawable(this, name); if (id != 0) i.setIcon(id); } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case M_SYNC: if (App.configured(this)) 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)); } return true; case M_CALS: startActivity(new Intent(this, CalendarsActivity.class)); return true; case M_SETTINGS: startActivity(new Intent(this, SettingsActivity.class)); return true; } return false; } List writableCalendars() { List out = new ArrayList(); for (Cal c : Db.get(this).calendarList()) if (c.enabled && c.writable) out.add(c); return out; } // -------------------------------------------------------------- details void showEvent(final Ev e) { StringBuilder m = new StringBuilder(); m.append(App.day(e.start)).append("\n").append(App.when(this, e)); 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."); AlertDialog.Builder b = new AlertDialog.Builder(this) .setTitle(e.title.length() > 0 ? e.title : "(no title)") .setMessage(m.toString()) .setNegativeButton("Close", null); if (e.writable && !e.recurring) { b.setPositiveButton("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); } }); } if (e.writable) { b.setNeutralButton("Delete", new DialogInterface.OnClickListener() { public void onClick(DialogInterface d, int w) { confirmDelete(e); } }); } b.show(); } 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() { public void onClick(DialogInterface d, int w) { new DeleteTask(e).execute(); } }) .setNegativeButton("Cancel", null) .show(); } class DeleteTask extends AsyncTask { final Ev e; ProgressDialog pd; DeleteTask(Ev e) { this.e = e; } @Override protected void onPreExecute() { pd = ProgressDialog.show(MainActivity.this, null, "Deleting…"); } @Override protected Throwable doInBackground(Void... v) { Dav d = null; try { d = App.dav(MainActivity.this); d.delete(e.href, e.etag); return null; } catch (Throwable t) { return t; } finally { if (d != null) d.close(); } } @Override protected void onPostExecute(Throwable t) { try { pd.dismiss(); } catch (RuntimeException ignored) { } if (t != null) { new AlertDialog.Builder(MainActivity.this).setTitle("Delete failed") .setMessage(App.describe(t)).setPositiveButton("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); } adapter.notifyDataSetChanged(); } } // ------------------------------------------------------------- adapter static class Holder { View bar; TextView time, title, where; } class Agenda extends BaseAdapter { public int getCount() { return rows.size(); } public Object getItem(int p) { return rows.get(p); } public long getItemId(int p) { return p; } @Override public int getViewTypeCount() { return 2; } @Override public int getItemViewType(int p) { return rows.get(p) instanceof Ev ? 1 : 0; } @Override public boolean areAllItemsEnabled() { return false; } @Override public boolean isEnabled(int p) { return rows.get(p) instanceof Ev; } public View getView(int p, View convert, ViewGroup parent) { Object o = rows.get(p); 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); h.setPadding(pad * 2, pad, pad, pad); h.setBackgroundColor(0xFF2E3B44); h.setText(o instanceof Long ? App.dayHeader(((Long) o).longValue()) : String.valueOf(o)); return h; } Ev e = (Ev) o; Holder hd; View v = convert; if (v == null || !(v.getTag() instanceof Holder)) { hd = new Holder(); LinearLayout row = new LinearLayout(c); row.setOrientation(LinearLayout.HORIZONTAL); 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); col.setPadding(pad * 2, pad, pad, pad); hd.time = App.text(c, "", 12, 0xFFB0BEC5); hd.title = App.text(c, "", 16, 0xFFFFFFFF); hd.where = App.text(c, "", 12, 0xFF90A4AE); col.addView(hd.time); col.addView(hd.title); col.addView(hd.where); row.addView(col, new LinearLayout.LayoutParams(-1, -2)); row.setTag(hd); v = row; } else { 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.where.setText(e.location); hd.where.setVisibility(e.location.length() > 0 ? View.VISIBLE : View.GONE); return v; } } }