diff --git a/app/src/main/java/hikapro/com/backpack/MainActivity.java b/app/src/main/java/hikapro/com/backpack/MainActivity.java index b053626..8034220 100644 --- a/app/src/main/java/hikapro/com/backpack/MainActivity.java +++ b/app/src/main/java/hikapro/com/backpack/MainActivity.java @@ -10,13 +10,11 @@ import android.util.Log; import hikapro.com.backpack.model.DetailModel; import hikapro.com.backpack.model.ItemModel; import hikapro.com.backpack.model.SetModel; -import hikapro.com.backpack.model.database.DAO; import hikapro.com.backpack.model.entities.Item; import hikapro.com.backpack.model.entities.Set; import hikapro.com.backpack.presenter.ItemDetailPresenter; import hikapro.com.backpack.presenter.ItemListPresenter; import hikapro.com.backpack.presenter.SetListPresenter; -import hikapro.com.backpack.presenter.adapters.helper.OnStartDragListener; import hikapro.com.backpack.view.View; import hikapro.com.backpack.view.fragments.ItemDetailFragment; import hikapro.com.backpack.view.fragments.ItemListFragment; diff --git a/app/src/main/java/hikapro/com/backpack/model/ItemModel.java b/app/src/main/java/hikapro/com/backpack/model/ItemModel.java index 679ab15..e63a1b4 100644 --- a/app/src/main/java/hikapro/com/backpack/model/ItemModel.java +++ b/app/src/main/java/hikapro/com/backpack/model/ItemModel.java @@ -9,16 +9,12 @@ import java.util.Collections; import java.util.Hashtable; import java.util.List; -import hikapro.com.backpack.model.database.Command; -import hikapro.com.backpack.model.database.DAO; -import hikapro.com.backpack.model.database.Event; +import hikapro.com.backpack.model.dao.Command; +import hikapro.com.backpack.model.dao.DAO; +import hikapro.com.backpack.model.dao.Event; import hikapro.com.backpack.model.entities.Category; import hikapro.com.backpack.model.entities.Item; -import hikapro.com.backpack.model.entities.Set; import hikapro.com.backpack.presenter.Presenter; -import retrofit2.Call; -import retrofit2.Callback; -import retrofit2.Response; /** * Created by tariel on 22/04/16. @@ -29,21 +25,27 @@ public class ItemModel implements Model.Item { private List rawCategories; private List sortedCategories; private List rawItems; + private DAO dao; - private int currentSet; - private Category currentCategory; - private List categoriesCache; + private int currentSet; + private Hashtable categoriesCache; + private List itemsCache; + private List itemsDiscardCache; private Hashtable> items; - private Hashtable>> cache; + private Hashtable> cache; public ItemModel() { this.rawCategories = new ArrayList<>(); this.rawItems = new ArrayList<>(); this.sortedCategories = new ArrayList<>(); - this.categoriesCache = new ArrayList<>(20); + + this.categoriesCache = new Hashtable<>(20, 0.9f); + + this.itemsCache = new ArrayList<>(); + this.itemsDiscardCache = new ArrayList<>(); this.cache = new Hashtable<>(12, 0.9f); - this.dao = DAO.getInstance(); + this.dao = DAO.getInstance(); dao.registerObserver(this); } @@ -51,8 +53,11 @@ public class ItemModel implements Model.Item { @Override public Category getCategoryByPosition(int position) { - currentCategory = categoriesCache.get(position); - return currentCategory; + Category ret = null; + if (cache.containsKey(currentSet)) + ret = categoriesCache.get(cache.get(currentSet).get(position).getCategory()); + return ret; + } @Override public int getCategoriesCount() { @@ -65,12 +70,23 @@ public class ItemModel implements Model.Item { @Override public int insertItem(Item item) { + List iList = cache.get(currentSet); + iList.add(item); + Collections.sort(iList); + cache.put(currentSet, iList); + // TODO write to database return 0; } @Override - public boolean deleteItem(int id) { + public boolean deleteItem(Item item) { + if (isPendingRemoval(item)) + itemsDiscardCache.remove(item); + cache.get(currentSet).remove(item); + //TODO write to ds + return false; } + @Override public Item findItem(int id) { Item item = null; @@ -83,15 +99,75 @@ public class ItemModel implements Model.Item { return item; } @Override - public Item getItemByPosition(int categoryId, int position) { - Item ret = cache.get(currentSet).get(currentCategory).get(position); + public Item getItemByPosition(int position) { + Item ret = null; + if (cache.containsKey(currentSet)) + ret = cache.get(currentSet).get(position); return ret; } + + @Override - public int getItemsCount(int categoryId) { - int ret = cache.get(currentSet).get(currentCategory).size(); - return ret; + public void filter(String query) { + + if (query.isEmpty()) { + Message command = Message.obtain(); + command.what = Command.SET_GET_ITEMS; + command.arg1 = presenter.getCurrentSet().getId(); + dao.executeCommand(command); + } else { + query = query.toLowerCase(); + String name; + List newList = new ArrayList<>(20); + List oldList = cache.get(currentSet); + for (Item item : oldList) { + name = item.getName().toLowerCase(); + if (name.contains(query)) { + newList.add(item); + } + } + cache.put(currentSet, newList); + } } + + @Override + public int getHeaderId(int position) { + return cache.containsKey(currentSet) ? cache.get(currentSet).get(position).getCategory() : -1; + } + + @Override + public int getItemId(int position) { + return cache.containsKey(currentSet) ? cache.get(currentSet).get(position).getId() : -1; + } + + @Override + public void clear() { + if (cache.containsKey(currentSet)) + cache.get(currentSet).clear(); + } + + @Override + public boolean isPendingRemoval(Item item) { + return itemsDiscardCache.contains(item); + } + + @Override + public void pendingRemoveCancel(Item item) { + if (itemsDiscardCache.contains(item)) + itemsDiscardCache.remove(item); + } + + @Override + public void pendingRemove(Item item) { + itemsDiscardCache.add(item); + } + + @Override + public int getItemsCount() { + boolean is = cache.containsKey(currentSet); + return is ? cache.get(currentSet).size() : 0; + } + // items <-- // events --> @@ -117,17 +193,21 @@ public class ItemModel implements Model.Item { @Override public void executeQuery() { + Message command; if (cache.contains(currentSet)) { - Hashtable> buff = cache.get(currentSet); - Category[] array = buff.keySet().toArray(new Category[buff.keySet().size()]); - categoriesCache = Arrays.asList(array); notifyDataSetChanged(); } else { - Message command = Message.obtain(); + if (categoriesCache.isEmpty()) { + command = Message.obtain(); + command.what = Command.ITEM_GET_CATEGORIES; + dao.executeCommand(command); + } + command = Message.obtain(); command.what = Command.SET_GET_ITEMS; command.arg1 = presenter.getCurrentSet().getId(); dao.executeCommand(command); + } } @Override @@ -146,13 +226,16 @@ public class ItemModel implements Model.Item { break; case Event.ITEM_INSERT_ERROR : break; + case Event.ITEM_CATEGORY_LOAD_ERROR : + break; case Event.SET_ITEMS_LOAD_COMPLETED : - Hashtable> res = (Hashtable>) event.obj; + List res = (List) event.obj; cache.put(event.arg1, res); - Category[] array = res.keySet().toArray(new Category[res.keySet().size()]); - categoriesCache = Arrays.asList(array); notifyDataSetChanged(); break; + case Event.ITEM_CATEGORY_LOAD_COMPLETED : + categoriesCache = (Hashtable)event.obj; + break; case Event.ITEM_FROM_SET_DELETED : break; case Event.ITEM_DELETED : @@ -220,7 +303,6 @@ public class ItemModel implements Model.Item { } } return category; - } private Category findCategory(int categoryId) { Category category = null; diff --git a/app/src/main/java/hikapro/com/backpack/model/Model.java b/app/src/main/java/hikapro/com/backpack/model/Model.java index f521607..08f0bdb 100644 --- a/app/src/main/java/hikapro/com/backpack/model/Model.java +++ b/app/src/main/java/hikapro/com/backpack/model/Model.java @@ -34,10 +34,20 @@ public interface Model { interface Item extends Base { int insertItem(hikapro.com.backpack.model.entities.Item item); - boolean deleteItem(int id); + boolean deleteItem(hikapro.com.backpack.model.entities.Item item); + void filter(String query); + int getHeaderId(int position);//TODO review + int getItemId(int position);//TODO review + void clear(); + boolean isPendingRemoval(hikapro.com.backpack.model.entities.Item item); + void pendingRemove(hikapro.com.backpack.model.entities.Item item); + void pendingRemoveCancel(hikapro.com.backpack.model.entities.Item item); + int getItemsCount(); + + hikapro.com.backpack.model.entities.Item findItem(int id); - hikapro.com.backpack.model.entities.Item getItemByPosition(int categoryId, int position); - int getItemsCount(int categoryId); + hikapro.com.backpack.model.entities.Item getItemByPosition(int position); + hikapro.com.backpack.model.entities.Category getCategoryByPosition(int position); int getCategoriesCount(); void setPresenter(Presenter.ItemList presenter); diff --git a/app/src/main/java/hikapro/com/backpack/model/SetModel.java b/app/src/main/java/hikapro/com/backpack/model/SetModel.java index d07cadb..cea372c 100644 --- a/app/src/main/java/hikapro/com/backpack/model/SetModel.java +++ b/app/src/main/java/hikapro/com/backpack/model/SetModel.java @@ -5,9 +5,9 @@ import android.os.Message; import java.util.ArrayList; import java.util.List; -import hikapro.com.backpack.model.database.Command; -import hikapro.com.backpack.model.database.DAO; -import hikapro.com.backpack.model.database.Event; +import hikapro.com.backpack.model.dao.Command; +import hikapro.com.backpack.model.dao.DAO; +import hikapro.com.backpack.model.dao.Event; import hikapro.com.backpack.model.entities.Set; import hikapro.com.backpack.presenter.Presenter; diff --git a/app/src/main/java/hikapro/com/backpack/model/database/Command.java b/app/src/main/java/hikapro/com/backpack/model/dao/Command.java similarity index 90% rename from app/src/main/java/hikapro/com/backpack/model/database/Command.java rename to app/src/main/java/hikapro/com/backpack/model/dao/Command.java index 15fdc8f..5542a54 100644 --- a/app/src/main/java/hikapro/com/backpack/model/database/Command.java +++ b/app/src/main/java/hikapro/com/backpack/model/dao/Command.java @@ -1,4 +1,4 @@ -package hikapro.com.backpack.model.database; +package hikapro.com.backpack.model.dao; /** * Created by tariel on 27/04/16. @@ -18,6 +18,7 @@ public interface Command { int ITEM_INSERT = 0x79; int ITEM_PACK = 0x7A; int ITEM_UNPACK = 0x7B; + int ITEM_GET_CATEGORIES = 0x7C; int MY_LIST_POST = 0x8C; int MY_LIST_ITEM_ADD = 0x8D; diff --git a/app/src/main/java/hikapro/com/backpack/model/database/DAO.java b/app/src/main/java/hikapro/com/backpack/model/dao/DAO.java similarity index 96% rename from app/src/main/java/hikapro/com/backpack/model/database/DAO.java rename to app/src/main/java/hikapro/com/backpack/model/dao/DAO.java index 8aa5054..44a87e9 100644 --- a/app/src/main/java/hikapro/com/backpack/model/database/DAO.java +++ b/app/src/main/java/hikapro/com/backpack/model/dao/DAO.java @@ -1,4 +1,4 @@ -package hikapro.com.backpack.model.database; +package hikapro.com.backpack.model.dao; import android.content.ContentValues; import android.content.Context; @@ -132,6 +132,12 @@ public class DAO { threadPool.execute(setTask); break; + case Command.ITEM_GET_CATEGORIES : + itemTask = new ItemTask(Command.ITEM_GET_CATEGORIES, + Process.THREAD_PRIORITY_MORE_FAVORABLE); + threadPool.execute(itemTask); + break; + case Command.ITEM_INSERT : itemTask = new ItemTask(Command.ITEM_INSERT, Process.THREAD_PRIORITY_BACKGROUND); @@ -590,6 +596,16 @@ public class DAO { else message.what = Event.ITEM_UNPACK_ERROR; break; + + case Command.ITEM_GET_CATEGORIES : + Hashtable res = readCategories(); + if (res.isEmpty()) + message.what = Event.ITEM_CATEGORY_LOAD_ERROR; + else { + message.what = Event.ITEM_CATEGORY_LOAD_COMPLETED; + message.obj = res; + } + break; } handler.sendMessage(message); } @@ -630,22 +646,8 @@ public class DAO { message.what = Event.SET_ITEMS_LOAD_ERROR; else { Collections.sort(items); - Hashtable categories = readCategories(); - Hashtable> result = new Hashtable<>(20, 0.9f); - Category category; - - for (Item item : items) { - category = categories.get(item.getCategory()); - if (result.containsKey(category)) { - result.get(category).add(item); - } else { - List innerList = new ArrayList<>(20); - innerList.add(item); - result.put(category, innerList); - } - } message.what = Event.SET_ITEMS_LOAD_COMPLETED; - message.obj = result; + message.obj = items; message.arg1 = setId; } break; diff --git a/app/src/main/java/hikapro/com/backpack/model/database/Db.java b/app/src/main/java/hikapro/com/backpack/model/dao/Db.java similarity index 99% rename from app/src/main/java/hikapro/com/backpack/model/database/Db.java rename to app/src/main/java/hikapro/com/backpack/model/dao/Db.java index 06cd845..26c643b 100644 --- a/app/src/main/java/hikapro/com/backpack/model/database/Db.java +++ b/app/src/main/java/hikapro/com/backpack/model/dao/Db.java @@ -1,8 +1,7 @@ -package hikapro.com.backpack.model.database; +package hikapro.com.backpack.model.dao; import android.content.ContentValues; import android.database.Cursor; -import android.util.Log; import com.google.gson.Gson; import com.google.gson.reflect.TypeToken; diff --git a/app/src/main/java/hikapro/com/backpack/model/database/DbHelper.java b/app/src/main/java/hikapro/com/backpack/model/dao/DbHelper.java similarity index 96% rename from app/src/main/java/hikapro/com/backpack/model/database/DbHelper.java rename to app/src/main/java/hikapro/com/backpack/model/dao/DbHelper.java index 9d8d947..e4e6a03 100644 --- a/app/src/main/java/hikapro/com/backpack/model/database/DbHelper.java +++ b/app/src/main/java/hikapro/com/backpack/model/dao/DbHelper.java @@ -1,4 +1,4 @@ -package hikapro.com.backpack.model.database; +package hikapro.com.backpack.model.dao; import android.content.Context; import android.database.sqlite.SQLiteDatabase; diff --git a/app/src/main/java/hikapro/com/backpack/model/database/Event.java b/app/src/main/java/hikapro/com/backpack/model/dao/Event.java similarity index 91% rename from app/src/main/java/hikapro/com/backpack/model/database/Event.java rename to app/src/main/java/hikapro/com/backpack/model/dao/Event.java index f2a4df9..e4ada30 100644 --- a/app/src/main/java/hikapro/com/backpack/model/database/Event.java +++ b/app/src/main/java/hikapro/com/backpack/model/dao/Event.java @@ -1,4 +1,4 @@ -package hikapro.com.backpack.model.database; +package hikapro.com.backpack.model.dao; /** * Created by tariel on 27/04/16. @@ -24,12 +24,14 @@ public interface Event { int ITEM_DELETE_ERROR = -0x16; int ITEM_PACK_ERROR = -0x17; int ITEM_UNPACK_ERROR = -0x18; + int ITEM_CATEGORY_LOAD_ERROR = -0x19; int ITEM_FROM_SET_DELETED = 0x14; int ITEM_INSERTED = 0x15; int ITEM_DELETED = 0x16; int ITEM_PACKED = 0x17; int ITEM_UNPACKED = 0x18; + int ITEM_CATEGORY_LOAD_COMPLETED = 0x19; int MY_LIST_POST_ERROR = -0x28; int MY_LIST_ITEM_ADD_ERROR = -0x29; diff --git a/app/src/main/java/hikapro/com/backpack/model/database/Test.java b/app/src/main/java/hikapro/com/backpack/model/dao/Test.java similarity index 82% rename from app/src/main/java/hikapro/com/backpack/model/database/Test.java rename to app/src/main/java/hikapro/com/backpack/model/dao/Test.java index e666937..f1464cf 100644 --- a/app/src/main/java/hikapro/com/backpack/model/database/Test.java +++ b/app/src/main/java/hikapro/com/backpack/model/dao/Test.java @@ -1,4 +1,4 @@ -package hikapro.com.backpack.model.database; +package hikapro.com.backpack.model.dao; /** * Created by tariel on 27/04/16. diff --git a/app/src/main/java/hikapro/com/backpack/presenter/ItemListPresenter.java b/app/src/main/java/hikapro/com/backpack/presenter/ItemListPresenter.java index 97e6193..28652f3 100644 --- a/app/src/main/java/hikapro/com/backpack/presenter/ItemListPresenter.java +++ b/app/src/main/java/hikapro/com/backpack/presenter/ItemListPresenter.java @@ -2,27 +2,24 @@ package hikapro.com.backpack.presenter; import android.content.Context; import android.os.Bundle; -import android.support.v7.widget.DefaultItemAnimator; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; +import android.support.v7.widget.helper.ItemTouchHelper; import android.view.LayoutInflater; import android.view.ViewGroup; import android.widget.Toast; import java.lang.ref.WeakReference; -import java.util.ArrayList; -import java.util.List; import hikapro.com.backpack.R; -import hikapro.com.backpack.model.ItemModel; import hikapro.com.backpack.model.Model; import hikapro.com.backpack.model.entities.Item; import hikapro.com.backpack.model.entities.Set; -import hikapro.com.backpack.presenter.adapters.CategoryListAdapter; +import hikapro.com.backpack.presenter.adapters.helper.items.DividerDecoration; import hikapro.com.backpack.presenter.adapters.ItemListAdapter; +import hikapro.com.backpack.presenter.adapters.helper.items.ItemSwipeCallback; +import hikapro.com.backpack.presenter.adapters.helper.items.StickyHeaderDecoration; import hikapro.com.backpack.view.View; -import hikapro.com.backpack.view.recycler.CategoryViewHolder; -import hikapro.com.backpack.view.recycler.ItemViewHolder; /** * Created by tariel on 20/04/16. @@ -34,10 +31,12 @@ public class ItemListPresenter implements Presenter.ItemList { private WeakReference view; private Model.Item model; private Set set; - private CategoryListAdapter categoryListAdapter; + private ItemListAdapter adapter; + RecyclerView recycler; public ItemListPresenter() { - this.categoryListAdapter = new CategoryListAdapter(this); + this.adapter = new ItemListAdapter(this); + adapter.setHasStableIds(true); } // life cycle --> @@ -56,10 +55,25 @@ public class ItemListPresenter implements Presenter.ItemList { set = (Set) savedInstanceState.getSerializable(BUNDLE_SET_LIST_KEY); android.view.View view = inflater.inflate(R.layout.fragment_item_list, container, false); LinearLayoutManager llm = new LinearLayoutManager(getActivityContext()); - RecyclerView mainRecycler = (RecyclerView) view.findViewById(R.id.categories_main_recycler); - mainRecycler.setLayoutManager(llm); - mainRecycler.setAdapter(categoryListAdapter); - mainRecycler.setItemAnimator(new DefaultItemAnimator()); + recycler = (RecyclerView) view.findViewById(R.id.items_recycler); + recycler.setLayoutManager(llm); + recycler.setAdapter(adapter); + + final StickyHeaderDecoration decoration = new StickyHeaderDecoration(adapter); + recycler.addItemDecoration(decoration); + recycler.addItemDecoration(new DividerDecoration(getActivityContext())); + adapter.registerAdapterDataObserver(new RecyclerView.AdapterDataObserver() { + @Override + public void onChanged() { + decoration.invalidateHeaders(); + } + }); + ItemSwipeCallback itemSwipeCallback = new ItemSwipeCallback(0, ItemTouchHelper.LEFT, adapter, getActivityContext()); + + ItemTouchHelper itemTouchHelper = new ItemTouchHelper(itemSwipeCallback); + itemTouchHelper.attachToRecyclerView(recycler); + adapter.setUndoOn(true); + model.executeQuery(); return view; } @@ -71,68 +85,11 @@ public class ItemListPresenter implements Presenter.ItemList { // life cycle <-- - // recycler --> - - @Override - public CategoryViewHolder createViewHolderCategory(ViewGroup parent, int viewType) { - CategoryViewHolder viewHolder; - android.view.View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.section, - parent, false); - viewHolder = new CategoryViewHolder(v); - return viewHolder; - } - @Override - public void bindViewHolderCategory(CategoryViewHolder holder, int position) { - final hikapro.com.backpack.model.entities.Category category = model.getCategoryByPosition(position); - holder.sectionText.setText(category.getName()); - LinearLayoutManager llm = new LinearLayoutManager(getActivityContext()); - holder.itemsRecycler.setLayoutManager(llm); - ItemListAdapter itemListAdapter = new ItemListAdapter(this); - itemListAdapter.setCategoryId(category.getId()); - holder.itemsRecycler.setAdapter(itemListAdapter); - holder.itemsRecycler.setItemAnimator(new DefaultItemAnimator()); - categoryListAdapter.addItemAdapter(itemListAdapter); - } - - @Override - public ItemViewHolder createViewHolderItem(ViewGroup parent, int viewType) { - ItemViewHolder viewHolder; - android.view.View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.item, - parent, false); - viewHolder = new ItemViewHolder(v); - return viewHolder; - } - @Override - public void bindViewHolderItem(final ItemViewHolder holder, final int position, int categoryId) { - - hikapro.com.backpack.model.entities.Item item = model.getItemByPosition(categoryId, position); - holder.title.setText(item.getName()); - holder.id = item.getId(); - holder.title.setOnClickListener(new android.view.View.OnClickListener() { - @Override - public void onClick(android.view.View view) { - clickItem(holder.id); - } - }); - } - @Override - public int getItemsCount(int categoryId) { - return model.getItemsCount(categoryId);//TODO category Id - } - @Override - public int getCategoriesCount() { - return model.getCategoriesCount(); - } - - - // recycler <-- - // process --> @Override public void notifyDataSetChanged() { - categoryListAdapter.notifyDataSetChanged(); - categoryListAdapter.notifyItemAdapters(); + adapter.notifyDataSetChanged(); } @Override @@ -156,6 +113,12 @@ public class ItemListPresenter implements Presenter.ItemList { public void setModel(Model.Item model) { this.model = model; } + + @Override + public Model.Item getModel() { + return model; + } + @Override public Context getAppContext() { try { @@ -186,6 +149,11 @@ public class ItemListPresenter implements Presenter.ItemList { throw new NullPointerException("View is unavailable"); } + @Override + public void filter(String query) { + adapter.filter(query); + recycler.scrollToPosition(0); + } @Override public Set getCurrentSet() { diff --git a/app/src/main/java/hikapro/com/backpack/presenter/Presenter.java b/app/src/main/java/hikapro/com/backpack/presenter/Presenter.java index 277232e..347c886 100644 --- a/app/src/main/java/hikapro/com/backpack/presenter/Presenter.java +++ b/app/src/main/java/hikapro/com/backpack/presenter/Presenter.java @@ -3,15 +3,12 @@ package hikapro.com.backpack.presenter; import android.content.Context; import android.os.Bundle; import android.support.v7.widget.RecyclerView; -import android.view.DragEvent; import android.view.LayoutInflater; -import android.view.View; import android.view.ViewGroup; import hikapro.com.backpack.model.Model; import hikapro.com.backpack.model.entities.Item; import hikapro.com.backpack.model.entities.Set; -import hikapro.com.backpack.view.recycler.CategoryViewHolder; import hikapro.com.backpack.view.recycler.DetailViewHolder; import hikapro.com.backpack.view.recycler.ItemViewHolder; import hikapro.com.backpack.view.recycler.SetViewHolder; @@ -45,21 +42,18 @@ public interface Presenter { } interface ItemList extends Base { - ItemViewHolder createViewHolderItem(ViewGroup parent, int viewType); - void bindViewHolderItem(ItemViewHolder holder, int position, int categoryId); - int getItemsCount(int categoryId); - CategoryViewHolder createViewHolderCategory(ViewGroup parent, int viewType); - void bindViewHolderCategory(CategoryViewHolder holder, int position); - int getCategoriesCount(); + void onDestroy(boolean isChangingConfiguration); android.view.View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState); void setView(hikapro.com.backpack.view.View.ItemList view); void setModel(Model.Item model); + Model.Item getModel(); void notifyDataSetChanged(); Set getCurrentSet(); void showMessage(String message); void onSaveInstanceState(Bundle outState); void clickItem(int itemId); + void filter(String query); } interface ItemDetail extends Base { diff --git a/app/src/main/java/hikapro/com/backpack/presenter/SetListPresenter.java b/app/src/main/java/hikapro/com/backpack/presenter/SetListPresenter.java index d1c6ad3..02604a1 100644 --- a/app/src/main/java/hikapro/com/backpack/presenter/SetListPresenter.java +++ b/app/src/main/java/hikapro/com/backpack/presenter/SetListPresenter.java @@ -17,8 +17,8 @@ import hikapro.com.backpack.R; import hikapro.com.backpack.model.Model; import hikapro.com.backpack.model.entities.Set; import hikapro.com.backpack.presenter.adapters.SetListAdapter; -import hikapro.com.backpack.presenter.adapters.helper.OnStartDragListener; -import hikapro.com.backpack.presenter.adapters.helper.SimpleItemTouchHelperCallback; +import hikapro.com.backpack.presenter.adapters.helper.sets.OnStartDragListener; +import hikapro.com.backpack.presenter.adapters.helper.sets.SimpleItemTouchHelperCallback; import hikapro.com.backpack.view.View; import hikapro.com.backpack.view.recycler.SetViewHolder; diff --git a/app/src/main/java/hikapro/com/backpack/presenter/adapters/CategoryListAdapter.java b/app/src/main/java/hikapro/com/backpack/presenter/adapters/CategoryListAdapter.java deleted file mode 100644 index d3d2a6c..0000000 --- a/app/src/main/java/hikapro/com/backpack/presenter/adapters/CategoryListAdapter.java +++ /dev/null @@ -1,50 +0,0 @@ -package hikapro.com.backpack.presenter.adapters; - -import android.support.v7.widget.RecyclerView; -import android.view.ViewGroup; - -import java.util.ArrayList; -import java.util.List; - -import hikapro.com.backpack.presenter.Presenter; -import hikapro.com.backpack.view.recycler.CategoryViewHolder; - -/** - * Created by tariel on 20/04/16. - */ -public class CategoryListAdapter extends RecyclerView.Adapter { - - private Presenter.ItemList presenter; - private List itemAdapters; - - public CategoryListAdapter(Presenter.ItemList presenter) { - this.presenter = presenter; - this.itemAdapters = new ArrayList<>(); - } - - @Override - public int getItemCount() { - return presenter.getCategoriesCount(); - } - - @Override - public void onBindViewHolder(CategoryViewHolder holder, int position) { - presenter.bindViewHolderCategory(holder, position); - } - - @Override - public CategoryViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { - return presenter.createViewHolderCategory(parent, viewType); - } - - public void addItemAdapter(ItemListAdapter adapter) { - itemAdapters.add(adapter); - adapter.notifyDataSetChanged(); - } - public void notifyItemAdapters() { - for (ItemListAdapter adapter : itemAdapters) { - adapter.notifyDataSetChanged(); - } - } - -} diff --git a/app/src/main/java/hikapro/com/backpack/presenter/adapters/ItemListAdapter.java b/app/src/main/java/hikapro/com/backpack/presenter/adapters/ItemListAdapter.java index 19fc89a..bdc93d4 100644 --- a/app/src/main/java/hikapro/com/backpack/presenter/adapters/ItemListAdapter.java +++ b/app/src/main/java/hikapro/com/backpack/presenter/adapters/ItemListAdapter.java @@ -1,18 +1,35 @@ package hikapro.com.backpack.presenter.adapters; +import android.graphics.Color; +import android.os.Handler; import android.support.v7.widget.RecyclerView; +import android.view.LayoutInflater; +import android.view.View; import android.view.ViewGroup; +import java.util.HashMap; + +import hikapro.com.backpack.R; +import hikapro.com.backpack.model.Model; +import hikapro.com.backpack.model.entities.Category; +import hikapro.com.backpack.model.entities.Item; +import hikapro.com.backpack.presenter.ItemListPresenter; import hikapro.com.backpack.presenter.Presenter; +import hikapro.com.backpack.presenter.adapters.helper.items.StickyHeaderAdapter; +import hikapro.com.backpack.view.recycler.HeaderViewHolder; import hikapro.com.backpack.view.recycler.ItemViewHolder; /** - * Created by tariel on 20/04/16. + * Created by tariel on 01/05/16. */ -public class ItemListAdapter extends RecyclerView.Adapter { +public class ItemListAdapter extends RecyclerView.Adapter implements StickyHeaderAdapter { + private static final int PENDING_REMOVAL_TIMEOUT = 4000; // 4sec + + boolean undoOn; // is undo on, you can turn it on from the toolbar menu + private Handler handler = new Handler(); // hanlder for running delayed runnables + HashMap pendingRunables = new HashMap<>(); // map of items to pending runnables, so we can cancel a removal if need be private Presenter.ItemList presenter; - private int categoryId; public ItemListAdapter(Presenter.ItemList presenter) { this.presenter = presenter; @@ -20,19 +37,145 @@ public class ItemListAdapter extends RecyclerView.Adapter { @Override public int getItemCount() { - return presenter.getItemsCount(categoryId); + int res = presenter.getModel().getItemsCount(); + return res; + } + + @Override + public long getItemId(int position) { + return presenter.getModel().getItemByPosition(position).getId(); + } + + @Override + public void onBindViewHolder(ItemViewHolder holder, final int position) { + + final Item item = presenter.getModel().getItemByPosition(position); + if (presenter.getModel().isPendingRemoval(item)) { + // we need to show the "undo" state of the row + holder.itemView.setBackgroundColor(Color.RED); + holder.title.setVisibility(View.GONE); + holder.undoButton.setVisibility(View.VISIBLE); + holder.undoButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + // user wants to undo the removal, let's cancel the pending task + Runnable pendingRemovalRunnable = pendingRunables.get(item); + pendingRunables.remove(item); + if (pendingRemovalRunnable != null) handler.removeCallbacks(pendingRemovalRunnable); + presenter.getModel().pendingRemoveCancel(item); + // this will rebind the row in "normal" state + notifyItemChanged(position); + } + }); + } else { + holder.title.setVisibility(View.VISIBLE); + holder.title.setText(item.getName()); + holder.id = item.getId(); + holder.categoryId = item.getCategory(); + holder.itemView.setBackgroundColor(0x33FF99); + holder.undoButton.setVisibility(View.GONE); + holder.undoButton.setOnClickListener(null); + } + } @Override public ItemViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { - return presenter.createViewHolderItem(parent, viewType); + ItemViewHolder viewHolder; + View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.view_item, + parent, false); + viewHolder = new ItemViewHolder(v); + return viewHolder; + } + + public void setUndoOn(boolean undoOn) { + this.undoOn = undoOn; + } + + public boolean isUndoOn() { + return undoOn; + } + + public void add(Item item) { + presenter.getModel().insertItem(item); + notifyDataSetChanged(); + } + + + public void clear() { + presenter.getModel().clear(); + notifyDataSetChanged(); + } + + public void filter(String query) { + presenter.getModel().filter(query); + notifyDataSetChanged(); } @Override - public void onBindViewHolder(ItemViewHolder holder, int position) { - presenter.bindViewHolderItem(holder, position, categoryId); + public void setHasStableIds(boolean hasStableIds) { + super.setHasStableIds(hasStableIds); } - public void setCategoryId(int categoryId) { - this.categoryId = categoryId; + + @Override + public long getHeaderId(int position) { + /*if (position == 0) { + return -1; + } else {*/ + return presenter.getModel().getHeaderId(position); + //} } + + @Override + public HeaderViewHolder onCreateHeaderViewHolder(ViewGroup parent) { + HeaderViewHolder viewHolder; + View v = LayoutInflater.from(parent.getContext()) + .inflate(R.layout.view_header, parent, false); + viewHolder = new HeaderViewHolder(v); + return viewHolder; + } + + @Override + public void onBindHeaderViewHolder(HeaderViewHolder holder, int position) { + Category category = presenter.getModel().getCategoryByPosition(position); + holder.id = category.getId(); + holder.title.setText(category.getName()); + holder.title.setBackgroundColor(0x2B1E15); + } + + public void pendingRemoval(final int position) { + final Item item = presenter.getModel().getItemByPosition(position); + + if (! presenter.getModel().isPendingRemoval(item)) { + presenter.getModel().pendingRemove(item); + // this will redraw row in "undo" state + notifyItemChanged(position); + // let's create, store and post a runnable to remove the item + Runnable pendingRemovalRunnable = new Runnable() { + @Override + public void run() { + remove(item, position); + } + }; + handler.postDelayed(pendingRemovalRunnable, PENDING_REMOVAL_TIMEOUT); + pendingRunables.put(item, pendingRemovalRunnable); + } + } + + public void remove(Item item, int position) { + presenter.getModel().deleteItem(item); + notifyItemRemoved(position); + } + + public void remove(int position) { + Item item = presenter.getModel().getItemByPosition(position); + presenter.getModel().deleteItem(item); + notifyItemRemoved(position); + } + + public boolean isPendingRemoval(int position) { + Item item = presenter.getModel().getItemByPosition(position); + return presenter.getModel().isPendingRemoval(item); + } + } diff --git a/app/src/main/java/hikapro/com/backpack/presenter/adapters/SetListAdapter.java b/app/src/main/java/hikapro/com/backpack/presenter/adapters/SetListAdapter.java index 42fe656..5293c81 100644 --- a/app/src/main/java/hikapro/com/backpack/presenter/adapters/SetListAdapter.java +++ b/app/src/main/java/hikapro/com/backpack/presenter/adapters/SetListAdapter.java @@ -4,7 +4,7 @@ import android.support.v7.widget.RecyclerView; import android.view.ViewGroup; import hikapro.com.backpack.presenter.SetListPresenter; -import hikapro.com.backpack.presenter.adapters.helper.ItemTouchHelperAdapter; +import hikapro.com.backpack.presenter.adapters.helper.sets.ItemTouchHelperAdapter; import hikapro.com.backpack.view.recycler.SetViewHolder; /** diff --git a/app/src/main/java/hikapro/com/backpack/presenter/adapters/helper/items/DimensionCalculator.java b/app/src/main/java/hikapro/com/backpack/presenter/adapters/helper/items/DimensionCalculator.java new file mode 100644 index 0000000..cf459ed --- /dev/null +++ b/app/src/main/java/hikapro/com/backpack/presenter/adapters/helper/items/DimensionCalculator.java @@ -0,0 +1,45 @@ +package hikapro.com.backpack.presenter.adapters.helper.items; + +import android.graphics.Rect; +import android.view.View; +import android.view.ViewGroup; + +/** + * Created by tariel on 30/04/16. + */ +public class DimensionCalculator { + + /** + * Populates {@link Rect} with margins for any view. + * + * + * @param margins rect to populate + * @param view for which to get margins + */ + public void initMargins(Rect margins, View view) { + ViewGroup.LayoutParams layoutParams = view.getLayoutParams(); + + if (layoutParams instanceof ViewGroup.MarginLayoutParams) { + ViewGroup.MarginLayoutParams marginLayoutParams = (ViewGroup.MarginLayoutParams) layoutParams; + initMarginRect(margins, marginLayoutParams); + } else { + margins.set(0, 0, 0, 0); + } + } + + /** + * Converts {@link ViewGroup.MarginLayoutParams} into a representative {@link Rect}. + * + * @param marginRect Rect to be initialized with margins coordinates, where + * {@link ViewGroup.MarginLayoutParams#leftMargin} is equivalent to {@link Rect#left}, etc. + * @param marginLayoutParams margins to populate the Rect with + */ + private void initMarginRect(Rect marginRect, ViewGroup.MarginLayoutParams marginLayoutParams) { + marginRect.set( + marginLayoutParams.leftMargin, + marginLayoutParams.topMargin, + marginLayoutParams.rightMargin, + marginLayoutParams.bottomMargin + ); + } +} diff --git a/app/src/main/java/hikapro/com/backpack/presenter/adapters/helper/items/DividerDecoration.java b/app/src/main/java/hikapro/com/backpack/presenter/adapters/helper/items/DividerDecoration.java new file mode 100644 index 0000000..7076da7 --- /dev/null +++ b/app/src/main/java/hikapro/com/backpack/presenter/adapters/helper/items/DividerDecoration.java @@ -0,0 +1,96 @@ +package hikapro.com.backpack.presenter.adapters.helper.items; + +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.Canvas; +import android.graphics.Rect; +import android.graphics.drawable.Drawable; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.view.View; + + +public class DividerDecoration extends RecyclerView.ItemDecoration { + + private static final int[] ATTRS = new int[]{ + android.R.attr.listDivider + }; + + public static final int HORIZONTAL_LIST = LinearLayoutManager.HORIZONTAL; + + public static final int VERTICAL_LIST = LinearLayoutManager.VERTICAL; + + private Drawable mDivider; + + public DividerDecoration(Context context) { + final TypedArray a = context.obtainStyledAttributes(ATTRS); + mDivider = a.getDrawable(0); + a.recycle(); + } + + private int getOrientation(RecyclerView parent) { + LinearLayoutManager layoutManager; + try { + layoutManager = (LinearLayoutManager) parent.getLayoutManager(); + } catch (ClassCastException e) { + throw new IllegalStateException("DividerDecoration can only be used with a " + + "LinearLayoutManager.", e); + } + return layoutManager.getOrientation(); + } + + @Override + public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) { + super.onDraw(c, parent, state); + + if (getOrientation(parent) == VERTICAL_LIST) { + drawVertical(c, parent); + } else { + drawHorizontal(c, parent); + } + } + + public void drawVertical(Canvas c, RecyclerView parent) { + final int left = parent.getPaddingLeft(); + final int right = parent.getWidth() - parent.getPaddingRight(); + final int recyclerViewTop = parent.getPaddingTop(); + final int recyclerViewBottom = parent.getHeight() - parent.getPaddingBottom(); + final int childCount = parent.getChildCount(); + for (int i = 0; i < childCount; i++) { + final View child = parent.getChildAt(i); + final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child + .getLayoutParams(); + final int top = Math.max(recyclerViewTop, child.getBottom() + params.bottomMargin); + final int bottom = Math.min(recyclerViewBottom, top + mDivider.getIntrinsicHeight()); + mDivider.setBounds(left, top, right, bottom); + mDivider.draw(c); + } + } + + public void drawHorizontal(Canvas c, RecyclerView parent) { + final int top = parent.getPaddingTop(); + final int bottom = parent.getHeight() - parent.getPaddingBottom(); + final int recyclerViewLeft = parent.getPaddingLeft(); + final int recyclerViewRight = parent.getWidth() - parent.getPaddingRight(); + final int childCount = parent.getChildCount(); + for (int i = 0; i < childCount; i++) { + final View child = parent.getChildAt(i); + final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child + .getLayoutParams(); + final int left = Math.max(recyclerViewLeft, child.getRight() + params.rightMargin); + final int right = Math.min(recyclerViewRight, left + mDivider.getIntrinsicHeight()); + mDivider.setBounds(left, top, right, bottom); + mDivider.draw(c); + } + } + + @Override + public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) { + super.getItemOffsets(outRect, view, parent, state); + if (getOrientation(parent) == VERTICAL_LIST) { + outRect.set(0, 0, 0, mDivider.getIntrinsicHeight()); + } else { + outRect.set(0, 0, mDivider.getIntrinsicWidth(), 0); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/hikapro/com/backpack/presenter/adapters/helper/items/HeaderPositionCalculator.java b/app/src/main/java/hikapro/com/backpack/presenter/adapters/helper/items/HeaderPositionCalculator.java new file mode 100644 index 0000000..2eab5be --- /dev/null +++ b/app/src/main/java/hikapro/com/backpack/presenter/adapters/helper/items/HeaderPositionCalculator.java @@ -0,0 +1,256 @@ +package hikapro.com.backpack.presenter.adapters.helper.items; + +import android.graphics.Rect; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.view.View; +import android.view.ViewGroup; +import android.widget.LinearLayout; + +/** + * Created by tariel on 30/04/16. + */ +public class HeaderPositionCalculator { + + private final StickyHeaderAdapter adapter; + private final HeaderProvider headerProvider; + private final DimensionCalculator dimensionCalculator; + + /** + * The following fields are used as buffers for internal calculations. Their sole purpose is to avoid + * allocating new Rect every time we need one. + */ + private final Rect tempRect1 = new Rect(); + private final Rect tempRect2 = new Rect(); + + public HeaderPositionCalculator(StickyHeaderAdapter adapter, HeaderProvider headerProvider, + DimensionCalculator dimensionCalculator) { + this.adapter = adapter; + this.headerProvider = headerProvider; + this.dimensionCalculator = dimensionCalculator; + } + + /** + * Determines if a view should have a sticky header. + * The view has a sticky header if: + * 1. It is the first element in the recycler view + * 2. It has a valid ID associated to its position + * + * @param itemView given by the RecyclerView + * @param orientation of the Recyclerview + * @param position of the list item in question + * @return True if the view should have a sticky header + */ + public boolean hasStickyHeader(View itemView, int orientation, int position) { + int offset, margin; + dimensionCalculator.initMargins(tempRect1, itemView); + if (orientation == LinearLayout.VERTICAL) { + offset = itemView.getTop(); + margin = tempRect1.top; + } else { + offset = itemView.getLeft(); + margin = tempRect1.left; + } + boolean ret = offset <= margin; + ret = ret && adapter.getHeaderId(position) >= 0; + + return ret; + } + + /** + * Determines if an item in the list should have a header that is different than the item in the + * list that immediately precedes it. Items with no headers will always return false. + * + * @param position of the list item in questions + * @param isReverseLayout TRUE if layout manager has flag isReverseLayout + * @return true if this item has a different header than the previous item in the list + */ + public boolean hasNewHeader(int position) { + if (indexOutOfBounds(position)) { + return false; + } + + long headerId = adapter.getHeaderId(position); + + if (headerId < 0) { + return false; + } + + long nextItemHeaderId = -1;//TODO -1 + int nextItemPosition = position - 1; // TODO -1 + if (!indexOutOfBounds(nextItemPosition)) { + nextItemHeaderId = adapter.getHeaderId(nextItemPosition); + } + int firstItemPosition = 0; + + return position == firstItemPosition || headerId != nextItemHeaderId; + } + + private boolean indexOutOfBounds(int position) { + return position < 0 || position >= adapter.getItemCount(); + } + + public void initHeaderBounds(Rect bounds, RecyclerView recyclerView, View header, View firstView, boolean firstHeader) { + initDefaultHeaderOffset(bounds, recyclerView, header, firstView, LinearLayout.VERTICAL); + + if (firstHeader && isStickyHeaderBeingPushedOffscreen(recyclerView, header)) { + View viewAfterNextHeader = getFirstViewUnobscuredByHeader(recyclerView, header); + int firstViewUnderHeaderPosition = recyclerView.getChildAdapterPosition(viewAfterNextHeader); + View secondHeader = headerProvider.getHeader(recyclerView, firstViewUnderHeaderPosition); + translateHeaderWithNextHeader(recyclerView, LinearLayout.VERTICAL, bounds, + header, viewAfterNextHeader, secondHeader); + } + } + + private void initDefaultHeaderOffset(Rect headerMargins, RecyclerView recyclerView, View header, View firstView, int orientation) { + int translationX, translationY; + dimensionCalculator.initMargins(tempRect1, header); + + ViewGroup.LayoutParams layoutParams = firstView.getLayoutParams(); + int leftMargin = 0; + int topMargin = 0; + if (layoutParams instanceof ViewGroup.MarginLayoutParams) { + ViewGroup.MarginLayoutParams marginLayoutParams = (ViewGroup.MarginLayoutParams) layoutParams; + leftMargin = marginLayoutParams.leftMargin; + topMargin = marginLayoutParams.topMargin; + } + + if (orientation == LinearLayoutManager.VERTICAL) { + translationX = firstView.getLeft() - leftMargin + tempRect1.left; + translationY = Math.max( + firstView.getTop() - topMargin - header.getHeight() - tempRect1.bottom, + getListTop(recyclerView) + tempRect1.top); + } else { + translationY = firstView.getTop() - topMargin + tempRect1.top; + translationX = Math.max( + firstView.getLeft() - leftMargin - header.getWidth() - tempRect1.right, + getListLeft(recyclerView) + tempRect1.left); + } + + headerMargins.set(translationX, translationY, translationX + header.getWidth(), + translationY + header.getHeight()); + } + + private boolean isStickyHeaderBeingPushedOffscreen(RecyclerView recyclerView, View stickyHeader) { + View viewAfterHeader = getFirstViewUnobscuredByHeader(recyclerView, stickyHeader); + int firstViewUnderHeaderPosition = recyclerView.getChildAdapterPosition(viewAfterHeader); + if (firstViewUnderHeaderPosition == RecyclerView.NO_POSITION) { + return false; + } + + int orientation = LinearLayoutManager.VERTICAL; + + if (firstViewUnderHeaderPosition > 0 && hasNewHeader(firstViewUnderHeaderPosition)) { + View nextHeader = headerProvider.getHeader(recyclerView, firstViewUnderHeaderPosition); + dimensionCalculator.initMargins(tempRect1, nextHeader); + dimensionCalculator.initMargins(tempRect2, stickyHeader); + + if (orientation == LinearLayoutManager.VERTICAL) { + int topOfNextHeader = viewAfterHeader.getTop() - tempRect1.bottom - nextHeader.getHeight() - tempRect1.top; + int bottomOfThisHeader = recyclerView.getPaddingTop() + stickyHeader.getBottom() + tempRect2.top + tempRect2.bottom; + if (topOfNextHeader < bottomOfThisHeader) { + return true; + } + } else { + int leftOfNextHeader = viewAfterHeader.getLeft() - tempRect1.right - nextHeader.getWidth() - tempRect1.left; + int rightOfThisHeader = recyclerView.getPaddingLeft() + stickyHeader.getRight() + tempRect2.left + tempRect2.right; + if (leftOfNextHeader < rightOfThisHeader) { + return true; + } + } + } + + return false; + } + + private void translateHeaderWithNextHeader(RecyclerView recyclerView, int orientation, Rect translation, + View currentHeader, View viewAfterNextHeader, View nextHeader) { + dimensionCalculator.initMargins(tempRect1, nextHeader); + dimensionCalculator.initMargins(tempRect2, currentHeader); + if (orientation == LinearLayoutManager.VERTICAL) { + int topOfStickyHeader = getListTop(recyclerView) + tempRect2.top + tempRect2.bottom; + int shiftFromNextHeader = viewAfterNextHeader.getTop() - nextHeader.getHeight() - tempRect1.bottom - tempRect1.top - currentHeader.getHeight() - topOfStickyHeader; + if (shiftFromNextHeader < topOfStickyHeader) { + translation.top += shiftFromNextHeader; + } + } else { + int leftOfStickyHeader = getListLeft(recyclerView) + tempRect2.left + tempRect2.right; + int shiftFromNextHeader = viewAfterNextHeader.getLeft() - nextHeader.getWidth() - tempRect1.right - tempRect1.left - currentHeader.getWidth() - leftOfStickyHeader; + if (shiftFromNextHeader < leftOfStickyHeader) { + translation.left += shiftFromNextHeader; + } + } + } + + /** + * Returns the first item currently in the RecyclerView that is not obscured by a header. + * + * @param parent Recyclerview containing all the list items + * @return first item that is fully beneath a header + */ + private View getFirstViewUnobscuredByHeader(RecyclerView parent, View firstHeader) { + int step = 1; + int from = 0; + for (int i = from; i >= 0 && i <= parent.getChildCount() - 1; i += step) { + View child = parent.getChildAt(i); + if (!itemIsObscuredByHeader(parent, child, firstHeader, LinearLayout.VERTICAL)) { + return child; + } + } + return null; + } + + /** + * Determines if an item is obscured by a header + * + * + * @param parent + * @param item to determine if obscured by header + * @param header that might be obscuring the item + * @param orientation of the {@link RecyclerView} + * @return true if the item view is obscured by the header view + */ + private boolean itemIsObscuredByHeader(RecyclerView parent, View item, View header, int orientation) { + RecyclerView.LayoutParams layoutParams = (RecyclerView.LayoutParams) item.getLayoutParams(); + dimensionCalculator.initMargins(tempRect1, header); + + int adapterPosition = parent.getChildAdapterPosition(item); + if (adapterPosition == RecyclerView.NO_POSITION || headerProvider.getHeader(parent, adapterPosition) != header) { + // Resolves https://github.com/timehop/sticky-headers-recyclerview/issues/36 + // Handles an edge case where a trailing header is smaller than the current sticky header. + return false; + } + + if (orientation == LinearLayoutManager.VERTICAL) { + int itemTop = item.getTop() - layoutParams.topMargin; + int headerBottom = getListTop(parent) + header.getBottom() + tempRect1.bottom + tempRect1.top; + if (itemTop >= headerBottom) { + return false; + } + } else { + int itemLeft = item.getLeft() - layoutParams.leftMargin; + int headerRight = getListLeft(parent) + header.getRight() + tempRect1.right + tempRect1.left; + if (itemLeft >= headerRight) { + return false; + } + } + + return true; + } + + private int getListTop(RecyclerView view) { + if (view.getLayoutManager().getClipToPadding()) { + return view.getPaddingTop(); + } else { + return 0; + } + } + + private int getListLeft(RecyclerView view) { + if (view.getLayoutManager().getClipToPadding()) { + return view.getPaddingLeft(); + } else { + return 0; + } + } +} diff --git a/app/src/main/java/hikapro/com/backpack/presenter/adapters/helper/items/HeaderProvider.java b/app/src/main/java/hikapro/com/backpack/presenter/adapters/helper/items/HeaderProvider.java new file mode 100644 index 0000000..d760882 --- /dev/null +++ b/app/src/main/java/hikapro/com/backpack/presenter/adapters/helper/items/HeaderProvider.java @@ -0,0 +1,24 @@ +package hikapro.com.backpack.presenter.adapters.helper.items; + +import android.support.v7.widget.RecyclerView; +import android.view.View; + +/** + * Created by tariel on 30/04/16. + */ +public interface HeaderProvider { + + /** + * Will provide a header view for a given position in the RecyclerView + * + * @param recyclerView that will display the header + * @param position that will be headed by the header + * @return a header view for the given position and list + */ + View getHeader(RecyclerView recyclerView, int position); + + /** + * TODO: describe this functionality and its necessity + */ + void invalidate(); +} diff --git a/app/src/main/java/hikapro/com/backpack/presenter/adapters/helper/items/HeaderRenderer.java b/app/src/main/java/hikapro/com/backpack/presenter/adapters/helper/items/HeaderRenderer.java new file mode 100644 index 0000000..6bde81a --- /dev/null +++ b/app/src/main/java/hikapro/com/backpack/presenter/adapters/helper/items/HeaderRenderer.java @@ -0,0 +1,70 @@ +package hikapro.com.backpack.presenter.adapters.helper.items; + +import android.graphics.Canvas; +import android.graphics.Rect; +import android.support.v7.widget.RecyclerView; +import android.view.View; + +/** + * Created by tariel on 30/04/16. + */ +public class HeaderRenderer { + + private final DimensionCalculator dimensionCalculator; + + /** + * The following field is used as a buffer for internal calculations. Its sole purpose is to avoid + * allocating new Rect every time we need one. + */ + private final Rect mTempRect = new Rect(); + + public HeaderRenderer() { + this.dimensionCalculator = new DimensionCalculator(); + } + + /** + * Draws a header to a canvas, offsetting by some x and y amount + * + * @param recyclerView the parent recycler view for drawing the header into + * @param canvas the canvas on which to draw the header + * @param header the view to draw as the header + * @param offset a Rect used to define the x/y offset of the header. Specify x/y offset by setting + * the {@link Rect#left} and {@link Rect#top} properties, respectively. + */ + public void drawHeader(RecyclerView recyclerView, Canvas canvas, View header, Rect offset) { + canvas.save(); + + if (recyclerView.getLayoutManager().getClipToPadding()) { + // Clip drawing of headers to the padding of the RecyclerView. Avoids drawing in the padding + initClipRectForHeader(mTempRect, recyclerView, header); + canvas.clipRect(mTempRect); + } + + canvas.translate(offset.left, offset.top); + + header.draw(canvas); + canvas.restore(); + } + + /** + * Initializes a clipping rect for the header based on the margins of the header and the padding of the + * recycler. + * FIXME: Currently right margin in VERTICAL orientation and bottom margin in HORIZONTAL + * orientation are clipped so they look accurate, but the headers are not being drawn at the + * correctly smaller width and height respectively. + * + * @param clipRect {@link Rect} for clipping a provided header to the padding of a recycler view + * @param recyclerView for which to provide a header + * @param header for clipping + */ + private void initClipRectForHeader(Rect clipRect, RecyclerView recyclerView, View header) { + dimensionCalculator.initMargins(clipRect, header); + + clipRect.set( + recyclerView.getPaddingLeft(), + recyclerView.getPaddingTop(), + recyclerView.getWidth() - recyclerView.getPaddingRight() - clipRect.right, + recyclerView.getHeight() - recyclerView.getPaddingBottom()); + + } +} diff --git a/app/src/main/java/hikapro/com/backpack/presenter/adapters/helper/items/HeaderViewCache.java b/app/src/main/java/hikapro/com/backpack/presenter/adapters/helper/items/HeaderViewCache.java new file mode 100644 index 0000000..118aeb3 --- /dev/null +++ b/app/src/main/java/hikapro/com/backpack/presenter/adapters/helper/items/HeaderViewCache.java @@ -0,0 +1,56 @@ +package hikapro.com.backpack.presenter.adapters.helper.items; + +import android.support.v4.util.LongSparseArray; +import android.support.v7.widget.RecyclerView; +import android.view.View; +import android.view.ViewGroup; + +/** + * Created by tariel on 30/04/16. + */ +public class HeaderViewCache implements HeaderProvider { + + private final StickyHeaderAdapter adapter; + private final LongSparseArray headerViews = new LongSparseArray<>(); + + public HeaderViewCache(StickyHeaderAdapter adapter) { + this.adapter = adapter; + } + + @Override + public View getHeader(RecyclerView parent, int position) { + long headerId = adapter.getHeaderId(position); + + View header = headerViews.get(headerId); + if (header == null) { + //TODO - recycle views + RecyclerView.ViewHolder viewHolder = adapter.onCreateHeaderViewHolder(parent); + adapter.onBindHeaderViewHolder(viewHolder, position); + header = viewHolder.itemView; + if (header.getLayoutParams() == null) { + header.setLayoutParams(new ViewGroup.LayoutParams( + ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)); + } + + int widthSpec; + int heightSpec; + + widthSpec = View.MeasureSpec.makeMeasureSpec(parent.getWidth(), View.MeasureSpec.EXACTLY); + heightSpec = View.MeasureSpec.makeMeasureSpec(parent.getHeight(), View.MeasureSpec.UNSPECIFIED); + + int childWidth = ViewGroup.getChildMeasureSpec(widthSpec, + parent.getPaddingLeft() + parent.getPaddingRight(), header.getLayoutParams().width); + int childHeight = ViewGroup.getChildMeasureSpec(heightSpec, + parent.getPaddingTop() + parent.getPaddingBottom(), header.getLayoutParams().height); + header.measure(childWidth, childHeight); + header.layout(0, 0, header.getMeasuredWidth(), header.getMeasuredHeight()); + headerViews.put(headerId, header); + } + return header; + } + + @Override + public void invalidate() { + headerViews.clear(); + } +} diff --git a/app/src/main/java/hikapro/com/backpack/presenter/adapters/helper/items/ItemSwipeCallback.java b/app/src/main/java/hikapro/com/backpack/presenter/adapters/helper/items/ItemSwipeCallback.java new file mode 100644 index 0000000..13bc750 --- /dev/null +++ b/app/src/main/java/hikapro/com/backpack/presenter/adapters/helper/items/ItemSwipeCallback.java @@ -0,0 +1,104 @@ +package hikapro.com.backpack.presenter.adapters.helper.items; + +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.PorterDuff; +import android.graphics.drawable.ColorDrawable; +import android.graphics.drawable.Drawable; +import android.support.v4.content.ContextCompat; +import android.support.v7.widget.RecyclerView; +import android.support.v7.widget.helper.ItemTouchHelper; +import android.view.View; + +import hikapro.com.backpack.R; +import hikapro.com.backpack.presenter.adapters.ItemListAdapter; + +/** + * Created by tariel on 02/05/16. + */ +public class ItemSwipeCallback extends ItemTouchHelper.SimpleCallback { + + // we want to cache these and not allocate anything repeatedly in the onChildDraw method + Drawable background; + Drawable xMark; + int xMarkMargin; + boolean initiated; + ItemListAdapter adapter; + Context context; + + public ItemSwipeCallback(int dragDirs, int swipeDirs, ItemListAdapter adapter, Context context) { + super(dragDirs, swipeDirs); + this.adapter = adapter; + this.context = context; + } + + private void init() { + background = new ColorDrawable(Color.RED); + xMark = ContextCompat.getDrawable(context, R.drawable.ic_clear_24dp); + xMark.setColorFilter(Color.WHITE, PorterDuff.Mode.SRC_ATOP); + xMarkMargin = (int) context.getResources().getDimension(R.dimen.ic_clear_margin); + initiated = true; + } + + // not important, we don't want drag & drop + @Override + public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) { + return false; + } + + @Override + public int getSwipeDirs(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) { + int position = viewHolder.getAdapterPosition(); + if (adapter.isUndoOn() && adapter.isPendingRemoval(position)) { + return 0; + } + return super.getSwipeDirs(recyclerView, viewHolder); + } + + @Override + public void onSwiped(RecyclerView.ViewHolder viewHolder, int swipeDir) { + int swipedPosition = viewHolder.getAdapterPosition(); + + boolean undoOn = adapter.isUndoOn(); + if (undoOn) { + adapter.pendingRemoval(swipedPosition); + } else { + adapter.remove(swipedPosition); + } + } + + @Override + public void onChildDraw(Canvas c, RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive) { + View itemView = viewHolder.itemView; + + // not sure why, but this method get's called for viewholder that are already swiped away + if (viewHolder.getAdapterPosition() == -1) { + // not interested in those + return; + } + + if (!initiated) { + init(); + } + + // draw red background + background.setBounds(itemView.getRight() + (int) dX, itemView.getTop(), itemView.getRight(), itemView.getBottom()); + background.draw(c); + + // draw x mark + int itemHeight = itemView.getBottom() - itemView.getTop(); + int intrinsicWidth = xMark.getIntrinsicWidth(); + int intrinsicHeight = xMark.getIntrinsicWidth(); + + int xMarkLeft = itemView.getRight() - xMarkMargin - intrinsicWidth; + int xMarkRight = itemView.getRight() - xMarkMargin; + int xMarkTop = itemView.getTop() + (itemHeight - intrinsicHeight)/2; + int xMarkBottom = xMarkTop + intrinsicHeight; + xMark.setBounds(xMarkLeft, xMarkTop, xMarkRight, xMarkBottom); + + xMark.draw(c); + + super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive); + } +} diff --git a/app/src/main/java/hikapro/com/backpack/presenter/adapters/helper/items/ItemSwipeDecoration.java b/app/src/main/java/hikapro/com/backpack/presenter/adapters/helper/items/ItemSwipeDecoration.java new file mode 100644 index 0000000..784595e --- /dev/null +++ b/app/src/main/java/hikapro/com/backpack/presenter/adapters/helper/items/ItemSwipeDecoration.java @@ -0,0 +1,88 @@ +package hikapro.com.backpack.presenter.adapters.helper.items; + +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.drawable.ColorDrawable; +import android.graphics.drawable.Drawable; +import android.support.v7.widget.RecyclerView; +import android.view.View; + +/** + * Created by tariel on 02/05/16. + */ +public class ItemSwipeDecoration extends RecyclerView.ItemDecoration { + + // we want to cache this and not allocate anything repeatedly in the onDraw method + Drawable background; + boolean initiated; + + private void init() { + background = new ColorDrawable(Color.RED); + initiated = true; + } + + @Override + public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) { + + if (!initiated) { + init(); + } + + // only if animation is in progress + if (parent.getItemAnimator().isRunning()) { + + // some items might be animating down and some items might be animating up to close the gap left by the removed item + // this is not exclusive, both movement can be happening at the same time + // to reproduce this leave just enough items so the first one and the last one would be just a little off screen + // then remove one from the middle + + // find first child with translationY > 0 + // and last one with translationY < 0 + // we're after a rect that is not covered in recycler-view views at this point in time + View lastViewComingDown = null; + View firstViewComingUp = null; + + // this is fixed + int left = 0; + int right = parent.getWidth(); + + // this we need to find out + int top = 0; + int bottom = 0; + + // find relevant translating views + int childCount = parent.getLayoutManager().getChildCount(); + for (int i = 0; i < childCount; i++) { + View child = parent.getLayoutManager().getChildAt(i); + if (child.getTranslationY() < 0) { + // view is coming down + lastViewComingDown = child; + } else if (child.getTranslationY() > 0) { + // view is coming up + if (firstViewComingUp == null) { + firstViewComingUp = child; + } + } + } + + if (lastViewComingDown != null && firstViewComingUp != null) { + // views are coming down AND going up to fill the void + top = lastViewComingDown.getBottom() + (int) lastViewComingDown.getTranslationY(); + bottom = firstViewComingUp.getTop() + (int) firstViewComingUp.getTranslationY(); + } else if (lastViewComingDown != null) { + // views are going down to fill the void + top = lastViewComingDown.getBottom() + (int) lastViewComingDown.getTranslationY(); + bottom = lastViewComingDown.getBottom(); + } else if (firstViewComingUp != null) { + // views are coming up to fill the void + top = firstViewComingUp.getTop(); + bottom = firstViewComingUp.getTop() + (int) firstViewComingUp.getTranslationY(); + } + + background.setBounds(left, top, right, bottom); + background.draw(c); + + } + super.onDraw(c, parent, state); + } +} diff --git a/app/src/main/java/hikapro/com/backpack/presenter/adapters/helper/items/ItemVisibilityAdapter.java b/app/src/main/java/hikapro/com/backpack/presenter/adapters/helper/items/ItemVisibilityAdapter.java new file mode 100644 index 0000000..2d6547d --- /dev/null +++ b/app/src/main/java/hikapro/com/backpack/presenter/adapters/helper/items/ItemVisibilityAdapter.java @@ -0,0 +1,19 @@ +package hikapro.com.backpack.presenter.adapters.helper.items; + +/** + * Created by tariel on 30/04/16. + */ +public interface ItemVisibilityAdapter { + + /** + * + * Return true the specified adapter position is visible, false otherwise + * + * The implementation of this method will typically return true if + * the position is between the layout manager's findFirstVisibleItemPosition + * and findLastVisibleItemPosition (inclusive). + * + * @param position the adapter position + */ + boolean isPositionVisible(final int position); +} diff --git a/app/src/main/java/hikapro/com/backpack/presenter/adapters/helper/items/StickyHeaderAdapter.java b/app/src/main/java/hikapro/com/backpack/presenter/adapters/helper/items/StickyHeaderAdapter.java new file mode 100644 index 0000000..f21ef78 --- /dev/null +++ b/app/src/main/java/hikapro/com/backpack/presenter/adapters/helper/items/StickyHeaderAdapter.java @@ -0,0 +1,15 @@ +package hikapro.com.backpack.presenter.adapters.helper.items; + +import android.support.v7.widget.RecyclerView; +import android.view.ViewGroup; + +/** + * Created by tariel on 01/05/16. + */ +public interface StickyHeaderAdapter { + + long getHeaderId(int position); + VH onCreateHeaderViewHolder(ViewGroup parent); + void onBindHeaderViewHolder(VH holder, int position); + int getItemCount(); +} diff --git a/app/src/main/java/hikapro/com/backpack/presenter/adapters/helper/items/StickyHeaderDecoration.java b/app/src/main/java/hikapro/com/backpack/presenter/adapters/helper/items/StickyHeaderDecoration.java new file mode 100644 index 0000000..074b954 --- /dev/null +++ b/app/src/main/java/hikapro/com/backpack/presenter/adapters/helper/items/StickyHeaderDecoration.java @@ -0,0 +1,158 @@ +package hikapro.com.backpack.presenter.adapters.helper.items; + +import android.graphics.Canvas; +import android.graphics.Rect; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.util.SparseArray; +import android.view.View; +import android.widget.LinearLayout; + +/** + * Created by tariel on 01/05/16. + */ +public class StickyHeaderDecoration extends RecyclerView.ItemDecoration { + + private final StickyHeaderAdapter adapter; + private final ItemVisibilityAdapter visibilityAdapter; + private final SparseArray headerRects = new SparseArray<>(); + private final HeaderProvider headerProvider; + private final HeaderPositionCalculator headerPositionCalculator; + private final HeaderRenderer renderer; + private final DimensionCalculator dimensionCalculator; + + private final Rect rect = new Rect(); + + + public StickyHeaderDecoration(StickyHeaderAdapter adapter) { + this(adapter, new DimensionCalculator(), null); + } + + private StickyHeaderDecoration(StickyHeaderAdapter adapter, + DimensionCalculator dimensionCalculator, ItemVisibilityAdapter visibilityAdapter) { + this(adapter, dimensionCalculator, new HeaderRenderer(), new HeaderViewCache(adapter), visibilityAdapter); + } + + private StickyHeaderDecoration(StickyHeaderAdapter adapter, DimensionCalculator dimensionCalculator, + HeaderRenderer headerRenderer, HeaderProvider headerProvider, + ItemVisibilityAdapter visibilityAdapter) { + this(adapter, headerRenderer, dimensionCalculator, headerProvider, + new HeaderPositionCalculator(adapter, headerProvider, + dimensionCalculator), visibilityAdapter); + } + + private StickyHeaderDecoration(StickyHeaderAdapter adapter, HeaderRenderer headerRenderer, + DimensionCalculator dimensionCalculator, HeaderProvider headerProvider, + HeaderPositionCalculator headerPositionCalculator, ItemVisibilityAdapter visibilityAdapter) { + this.adapter = adapter; + this.headerProvider = headerProvider; + this.renderer = headerRenderer; + this.dimensionCalculator = dimensionCalculator; + this.headerPositionCalculator = headerPositionCalculator; + this.visibilityAdapter = visibilityAdapter; + } + + + @Override + public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) { + super.getItemOffsets(outRect, view, parent, state); + + int itemPosition = parent.getChildAdapterPosition(view); + if (itemPosition == RecyclerView.NO_POSITION) { + return; + } + if (headerPositionCalculator.hasNewHeader(itemPosition)) { + View header = getHeaderView(parent, itemPosition); + setItemOffsetsForHeader(outRect, header, LinearLayout.VERTICAL); + } + } + + /** + * Sets the offsets for the first item in a section to make room for the header view + * + * @param itemOffsets rectangle to define offsets for the item + * @param header view used to calculate offset for the item + * @param orientation used to calculate offset for the item + */ + private void setItemOffsetsForHeader(Rect itemOffsets, View header, int orientation) { + dimensionCalculator.initMargins(rect, header); + if (orientation == LinearLayoutManager.VERTICAL) { + itemOffsets.top = header.getHeight() + rect.top + rect.bottom; + } else { + itemOffsets.left = header.getWidth() + rect.left + + rect.right; + } + } + + @Override + public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) { + super.onDrawOver(c, parent, state); + + final int childCount = parent.getChildCount(); + if (childCount <= 0 || adapter.getItemCount() <= 0) { + return; + } + + for (int i = 0; i < childCount; i++) { + View itemView = parent.getChildAt(i); + int position = parent.getChildAdapterPosition(itemView); + if (position == RecyclerView.NO_POSITION) { + continue; + } + + boolean hasStickyHeader = headerPositionCalculator.hasStickyHeader(itemView, LinearLayout.VERTICAL, position); + if (hasStickyHeader || headerPositionCalculator.hasNewHeader(position)) { + View header = headerProvider.getHeader(parent, position); + //re-use existing Rect, if any. + Rect headerOffset = headerRects.get(position); + if (headerOffset == null) { + headerOffset = new Rect(); + headerRects.put(position, headerOffset); + } + headerPositionCalculator.initHeaderBounds(headerOffset, parent, header, itemView, hasStickyHeader); + renderer.drawHeader(parent, c, header, headerOffset); + } + } + } + + /** + * Gets the position of the header under the specified (x, y) coordinates. + * + * @param x x-coordinate + * @param y y-coordinate + * @return position of header, or -1 if not found + */ + public int findHeaderPositionUnder(int x, int y) { + for (int i = 0; i < headerRects.size(); i++) { + Rect rect = headerRects.get(headerRects.keyAt(i)); + if (rect.contains(x, y)) { + int position = headerRects.keyAt(i); + if (visibilityAdapter == null || visibilityAdapter.isPositionVisible(position)) { + return position; + } + } + } + return -1; + } + + /** + * Gets the header view for the associated position. If it doesn't exist yet, it will be + * created, measured, and laid out. + * + * @param parent the recyclerview + * @param position the position to get the header view for + * @return Header view + */ + public View getHeaderView(RecyclerView parent, int position) { + return headerProvider.getHeader(parent, position); + } + + /** + * Invalidates cached headers. This does not invalidate the recyclerview, you should do that manually after + * calling this method. + */ + public void invalidateHeaders() { + headerProvider.invalidate(); + headerRects.clear(); + } +} diff --git a/app/src/main/java/hikapro/com/backpack/presenter/adapters/helper/ItemTouchHelperAdapter.java b/app/src/main/java/hikapro/com/backpack/presenter/adapters/helper/sets/ItemTouchHelperAdapter.java similarity index 77% rename from app/src/main/java/hikapro/com/backpack/presenter/adapters/helper/ItemTouchHelperAdapter.java rename to app/src/main/java/hikapro/com/backpack/presenter/adapters/helper/sets/ItemTouchHelperAdapter.java index bce3ead..a1377aa 100644 --- a/app/src/main/java/hikapro/com/backpack/presenter/adapters/helper/ItemTouchHelperAdapter.java +++ b/app/src/main/java/hikapro/com/backpack/presenter/adapters/helper/sets/ItemTouchHelperAdapter.java @@ -1,4 +1,4 @@ -package hikapro.com.backpack.presenter.adapters.helper; +package hikapro.com.backpack.presenter.adapters.helper.sets; /** * Created by N551 on 25.04.2016. diff --git a/app/src/main/java/hikapro/com/backpack/presenter/adapters/helper/ItemTouchHelperViewHolder.java b/app/src/main/java/hikapro/com/backpack/presenter/adapters/helper/sets/ItemTouchHelperViewHolder.java similarity index 73% rename from app/src/main/java/hikapro/com/backpack/presenter/adapters/helper/ItemTouchHelperViewHolder.java rename to app/src/main/java/hikapro/com/backpack/presenter/adapters/helper/sets/ItemTouchHelperViewHolder.java index 0060002..96085d0 100644 --- a/app/src/main/java/hikapro/com/backpack/presenter/adapters/helper/ItemTouchHelperViewHolder.java +++ b/app/src/main/java/hikapro/com/backpack/presenter/adapters/helper/sets/ItemTouchHelperViewHolder.java @@ -1,4 +1,4 @@ -package hikapro.com.backpack.presenter.adapters.helper; +package hikapro.com.backpack.presenter.adapters.helper.sets; /** * Created by N551 on 25.04.2016. diff --git a/app/src/main/java/hikapro/com/backpack/presenter/adapters/helper/OnStartDragListener.java b/app/src/main/java/hikapro/com/backpack/presenter/adapters/helper/sets/OnStartDragListener.java similarity index 78% rename from app/src/main/java/hikapro/com/backpack/presenter/adapters/helper/OnStartDragListener.java rename to app/src/main/java/hikapro/com/backpack/presenter/adapters/helper/sets/OnStartDragListener.java index aca2fbe..02770ec 100644 --- a/app/src/main/java/hikapro/com/backpack/presenter/adapters/helper/OnStartDragListener.java +++ b/app/src/main/java/hikapro/com/backpack/presenter/adapters/helper/sets/OnStartDragListener.java @@ -1,4 +1,4 @@ -package hikapro.com.backpack.presenter.adapters.helper; +package hikapro.com.backpack.presenter.adapters.helper.sets; import android.support.v7.widget.RecyclerView; diff --git a/app/src/main/java/hikapro/com/backpack/presenter/adapters/helper/SimpleItemTouchHelperCallback.java b/app/src/main/java/hikapro/com/backpack/presenter/adapters/helper/sets/SimpleItemTouchHelperCallback.java similarity index 98% rename from app/src/main/java/hikapro/com/backpack/presenter/adapters/helper/SimpleItemTouchHelperCallback.java rename to app/src/main/java/hikapro/com/backpack/presenter/adapters/helper/sets/SimpleItemTouchHelperCallback.java index c8774b3..0e70a70 100644 --- a/app/src/main/java/hikapro/com/backpack/presenter/adapters/helper/SimpleItemTouchHelperCallback.java +++ b/app/src/main/java/hikapro/com/backpack/presenter/adapters/helper/sets/SimpleItemTouchHelperCallback.java @@ -1,4 +1,4 @@ -package hikapro.com.backpack.presenter.adapters.helper; +package hikapro.com.backpack.presenter.adapters.helper.sets; import android.graphics.Canvas; import android.support.v7.widget.RecyclerView; diff --git a/app/src/main/java/hikapro/com/backpack/view/View.java b/app/src/main/java/hikapro/com/backpack/view/View.java index 9e02aba..45148c1 100644 --- a/app/src/main/java/hikapro/com/backpack/view/View.java +++ b/app/src/main/java/hikapro/com/backpack/view/View.java @@ -1,12 +1,11 @@ package hikapro.com.backpack.view; import android.content.Context; -import android.widget.Toast; import hikapro.com.backpack.model.entities.Item; import hikapro.com.backpack.model.entities.Set; import hikapro.com.backpack.presenter.Presenter; -import hikapro.com.backpack.presenter.adapters.helper.OnStartDragListener; +import hikapro.com.backpack.presenter.adapters.helper.sets.OnStartDragListener; /** * Created by tariel on 19/04/16. diff --git a/app/src/main/java/hikapro/com/backpack/view/fragments/ItemListFragment.java b/app/src/main/java/hikapro/com/backpack/view/fragments/ItemListFragment.java index 36bef71..67a4c3a 100644 --- a/app/src/main/java/hikapro/com/backpack/view/fragments/ItemListFragment.java +++ b/app/src/main/java/hikapro/com/backpack/view/fragments/ItemListFragment.java @@ -7,8 +7,12 @@ import android.content.Context; import android.os.Bundle; import android.util.Log; import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; +import android.widget.SearchView; import android.widget.Toast; import hikapro.com.backpack.R; @@ -18,7 +22,8 @@ import hikapro.com.backpack.presenter.ItemListPresenter; import hikapro.com.backpack.presenter.Presenter; -public class ItemListFragment extends Fragment implements hikapro.com.backpack.view.View.ItemList { +public class ItemListFragment extends Fragment implements hikapro.com.backpack.view.View.ItemList, + SearchView.OnQueryTextListener { private static final String BUNDLE_SET_KEY = "BUNDLE_SET_KEY"; private hikapro.com.backpack.view.View.ActivityCallback activityCallback; @@ -40,6 +45,34 @@ public class ItemListFragment extends Fragment implements hikapro.com.backpack.v return ret; } + @Override + public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { + + inflater.inflate(R.menu.menu_main, menu); + + final MenuItem item = menu.findItem(R.id.action_search); + final SearchView searchView = (SearchView) item.getActionView(); + searchView.setOnQueryTextListener(this); + super.onCreateOptionsMenu(menu, inflater); + + } + + @Override + public boolean onQueryTextSubmit(String query) { + return false; + } + + @Override + public boolean onQueryTextChange(String newText) { + presenter.filter(newText); + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + return super.onOptionsItemSelected(item); + } + // life cycle --> @Override public void onAttach(Context context) { @@ -65,6 +98,7 @@ public class ItemListFragment extends Fragment implements hikapro.com.backpack.v @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + setHasOptionsMenu(true); Log.i(this.toString(), "onCreate"); } @Override diff --git a/app/src/main/java/hikapro/com/backpack/view/fragments/SetListFragment.java b/app/src/main/java/hikapro/com/backpack/view/fragments/SetListFragment.java index d2b4985..9537794 100644 --- a/app/src/main/java/hikapro/com/backpack/view/fragments/SetListFragment.java +++ b/app/src/main/java/hikapro/com/backpack/view/fragments/SetListFragment.java @@ -6,7 +6,6 @@ import android.app.Fragment; import android.content.Context; import android.os.Bundle; import android.support.v7.widget.RecyclerView; -import android.support.v7.widget.helper.ItemTouchHelper; import android.util.Log; import android.view.LayoutInflater; import android.view.View; @@ -14,7 +13,7 @@ import android.view.ViewGroup; import hikapro.com.backpack.model.entities.Set; import hikapro.com.backpack.presenter.Presenter; -import hikapro.com.backpack.presenter.adapters.helper.OnStartDragListener; +import hikapro.com.backpack.presenter.adapters.helper.sets.OnStartDragListener; public class SetListFragment extends Fragment implements hikapro.com.backpack.view.View.SetList, OnStartDragListener{ diff --git a/app/src/main/java/hikapro/com/backpack/view/recycler/CategoryViewHolder.java b/app/src/main/java/hikapro/com/backpack/view/recycler/CategoryViewHolder.java deleted file mode 100644 index 62352e9..0000000 --- a/app/src/main/java/hikapro/com/backpack/view/recycler/CategoryViewHolder.java +++ /dev/null @@ -1,31 +0,0 @@ -package hikapro.com.backpack.view.recycler; - -import android.support.v7.widget.LinearLayoutManager; -import android.support.v7.widget.RecyclerView; -import android.view.View; -import android.widget.LinearLayout; -import android.widget.TextView; - -import hikapro.com.backpack.R; - -/** - * Created by tariel on 20/04/16. - */ -public class CategoryViewHolder extends RecyclerView.ViewHolder { - - public LinearLayout section; - public TextView sectionText; - public RecyclerView itemsRecycler; - - public CategoryViewHolder(View v) { - super(v); - setupViews(v); - } - - private void setupViews(View view) { - section = (LinearLayout)view.findViewById(R.id.linear); - sectionText = (TextView)view.findViewById(R.id.section_text); - itemsRecycler = (RecyclerView) view.findViewById(R.id.category_inner_recycler); - } - -} diff --git a/app/src/main/java/hikapro/com/backpack/view/recycler/HeaderViewHolder.java b/app/src/main/java/hikapro/com/backpack/view/recycler/HeaderViewHolder.java new file mode 100644 index 0000000..95de0c9 --- /dev/null +++ b/app/src/main/java/hikapro/com/backpack/view/recycler/HeaderViewHolder.java @@ -0,0 +1,25 @@ +package hikapro.com.backpack.view.recycler; + +import android.support.v7.widget.RecyclerView; +import android.view.View; +import android.widget.TextView; + +import hikapro.com.backpack.R; + +/** + * Created by tariel on 01/05/16. + */ +public class HeaderViewHolder extends RecyclerView.ViewHolder { + + public int id; + public TextView title; + + public HeaderViewHolder(View v) { + super(v); + setupViews(v); + } + + private void setupViews(View view) { + title = (TextView) view.findViewById(R.id.header); + } +} diff --git a/app/src/main/java/hikapro/com/backpack/view/recycler/ItemViewHolder.java b/app/src/main/java/hikapro/com/backpack/view/recycler/ItemViewHolder.java index 5a86246..b448f3f 100644 --- a/app/src/main/java/hikapro/com/backpack/view/recycler/ItemViewHolder.java +++ b/app/src/main/java/hikapro/com/backpack/view/recycler/ItemViewHolder.java @@ -2,18 +2,21 @@ package hikapro.com.backpack.view.recycler; import android.support.v7.widget.RecyclerView; import android.view.View; +import android.widget.Button; import android.widget.TextView; import hikapro.com.backpack.R; + /** - * Created by tariel on 20/04/16. + * Created by tariel on 01/05/16. */ public class ItemViewHolder extends RecyclerView.ViewHolder { - public TextView title; public int id; public int categoryId; + public TextView title; + public Button undoButton; public ItemViewHolder(View v) { super(v); @@ -21,6 +24,7 @@ public class ItemViewHolder extends RecyclerView.ViewHolder { } private void setupViews(View view) { - title = (TextView) view.findViewById(R.id.item_text); + title = (TextView) view.findViewById(R.id.item_txt); + undoButton = (Button) view.findViewById(R.id.undo_button); } } diff --git a/app/src/main/java/hikapro/com/backpack/view/recycler/SetViewHolder.java b/app/src/main/java/hikapro/com/backpack/view/recycler/SetViewHolder.java index e57463f..ed82162 100644 --- a/app/src/main/java/hikapro/com/backpack/view/recycler/SetViewHolder.java +++ b/app/src/main/java/hikapro/com/backpack/view/recycler/SetViewHolder.java @@ -8,7 +8,7 @@ import android.view.View; import android.widget.TextView; import hikapro.com.backpack.R; -import hikapro.com.backpack.presenter.adapters.helper.ItemTouchHelperViewHolder; +import hikapro.com.backpack.presenter.adapters.helper.sets.ItemTouchHelperViewHolder; /** * Created by tariel on 20/04/16. diff --git a/app/src/main/res/drawable/ic_clear_24dp.xml b/app/src/main/res/drawable/ic_clear_24dp.xml new file mode 100644 index 0000000..ede4b71 --- /dev/null +++ b/app/src/main/res/drawable/ic_clear_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/layout/fragment_item_list.xml b/app/src/main/res/layout/fragment_item_list.xml index 361ca3b..ca43fc4 100644 --- a/app/src/main/res/layout/fragment_item_list.xml +++ b/app/src/main/res/layout/fragment_item_list.xml @@ -6,7 +6,7 @@ android:orientation="vertical"> - - \ No newline at end of file diff --git a/app/src/main/res/layout/section.xml b/app/src/main/res/layout/section.xml deleted file mode 100644 index 973fa15..0000000 --- a/app/src/main/res/layout/section.xml +++ /dev/null @@ -1,49 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/sticky_header_layout.xml b/app/src/main/res/layout/sticky_header_layout.xml new file mode 100644 index 0000000..dc7a091 --- /dev/null +++ b/app/src/main/res/layout/sticky_header_layout.xml @@ -0,0 +1,5 @@ + + \ No newline at end of file diff --git a/app/src/main/res/layout/view_header.xml b/app/src/main/res/layout/view_header.xml new file mode 100644 index 0000000..115b9c6 --- /dev/null +++ b/app/src/main/res/layout/view_header.xml @@ -0,0 +1,16 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/layout/view_item.xml b/app/src/main/res/layout/view_item.xml new file mode 100644 index 0000000..d7557bf --- /dev/null +++ b/app/src/main/res/layout/view_item.xml @@ -0,0 +1,30 @@ + + + + +