package com.vaguehope.onosendai.ui.pref; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; import org.json.JSONException; import android.app.AlertDialog; import android.content.Context; import android.content.DialogInterface; import android.view.LayoutInflater; import android.view.View; import android.view.View.OnClickListener; import android.view.View.OnLongClickListener; import android.widget.ArrayAdapter; import android.widget.Button; import android.widget.CheckBox; import android.widget.EditText; import android.widget.Spinner; import android.widget.TableRow; import com.vaguehope.onosendai.R; import com.vaguehope.onosendai.config.Account; import com.vaguehope.onosendai.config.Column; import com.vaguehope.onosendai.config.ColumnFeed; import com.vaguehope.onosendai.config.InlineMediaStyle; import com.vaguehope.onosendai.config.NotificationStyle; import com.vaguehope.onosendai.config.Prefs; import com.vaguehope.onosendai.ui.pref.ColumnChooser.ColumnChoiceListener; import com.vaguehope.onosendai.util.CollectionHelper; import com.vaguehope.onosendai.util.DialogHelper; import com.vaguehope.onosendai.util.DialogHelper.Listener; import com.vaguehope.onosendai.util.DialogHelper.Question; import com.vaguehope.onosendai.util.Titleable; class ColumnDialog { private static final List<Duration> REFRESH_DURAITONS = CollectionHelper.listOf( new Duration(0, "Never"), //ES new Duration(5, "5 minutes (15 min in background)"), //ES new Duration(15, "15 minutes"), //ES new Duration(30, "30 minutes"), //ES new Duration(45, "45 minutes"), //ES new Duration(60, "1 hour"), //ES new Duration(60 * 2, "2 hours"), //ES new Duration(60 * 3, "3 hours"), //ES new Duration(60 * 6, "6 hours"), //ES new Duration(60 * 12, "12 hours"), //ES new Duration(60 * 24, "24 hours") //ES ); private final Context context; private final Map<Integer, Column> allColumns; private final int id; private final Column initialValue; private final Prefs prefs; private final View llParent; private final EditText txtTitle; private final Spinner spnPosition; private final Button btnFeeds; private final Button btnExclude; private final Spinner spnRefresh; private final Spinner spnInlineMedia; private final CheckBox chkHdMedia; private final CheckBox chkNotify; private final CheckBox chkDelete; private final TableRow rowNotification; private final Button btnNotification; private final Set<ColumnFeed> feeds = new LinkedHashSet<ColumnFeed>(); private final Set<Integer> excludes = new HashSet<Integer>(); private NotificationStyle notificationStyle = null; public ColumnDialog (final Context context, final Prefs prefs, final int id) { this(context, prefs, id, null); } public ColumnDialog (final Context context, final Prefs prefs, final Column initialValue) { this(context, prefs, initialValue != null ? initialValue.getId() : null, initialValue); } private ColumnDialog (final Context context, final Prefs prefs, final int id, final Column initialValue) { this.context = context; if (prefs == null) throw new IllegalArgumentException("Prefs can not be null."); if (initialValue != null && initialValue.getId() != id) throw new IllegalStateException("ID and initialValue ID do not match."); this.id = id; this.initialValue = initialValue; this.prefs = prefs; try { this.allColumns = Collections.unmodifiableMap(prefs.readColumnsAsMap()); } catch (final JSONException e) { throw new IllegalStateException(e); // FIXME } final LayoutInflater inflater = LayoutInflater.from(context); this.llParent = inflater.inflate(R.layout.columndialog, null); this.txtTitle = (EditText) this.llParent.findViewById(R.id.txtTitle); this.spnPosition = (Spinner) this.llParent.findViewById(R.id.spnPosition); this.btnFeeds = (Button) this.llParent.findViewById(R.id.btnFeeds); this.btnExclude = (Button) this.llParent.findViewById(R.id.btnExclude); this.spnRefresh = (Spinner) this.llParent.findViewById(R.id.spnRefresh); this.spnInlineMedia = (Spinner) this.llParent.findViewById(R.id.spnInlineMedia); this.chkHdMedia = (CheckBox) this.llParent.findViewById(R.id.chkHdMedia); this.chkNotify = (CheckBox) this.llParent.findViewById(R.id.chkNotify); this.chkDelete = (CheckBox) this.llParent.findViewById(R.id.chkDelete); this.rowNotification = (TableRow) this.llParent.findViewById(R.id.rowNotification); this.btnNotification = (Button) this.llParent.findViewById(R.id.btnNotification); final ArrayAdapter<Integer> posAdapter = new ArrayAdapter<Integer>(context, R.layout.numberspinneritem); posAdapter.addAll(CollectionHelper.sequence(1, prefs.readColumnIds().size() + (initialValue == null ? 1 : 0))); this.spnPosition.setAdapter(posAdapter); this.btnFeeds.setOnClickListener(this.btnFeedsClickListener); this.btnFeeds.setOnLongClickListener(this.btnFeedsLongClickListener); this.btnExclude.setOnClickListener(this.btnExcludeClickListener); this.chkNotify.setOnClickListener(this.chkNotifyClickListener); this.btnNotification.setOnClickListener(this.btnNotificationClickListener); final ArrayAdapter<Duration> refAdapter = new ArrayAdapter<Duration>(context, R.layout.numberspinneritem); refAdapter.addAll(REFRESH_DURAITONS); this.spnRefresh.setAdapter(refAdapter); final ArrayAdapter<InlineMediaStyle> inlineMediaAdapter = new ArrayAdapter<InlineMediaStyle>(context, R.layout.numberspinneritem); inlineMediaAdapter.addAll(InlineMediaStyle.values()); this.spnInlineMedia.setAdapter(inlineMediaAdapter); this.chkDelete.setChecked(false); if (initialValue != null) { this.txtTitle.setText(initialValue.getTitle()); this.spnPosition.setSelection(posAdapter.getPosition(Integer.valueOf(prefs.readColumnPosition(initialValue.getId()) + 1))); setFeeds(initialValue.getFeeds()); setExcludeIds(initialValue.getExcludeColumnIds()); setDurationSpinner(initialValue.getRefreshIntervalMins(), refAdapter); this.spnInlineMedia.setSelection(inlineMediaAdapter.getPosition(initialValue.getInlineMediaStyle())); this.chkHdMedia.setChecked(initialValue.isHdMedia()); setNotificationStyle(initialValue.getNotificationStyle()); this.chkDelete.setVisibility(View.VISIBLE); } else { this.spnPosition.setSelection(posAdapter.getCount() - 1); // Last item. setDurationSpinner(0, refAdapter); // Default to no background refresh. this.chkDelete.setVisibility(View.GONE); } } protected void setExcludeColumns (final Set<Column> excludes) { final Set<Integer> ids = new HashSet<Integer>(); for (final Column column : excludes) { ids.add(Integer.valueOf(column.getId())); } setExcludeIds(ids); } public void setFeeds (final Set<ColumnFeed> feeds) { this.feeds.clear(); if (feeds != null) this.feeds.addAll(feeds); redrawFeeds(); } public void addFeed (final ColumnFeed feed) { this.feeds.add(feed); redrawFeeds(); } public void removeFeed (final ColumnFeed feed) { this.feeds.remove(feed); redrawFeeds(); } public void replaceFeed (final ColumnFeed was, final ColumnFeed replaceWith) { // TODO could avoid reordering? this.feeds.remove(was); this.feeds.add(replaceWith); redrawFeeds(); } private void redrawFeeds () { this.btnFeeds.setText(String.format("Click to edit (%s)", this.feeds.size())); //ES this.spnRefresh.setEnabled(ColumnFeed.uniqAccountIds(this.feeds).size() > 0); } private void setExcludeIds (final Set<Integer> excludes) { this.excludes.clear(); if (excludes != null) this.excludes.addAll(excludes); redrawExcludes(); } private void redrawExcludes () { final StringBuilder s = new StringBuilder(); if (this.excludes.size() > 0) { for (final Integer exId : this.excludes) { if (s.length() > 0) s.append(", "); final Column exCol = this.allColumns.get(exId); s.append(exCol != null ? exCol.getUiTitle() : "(" + exId + ")"); } } else { s.append("(none)"); //ES } this.btnExclude.setText(s.toString()); } protected void setNotificationStyle (final NotificationStyle ns) { this.notificationStyle = ns; this.chkNotify.setChecked(ns != null); redrawNotificationStyle(); } protected void updateNotificationStyle () { if (this.chkNotify.isChecked()) { if (this.notificationStyle == null) this.notificationStyle = NotificationStyle.DEFAULT; } else { this.notificationStyle = null; } redrawNotificationStyle(); } private void redrawNotificationStyle () { this.rowNotification.setVisibility(this.notificationStyle != null ? View.VISIBLE : View.GONE); this.btnNotification.setText(this.notificationStyle != null ? this.notificationStyle.getUiTitle() : "false"); } private final OnClickListener btnFeedsClickListener = new OnClickListener() { @Override public void onClick (final View v) { btnFeedsClick(); } }; private final OnLongClickListener btnFeedsLongClickListener = new OnLongClickListener() { @Override public boolean onLongClick (final View v) { btnFeedsLongClick(); return true; } }; private final OnClickListener btnExcludeClickListener = new OnClickListener() { @Override public void onClick (final View v) { btnExcludeClick(); } }; private final OnClickListener chkNotifyClickListener = new OnClickListener() { @Override public void onClick (final View v) { updateNotificationStyle(); } }; private final OnClickListener btnNotificationClickListener = new OnClickListener() { @Override public void onClick (final View v) { btnNotificationClick(); } }; private static enum AddFeed implements Titleable { INSTANCE; @Override public String getUiTitle () { return "Add feed..."; //ES } } protected void btnFeedsClick () { final List<Titleable> items = new ArrayList<Titleable>(); items.add(AddFeed.INSTANCE); try { items.addAll(ColumnFeed.mixInAccountTitles(this.feeds, this.prefs.asConfig())); } catch (final JSONException e) { DialogHelper.alert(this.context, e); return; } DialogHelper.askItem(this.context, "Edit Feeds", items, new Listener<Titleable>() { //ES @Override public void onAnswer (final Titleable item) { if (item instanceof ColumnFeed) { showEditFeed((ColumnFeed) item); } else if (item == AddFeed.INSTANCE) { promptAddFeed(); } else { DialogHelper.alert(ColumnDialog.this.context, "Unknown item: " + item); } } }); } protected void promptAddFeed () { new ColumnChooser(this.context, this.prefs, new ColumnChoiceListener() { @Override public void onColumn (final Account account, final String newResource, final String newTitle) { if (newResource != null) addFeed(new ColumnFeed(account != null ? account.getId() : null, newResource)); } }).promptAddColumn(); } protected void showEditFeed (final ColumnFeed feed) { final Account account; try { account = this.prefs.readAccount(feed.getAccountId()); } catch (final JSONException e) { DialogHelper.alert(this.context, e); return; } new ColumnChooser(this.context, this.prefs, new ColumnChoiceListener() { @Override public void onColumn (final Account newAccount, final String newResource, final String newTitle) { if (newResource != null) replaceFeed(feed, new ColumnFeed(newAccount.getId(), newResource)); } }).promptAddColumn(account, feed.getResource()); } protected void btnFeedsLongClick () { final List<ColumnFeed> items; try { items = ColumnFeed.mixInAccountTitles(this.feeds, this.prefs.asConfig()); } catch (final JSONException e) { DialogHelper.alert(this.context, e); return; } DialogHelper.askItem(this.context, "Delete a Feed", items, new Listener<ColumnFeed>() { //ES @Override public void onAnswer (final ColumnFeed feed) { askDeleteFeed(feed); } }); } protected void askDeleteFeed (final ColumnFeed feed) { DialogHelper.askYesNo(this.context, "Delete feed?\n" + feed.getUiTitle(), "Delete", "Keep", new Runnable() { //ES @Override public void run () { removeFeed(feed); } }); } protected void btnExcludeClick () { final List<Column> columns = new ArrayList<Column>(this.allColumns.values()); final Iterator<Column> iterator = columns.iterator(); while (iterator.hasNext()) { if (this.id == iterator.next().getId()) iterator.remove(); } final Set<Integer> exes = this.excludes; DialogHelper.askItems(this.context, "Exclude items also in:", columns, new Question<Column>() { //ES @Override public boolean ask (final Column arg) { return exes.contains(Integer.valueOf(arg.getId())); } }, new Listener<Set<Column>>() { @Override public void onAnswer (final Set<Column> answer) { setExcludeColumns(answer); } }); } protected void btnNotificationClick () { final NotificationStyleDialog dlg = new NotificationStyleDialog(this.context, this.notificationStyle); final AlertDialog.Builder dlgBuilder = new AlertDialog.Builder(this.context); dlgBuilder.setTitle(dlg.getUiTitle()); dlgBuilder.setView(dlg.getRootView()); dlgBuilder.setPositiveButton(android.R.string.ok, new android.content.DialogInterface.OnClickListener() { @Override public void onClick (final DialogInterface dialog, final int which) { setNotificationStyle(dlg.getValue()); dialog.dismiss(); } }); dlgBuilder.setNegativeButton("Cancel", DialogHelper.DLG_CANCEL_CLICK_LISTENER); //ES dlgBuilder.create().show(); } private void setDurationSpinner (final int mins, final ArrayAdapter<Duration> refAdapter) { final Duration duration = new Duration(mins > 0 ? mins : 0); final int pos = refAdapter.getPosition(duration); if (pos < 0) refAdapter.add(duration); // Allow for any value to have been chosen before. this.spnRefresh.setSelection(pos < 0 ? refAdapter.getPosition(duration) : pos); } public Column getInitialValue () { return this.initialValue; } public View getRootView () { return this.llParent; } public void setTitle (final String title) { this.txtTitle.setText(title); } /** * 0 based. */ public int getPosition () { return ((Integer) this.spnPosition.getSelectedItem()) - 1; } public boolean isDeleteSelected () { return this.chkDelete.isChecked(); } public Column getValue () { return new Column(this.id, this.txtTitle.getText().toString(), this.feeds, ((Duration) this.spnRefresh.getSelectedItem()).getMins(), this.excludes.size() > 0 ? this.excludes : null, this.notificationStyle, (InlineMediaStyle) this.spnInlineMedia.getSelectedItem(), this.chkHdMedia.isChecked()); } private static class Duration { private final int mins; private final String title; public Duration (final int mins) { this(mins, null); } public Duration (final int mins, final String title) { this.mins = mins; this.title = title; } public int getMins () { return this.mins; } @Override public String toString () { return this.title; } @Override public boolean equals (final Object o) { if (o == null) return false; if (o == this) return true; if (!(o instanceof Duration)) return false; final Duration that = (Duration) o; return this.mins == that.mins; } @Override public int hashCode () { return this.mins; } } }