diff --git a/Clover/app/src/main/java/org/floens/chan/ui/controller/BoardEditController.java b/Clover/app/src/main/java/org/floens/chan/ui/controller/BoardEditController.java index 60d696ee..fdd7a4b8 100644 --- a/Clover/app/src/main/java/org/floens/chan/ui/controller/BoardEditController.java +++ b/Clover/app/src/main/java/org/floens/chan/ui/controller/BoardEditController.java @@ -1,11 +1,22 @@ package org.floens.chan.ui.controller; +import android.app.AlertDialog; import android.content.Context; +import android.content.DialogInterface; import android.support.v7.widget.RecyclerView; +import android.text.TextUtils; import android.view.LayoutInflater; +import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; +import android.view.inputmethod.EditorInfo; +import android.view.inputmethod.InputMethodManager; +import android.widget.ArrayAdapter; +import android.widget.AutoCompleteTextView; +import android.widget.Filter; +import android.widget.Filterable; import android.widget.ImageView; +import android.widget.LinearLayout; import android.widget.TextView; import org.floens.chan.ChanApplication; @@ -15,10 +26,20 @@ import org.floens.chan.core.manager.BoardManager; import org.floens.chan.core.model.Board; import org.floens.chan.ui.drawable.ThumbDrawable; import org.floens.chan.ui.helper.SwipeListener; +import org.floens.chan.ui.toolbar.ToolbarMenu; +import org.floens.chan.ui.toolbar.ToolbarMenuItem; +import org.floens.chan.ui.view.FloatingMenuItem; +import org.floens.chan.utils.AndroidUtils; +import java.util.ArrayList; import java.util.List; +import java.util.Locale; + +import static org.floens.chan.utils.AndroidUtils.dp; + +public class BoardEditController extends Controller implements SwipeListener.Callback, ToolbarMenuItem.ToolbarMenuItemCallback { + private static final int ADD_ID = 1; -public class BoardEditController extends Controller implements SwipeListener.Callback { private final BoardManager boardManager = ChanApplication.getBoardManager(); private RecyclerView recyclerView; @@ -35,6 +56,8 @@ public class BoardEditController extends Controller implements SwipeListener.Cal super.onCreate(); navigationItem.title = string(R.string.board_edit); + navigationItem.menu = new ToolbarMenu(context); + navigationItem.menu.addItem(new ToolbarMenuItem(context, this, ADD_ID, R.drawable.ic_add_white_24dp)); view = inflateRes(R.layout.controller_board_edit); recyclerView = (RecyclerView) view.findViewById(R.id.recycler_view); recyclerView.setHasFixedSize(true); @@ -47,46 +70,292 @@ public class BoardEditController extends Controller implements SwipeListener.Cal new SwipeListener(context, recyclerView, this); } + @Override + public void onDestroy() { + super.onDestroy(); + + for (int i = 0; i < boards.size(); i++) { + boards.get(i).order = i; + } + + boardManager.updateSavedBoards(); + } + + @Override + public void onMenuItemClicked(ToolbarMenuItem item) { + if ((Integer) item.getId() == ADD_ID) { + showAddBoardDialog(); + } + } + + @Override + public void onSubMenuItemClicked(ToolbarMenuItem parent, FloatingMenuItem item) { + + } + @Override public SwipeListener.Swipeable getSwipeable(int position) { - return boards.size() > 1 ? SwipeListener.Swipeable.BOTH : SwipeListener.Swipeable.NO; + return (position > 0 && boards.size() > 1) ? SwipeListener.Swipeable.BOTH : SwipeListener.Swipeable.NO; } @Override public void removeItem(int position) { - + Board board = boards.get(position - 1); + board.saved = false; + boards.remove(position - 1); + adapter.notifyItemRemoved(position); } @Override public boolean isMoveable(int position) { - return false; + return position > 0; } @Override public void moveItem(int from, int to) { - + Board item = boards.remove(from - 1); + boards.add(to - 1, item); + adapter.notifyItemMoved(from, to); } @Override public void movingDone() { + } + + private void showAddBoardDialog() { + LinearLayout wrap = new LinearLayout(context); + wrap.setPadding(dp(16), dp(16), dp(16), 0); + final AutoCompleteTextView text = new AutoCompleteTextView(context); + text.setSingleLine(); + wrap.addView(text, LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT); + + FillAdapter fillAdapter = new FillAdapter(context, 0); + fillAdapter.setEditingList(boards); + fillAdapter.setAutoCompleteView(text); + text.setAdapter(fillAdapter); + text.setThreshold(1); + text.setDropDownHeight(ViewGroup.LayoutParams.WRAP_CONTENT); + text.setHint(R.string.board_add_hint); + text.setImeOptions(EditorInfo.IME_FLAG_NO_FULLSCREEN); + AlertDialog dialog = new AlertDialog.Builder(context) + .setPositiveButton(R.string.add, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface d, int which) { + String value = text.getText().toString(); + + if (!TextUtils.isEmpty(value)) { + addBoard(value.toLowerCase(Locale.ENGLISH)); + } + } + }).setNegativeButton(R.string.cancel, null) + .setTitle(R.string.board_add) + .setView(wrap) + .create(); + + AndroidUtils.requestKeyboardFocus(dialog, text); + + dialog.show(); } - private class BoardEditAdapter extends RecyclerView.Adapter { + private void addBoard(String value) { + value = value.replace(" ", ""); + value = value.replace("/", ""); + value = value.replace("\\", ""); + + // Duplicate + for (Board board : boards) { + if (board.value.equals(value)) { + new AlertDialog.Builder(context).setMessage(R.string.board_add_duplicate).setPositiveButton(R.string.ok, null).show(); + + return; + } + } + + // Normal add + List all = ChanApplication.getBoardManager().getAllBoards(); + for (Board board : all) { + if (board.value.equals(value)) { + board.saved = true; + boards.add(board); + adapter.notifyDataSetChanged(); + + new AlertDialog.Builder(context).setMessage(string(R.string.board_add_success) + " " + board.key).setPositiveButton(R.string.ok, null).show(); + + return; + } + } + + // Unknown + new AlertDialog.Builder(context) + .setTitle(R.string.board_add_unknown_title) + .setMessage(context.getString(R.string.board_add_unknown, value)) + .setPositiveButton(R.string.ok, null) + .show(); + } + + private class FillAdapter extends ArrayAdapter implements Filterable { + private List currentlyEditing; + private View autoCompleteView; + private final Filter filter; + private final List filtered = new ArrayList<>(); + + public FillAdapter(Context context, int resource) { + super(context, resource); + + filter = new Filter() { + @Override + protected synchronized FilterResults performFiltering(CharSequence constraint) { + FilterResults results = new FilterResults(); + + if (TextUtils.isEmpty(constraint) || (constraint.toString().startsWith(" "))) { + results.values = null; + results.count = 0; + } else { + List keys = getFiltered(constraint.toString()); + results.values = keys; + results.count = keys.size(); + } + + return results; + } + + @SuppressWarnings("unchecked") + @Override + protected void publishResults(CharSequence constraint, FilterResults results) { + filtered.clear(); + + if (results.values != null) { + filtered.addAll((List) results.values); + } else { + filtered.addAll(getBoards()); + } + + notifyDataSetChanged(); + } + }; + } + + public void setEditingList(List list) { + currentlyEditing = list; + } + + public void setAutoCompleteView(View autoCompleteView) { + this.autoCompleteView = autoCompleteView; + } + @Override - public BoardEditItem onCreateViewHolder(ViewGroup parent, int viewType) { - return new BoardEditItem(LayoutInflater.from(parent.getContext()).inflate(R.layout.cell_board_edit, parent, false)); + public int getCount() { + return filtered.size(); } @Override - public void onBindViewHolder(BoardEditItem holder, int position) { - Board board = boards.get(position); - holder.text.setText("/" + board.value + "/ " + board.key); + public String getItem(int position) { + return filtered.get(position).value; + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + LayoutInflater inflater = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE); + + TextView view = (TextView) inflater.inflate(android.R.layout.simple_list_item_1, parent, false); + Board b = filtered.get(position); + view.setText("/" + b.value + "/ " + b.key); + + view.setOnTouchListener(new View.OnTouchListener() { + @Override + public boolean onTouch(View v, MotionEvent event) { + if (event.getAction() == MotionEvent.ACTION_DOWN) { + InputMethodManager imm = (InputMethodManager) getContext().getSystemService( + Context.INPUT_METHOD_SERVICE); + imm.hideSoftInputFromWindow(autoCompleteView.getWindowToken(), 0); + } + + return false; + } + }); + + return view; + } + + @Override + public Filter getFilter() { + return filter; + } + + private List getFiltered(String filter) { + String lowered = filter.toLowerCase(Locale.ENGLISH); + List list = new ArrayList<>(); + for (Board b : getBoards()) { + if ((b.key.toLowerCase(Locale.ENGLISH).contains(lowered) || b.value.toLowerCase(Locale.ENGLISH) + .contains(lowered))) { + list.add(b); + } + } + return list; + } + + private boolean haveBoard(String value) { + for (Board b : currentlyEditing) { + if (b.value.equals(value)) + return true; + } + return false; + } + + private List getBoards() { + // Lets be cheaty here: if the user has nsfw boards in the list, + // show them in the autofiller. + boolean showUnsafe = false; + for (Board has : currentlyEditing) { + if (!has.workSafe) { + showUnsafe = true; + break; + } + } + + List s = new ArrayList<>(); + for (Board b : boardManager.getAllBoards()) { + if (!haveBoard(b.value) && (showUnsafe || b.workSafe)) + s.add(b); + } + return s; + } + } + + private class BoardEditAdapter extends RecyclerView.Adapter { + private int TYPE_ITEM = 0; + private int TYPE_HEADER = 1; + + @Override + public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + if (viewType == TYPE_ITEM) { + return new BoardEditItem(LayoutInflater.from(parent.getContext()).inflate(R.layout.cell_board_edit, parent, false)); + } else { + return new BoardEditHeader(LayoutInflater.from(parent.getContext()).inflate(R.layout.cell_board_edit_header, parent, false)); + } + } + + @Override + public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { + if (getItemViewType(position) == TYPE_HEADER) { + BoardEditHeader header = (BoardEditHeader) holder; + header.text.setText(R.string.board_edit_header); + } else { + BoardEditItem item = (BoardEditItem) holder; + Board board = boards.get(position - 1); + item.text.setText("/" + board.value + "/ " + board.key); + } + } + + @Override + public int getItemViewType(int position) { + return position == 0 ? TYPE_HEADER : TYPE_ITEM; } @Override public int getItemCount() { - return boards.size(); + return boards.size() + 1; } } @@ -98,8 +367,17 @@ public class BoardEditController extends Controller implements SwipeListener.Cal super(itemView); image = (ImageView) itemView.findViewById(R.id.thumb); text = (TextView) itemView.findViewById(R.id.text); - image.setImageDrawable(new ThumbDrawable()); } } + + private class BoardEditHeader extends RecyclerView.ViewHolder { + private TextView text; + + public BoardEditHeader(View itemView) { + super(itemView); + text = (TextView) itemView.findViewById(R.id.text); + text.setTypeface(AndroidUtils.ROBOTO_MEDIUM_ITALIC); + } + } } diff --git a/Clover/app/src/main/java/org/floens/chan/ui/controller/BrowseController.java b/Clover/app/src/main/java/org/floens/chan/ui/controller/BrowseController.java index 6e4a64b8..60ff4a11 100644 --- a/Clover/app/src/main/java/org/floens/chan/ui/controller/BrowseController.java +++ b/Clover/app/src/main/java/org/floens/chan/ui/controller/BrowseController.java @@ -94,7 +94,7 @@ public class BrowseController extends ThreadController implements ToolbarMenuIte @Override public void onMenuItemClicked(ToolbarMenuItem item) { - switch (item.getId()) { + switch ((Integer) item.getId()) { case REFRESH_ID: threadLayout.getPresenter().requestData(); break; @@ -128,7 +128,8 @@ public class BrowseController extends ThreadController implements ToolbarMenuIte loadBoard(((FloatingMenuItemBoard) item).board); navigationController.toolbar.updateNavigation(); } else { - // TODO start board editor + navigationController.pushController(new BoardEditController(context)); + menu.dismiss(); } } } diff --git a/Clover/app/src/main/java/org/floens/chan/ui/controller/ViewThreadController.java b/Clover/app/src/main/java/org/floens/chan/ui/controller/ViewThreadController.java index b8819196..94addd4e 100644 --- a/Clover/app/src/main/java/org/floens/chan/ui/controller/ViewThreadController.java +++ b/Clover/app/src/main/java/org/floens/chan/ui/controller/ViewThreadController.java @@ -81,7 +81,7 @@ public class ViewThreadController extends ThreadController implements ThreadLayo public void onShow() { super.onShow(); if (navigationController instanceof RootNavigationController) { - ((RootNavigationController)navigationController).updateHighlighted(); + ((RootNavigationController) navigationController).updateHighlighted(); } } @@ -114,7 +114,7 @@ public class ViewThreadController extends ThreadController implements ThreadLayo public void onClick(final DialogInterface dialog, final int which) { loadLoadable(threadLoadable); if (navigationController instanceof RootNavigationController) { - ((RootNavigationController)navigationController).updateHighlighted(); + ((RootNavigationController) navigationController).updateHighlighted(); } } }) @@ -155,7 +155,7 @@ public class ViewThreadController extends ThreadController implements ThreadLayo @Override public void onMenuItemClicked(ToolbarMenuItem item) { - switch (item.getId()) { + switch ((Integer) item.getId()) { case PIN_ID: setPinIconState(threadLayout.getPresenter().pin()); break; diff --git a/Clover/app/src/main/java/org/floens/chan/ui/toolbar/ToolbarMenu.java b/Clover/app/src/main/java/org/floens/chan/ui/toolbar/ToolbarMenu.java index 2817f24b..75778ba9 100644 --- a/Clover/app/src/main/java/org/floens/chan/ui/toolbar/ToolbarMenu.java +++ b/Clover/app/src/main/java/org/floens/chan/ui/toolbar/ToolbarMenu.java @@ -57,14 +57,14 @@ public class ToolbarMenu extends LinearLayout { items.add(item); ImageView icon = item.getView(); if (icon != null) { - int viewIndex = Math.min(getChildCount(), item.getId()); + int viewIndex = Math.min(getChildCount(), item.getOrder()); addView(icon, viewIndex); } return item; } public ToolbarMenuItem createOverflow(ToolbarMenuItem.ToolbarMenuItemCallback callback) { - ToolbarMenuItem overflow = addItem(new ToolbarMenuItem(getContext(), callback, 100, R.drawable.ic_more)); + ToolbarMenuItem overflow = addItem(new ToolbarMenuItem(getContext(), callback, 100, 100, R.drawable.ic_more)); ImageView overflowImage = overflow.getView(); overflowImage.setLayoutParams(new LinearLayout.LayoutParams(dp(36), dp(54))); overflowImage.setPadding(0, 0, dp(16), 0); diff --git a/Clover/app/src/main/java/org/floens/chan/ui/toolbar/ToolbarMenuItem.java b/Clover/app/src/main/java/org/floens/chan/ui/toolbar/ToolbarMenuItem.java index 8586be27..a2f68748 100644 --- a/Clover/app/src/main/java/org/floens/chan/ui/toolbar/ToolbarMenuItem.java +++ b/Clover/app/src/main/java/org/floens/chan/ui/toolbar/ToolbarMenuItem.java @@ -33,17 +33,23 @@ import static org.floens.chan.utils.AndroidUtils.getAttrDrawable; public class ToolbarMenuItem implements View.OnClickListener, FloatingMenu.FloatingMenuCallback { private ToolbarMenuItemCallback callback; - private int id; + private Object id; + private int order; private FloatingMenu subMenu; private ImageView imageView; - public ToolbarMenuItem(Context context, ToolbarMenuItem.ToolbarMenuItemCallback callback, int id, int drawable) { - this(context, callback, id, context.getResources().getDrawable(drawable)); + public ToolbarMenuItem(Context context, ToolbarMenuItem.ToolbarMenuItemCallback callback, int order, int drawable) { + this(context, callback, order, order, context.getResources().getDrawable(drawable)); } - public ToolbarMenuItem(Context context, ToolbarMenuItem.ToolbarMenuItemCallback callback, int id, Drawable drawable) { + public ToolbarMenuItem(Context context, ToolbarMenuItem.ToolbarMenuItemCallback callback, Object id, int order, int drawable) { + this(context, callback, id, order, context.getResources().getDrawable(drawable)); + } + + public ToolbarMenuItem(Context context, ToolbarMenuItem.ToolbarMenuItemCallback callback, Object id, int order, Drawable drawable) { this.id = id; + this.order = order; this.callback = callback; if (drawable != null) { @@ -86,10 +92,14 @@ public class ToolbarMenuItem implements View.OnClickListener, FloatingMenu.Float callback.onMenuItemClicked(this); } - public int getId() { + public Object getId() { return id; } + public int getOrder() { + return order; + } + public ImageView getView() { return imageView; } diff --git a/Clover/app/src/main/java/org/floens/chan/ui/view/FloatingMenu.java b/Clover/app/src/main/java/org/floens/chan/ui/view/FloatingMenu.java index d5bc94ce..94a20c97 100644 --- a/Clover/app/src/main/java/org/floens/chan/ui/view/FloatingMenu.java +++ b/Clover/app/src/main/java/org/floens/chan/ui/view/FloatingMenu.java @@ -179,6 +179,13 @@ public class FloatingMenu { return popupWindow != null && popupWindow.isShowing(); } + public void dismiss() { + if (popupWindow != null && popupWindow.isShowing()) { + popupWindow.dismiss(); + popupWindow = null; + } + } + public interface FloatingMenuCallback { void onFloatingMenuItemClicked(FloatingMenu menu, FloatingMenuItem item); } diff --git a/Clover/app/src/main/res/drawable-hdpi/ic_add_white_24dp.png b/Clover/app/src/main/res/drawable-hdpi/ic_add_white_24dp.png new file mode 100644 index 00000000..481643ec Binary files /dev/null and b/Clover/app/src/main/res/drawable-hdpi/ic_add_white_24dp.png differ diff --git a/Clover/app/src/main/res/drawable-mdpi/ic_add_white_24dp.png b/Clover/app/src/main/res/drawable-mdpi/ic_add_white_24dp.png new file mode 100644 index 00000000..977dd342 Binary files /dev/null and b/Clover/app/src/main/res/drawable-mdpi/ic_add_white_24dp.png differ diff --git a/Clover/app/src/main/res/drawable-xhdpi/ic_add_white_24dp.png b/Clover/app/src/main/res/drawable-xhdpi/ic_add_white_24dp.png new file mode 100644 index 00000000..67042105 Binary files /dev/null and b/Clover/app/src/main/res/drawable-xhdpi/ic_add_white_24dp.png differ diff --git a/Clover/app/src/main/res/drawable-xxhdpi/ic_add_white_24dp.png b/Clover/app/src/main/res/drawable-xxhdpi/ic_add_white_24dp.png new file mode 100644 index 00000000..72cedcad Binary files /dev/null and b/Clover/app/src/main/res/drawable-xxhdpi/ic_add_white_24dp.png differ diff --git a/Clover/app/src/main/res/drawable-xxxhdpi/ic_add_white_24dp.png b/Clover/app/src/main/res/drawable-xxxhdpi/ic_add_white_24dp.png new file mode 100644 index 00000000..2bef0595 Binary files /dev/null and b/Clover/app/src/main/res/drawable-xxxhdpi/ic_add_white_24dp.png differ diff --git a/Clover/app/src/main/res/layout/cell_board_edit_header.xml b/Clover/app/src/main/res/layout/cell_board_edit_header.xml new file mode 100644 index 00000000..beb2b491 --- /dev/null +++ b/Clover/app/src/main/res/layout/cell_board_edit_header.xml @@ -0,0 +1,18 @@ + + + + + + diff --git a/Clover/app/src/main/res/values/strings.xml b/Clover/app/src/main/res/values/strings.xml index 4b2607cb..13ec165b 100644 --- a/Clover/app/src/main/res/values/strings.xml +++ b/Clover/app/src/main/res/values/strings.xml @@ -87,6 +87,7 @@ along with this program. If not, see . Closed Board editor + Add, remove and reorder your boards here.\nThe topmost board will be loaded automatically. Add board Board code e.g. lit Added board