Merge branch 'dev'

This commit is contained in:
Tariel Hlontsi 2016-05-03 18:49:24 +03:00
commit eaa2ba996e
73 changed files with 3715 additions and 439 deletions

View File

@ -1 +1 @@
BackPack
packwithme

View File

@ -0,0 +1,7 @@
<component name="ProjectDictionaryState">
<dictionary name="tariel">
<words>
<w>tariel</w>
</words>
</dictionary>
</component>

View File

@ -5,6 +5,7 @@
<GradleProjectSettings>
<option name="distributionType" value="DEFAULT_WRAPPED" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="gradleJvm" value="1.8" />
<option name="modules">
<set>
<option value="$PROJECT_DIR$" />

View File

@ -37,37 +37,10 @@
<ConfirmationsSetting value="0" id="Add" />
<ConfirmationsSetting value="0" id="Remove" />
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_7" default="true" assert-keyword="true" jdk-15="true" project-jdk-name="1.7" project-jdk-type="JavaSDK">
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" default="true" assert-keyword="true" jdk-15="true" project-jdk-name="1.8" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" />
</component>
<component name="ProjectType">
<option name="id" value="Android" />
</component>
<component name="masterDetails">
<states>
<state key="ProjectJDKs.UI">
<settings>
<last-edited>1.8</last-edited>
<splitter-proportions>
<option name="proportions">
<list>
<option value="0.2" />
</list>
</option>
</splitter-proportions>
</settings>
</state>
<state key="ScopeChooserConfigurable.UI">
<settings>
<splitter-proportions>
<option name="proportions">
<list>
<option value="0.2" />
</list>
</option>
</splitter-proportions>
</settings>
</state>
</states>
</component>
</project>

View File

@ -32,11 +32,10 @@ dependencies {
compile 'com.squareup.retrofit2:retrofit:2.0.1'
compile 'com.squareup.retrofit2:converter-gson:2.0.1'
compile 'com.squareup.okhttp3:okhttp:3.2.0'
compile 'com.squareup.okhttp:logging-interceptor:2.7.0'
compile 'com.android.support:support-v4:23.3.0'
compile 'com.android.support:appcompat-v7:23.3.0'
compile 'com.android.support:design:23.3.0'
compile 'com.android.support:support-v4:23.3.0'
compile 'com.android.support:cardview-v7:23.3.0'
}

View File

@ -4,6 +4,7 @@
<uses-permission android:name="android.permission.INTERNET" />
<application
android:name=".App"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"

View File

@ -0,0 +1,22 @@
package hikapro.com.backpack;
import android.app.Application;
import android.content.Context;
/**
* Created by tariel on 27/04/16.
*/
public class App extends Application {
private static Context context;
@Override
public void onCreate() {
super.onCreate();
App.context = getApplicationContext();
}
public static Context getAppContext() {
return App.context;
}
}

View File

@ -5,44 +5,181 @@ import android.app.Fragment;
import android.app.FragmentManager;
import android.app.FragmentTransaction;
import android.os.Bundle;
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.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.view.View;
import hikapro.com.backpack.view.fragments.ItemDetailFragment;
import hikapro.com.backpack.view.fragments.ItemListFragment;
import hikapro.com.backpack.view.fragments.SetListFragment;
public class MainActivity extends Activity implements View.ActivityCallback {
private static String TAG = "TAG";
private FragmentManager fragmentManager;
private final StateMaintainer stateMaintainer =
new StateMaintainer(getFragmentManager(), MainActivity.class.getName());
// life cycle -->
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
fragmentManager = getFragmentManager();
Fragment fragment = fragmentManager.findFragmentByTag(TAG);
if (fragment == null)
replaceFragment(SetListFragment.construct(), false);
stateMaintainer.init();
if (fragmentManager.getBackStackEntryCount() == 0 && savedInstanceState == null) {
startSetListFragment();
} else {
Fragment fragment = fragmentManager.findFragmentByTag(SetListFragment.class.getName());
if (fragment != null) {
SetListFragment view = (SetListFragment) fragment;
SetListPresenter presenter = stateMaintainer.get(SetListPresenter.class.getName());
SetModel model = stateMaintainer.get(SetModel.class.getName());
view.setPresenter(presenter);
presenter.setView(view);
presenter.setModel(model);
model.setPresenter(presenter);
}
fragment = fragmentManager.findFragmentByTag(ItemListFragment.class.getName());
if (fragment != null) {
ItemListFragment view = (ItemListFragment) fragment;
ItemListPresenter presenter = stateMaintainer.get(ItemListPresenter.class.getName());
ItemModel model = stateMaintainer.get(ItemModel.class.getName());
view.setPresenter(presenter);
presenter.setView(view);
presenter.setModel(model);
model.setPresenter(presenter);
}
fragment = fragmentManager.findFragmentByTag(ItemDetailFragment.class.getName());
if (fragment != null) {
ItemDetailFragment view = (ItemDetailFragment) fragment;
ItemDetailPresenter presenter = stateMaintainer.get(ItemDetailPresenter.class.getName());
DetailModel model = stateMaintainer.get(DetailModel.class.getName());
view.setPresenter(presenter);
presenter.setView(view);
presenter.setModel(model);
model.setPresenter(presenter);
}
}
Log.i("On create", "Activity");
}
@Override
protected void onStart() {
super.onStart();
Log.i("onStart", "Activity");
}
@Override
protected void onRestart() {
super.onRestart();
Log.i("onRestart", "Activity");
}
@Override
protected void onResume() {
super.onResume();
Log.i("onResume", "Activity");
}
@Override
protected void onPause() {
super.onPause();
Log.i("onPause", "Activity");
}
@Override
protected void onStop() {
super.onStop();
Log.i("onStop", "Activity");
}
@Override
protected void onDestroy() {
super.onDestroy();
Log.i("onDestroy", "Activity");
}
// life cycle <--
@Override
public void startSetListFragment() {
SetListFragment view = SetListFragment.construct();
SetListPresenter presenter = new SetListPresenter();
SetModel model = new SetModel();
view.setPresenter(presenter);
//presenter.setView(view);
presenter.setModel(model);
model.setPresenter(presenter);
replaceFragment(view, false, SetListFragment.class.getName());
stateMaintainer.put(presenter);
stateMaintainer.put(model);
}
@Override
public void startItemListFragment(Set set) {
replaceFragment(ItemListFragment.newFromSet(set), true);
ItemListFragment view = ItemListFragment.newFromSet(set);
ItemListPresenter presenter = new ItemListPresenter();
ItemModel model = new ItemModel();
view.setPresenter(presenter);
presenter.setView(view);
presenter.setModel(model);
model.setPresenter(presenter);
replaceFragment(view, true, ItemListFragment.class.getName());
stateMaintainer.put(presenter);
stateMaintainer.put(model);
}
@Override
public void startItemDetailFragment() {
public void startItemDetailFragment(Item item) {
ItemDetailFragment view = ItemDetailFragment.newFromItem(item);
ItemDetailPresenter presenter = new ItemDetailPresenter();
DetailModel model = new DetailModel();
view.setPresenter(presenter);
presenter.setView(view);
presenter.setModel(model);
model.setPresenter(presenter);
replaceFragment(view, true, ItemDetailFragment.class.getName());
stateMaintainer.put(presenter);
stateMaintainer.put(model);
}
private void replaceFragment(Fragment fragment, boolean addBackStack) {
private void replaceFragment(Fragment fragment, boolean addBackStack, String tag) {
FragmentTransaction transaction = fragmentManager.beginTransaction();
transaction.replace(R.id.container, fragment, TAG);
transaction.replace(R.id.container, fragment, tag);
if (addBackStack)
transaction.addToBackStack(null);
transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
transaction.commit();
}
}

View File

@ -0,0 +1,97 @@
package hikapro.com.backpack;
import android.app.Fragment;
import android.app.FragmentManager;
import android.os.Bundle;
import android.util.Log;
import java.lang.ref.WeakReference;
import java.util.HashMap;
/**
* Created by tariel on 24/04/16.
*/
public class StateMaintainer {
protected final String TAG = getClass().getSimpleName();
private final String stateMaintainerTag;
private final WeakReference<FragmentManager> fragmentManager;
private StateMngFragment stateMngFragment;
private boolean isRecreating;
public StateMaintainer(FragmentManager fragmentManager, String stateMaintainerTAG) {
this.fragmentManager = new WeakReference<>(fragmentManager);
this.stateMaintainerTag = stateMaintainerTAG;
}
public boolean init() {
try {
stateMngFragment = (StateMngFragment)
fragmentManager.get().findFragmentByTag(stateMaintainerTag);
if (stateMngFragment == null) {
stateMngFragment = new StateMngFragment();
fragmentManager.get().beginTransaction()
.add(stateMngFragment, stateMaintainerTag).commit();
isRecreating = false;
return true;
} else {
isRecreating = true;
return false;
}
} catch (NullPointerException e) {
return false;
}
}
public boolean wasRecreated() { return isRecreating; }
public void put(String key, Object obj) {
stateMngFragment.put(key, obj);
}
public void put(Object obj) {
put(obj.getClass().getName(), obj);
}
public <T> T get(String key) {
return stateMngFragment.get(key);
}
public boolean hasKey(String key) {
return stateMngFragment.get(key) != null;
}
public static class StateMngFragment extends Fragment {
private HashMap<String, Object> data = new HashMap<>();
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Grants that the fragment will be preserved
setRetainInstance(true);
}
public void put(String key, Object obj) {
data.put(key, obj);
Log.i(this.toString(), String.format("Put object %s Total count %d", key, data.size()));
}
public void put(Object object) {
put(object.getClass().getName(), object);
}
@SuppressWarnings("unchecked")
public <T> T get(String key) {
Log.i(this.toString(), String.format("Get object %s Total count %d", key, data.size()));
return (T) data.get(key);
}
}
}

View File

@ -6,20 +6,31 @@ 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.model.entities.Timestamp;
import hikapro.com.backpack.model.entities.Updates;
import retrofit2.Call;
import retrofit2.http.GET;
import retrofit2.http.Query;
/**
* Created by tariel on 19/04/16.
*/
public interface Api {
@GET("/api/v1/backpack/items")
@GET("api/v1/backpack/items")
Call<List<Item>> getItems();
@GET("/api/v1/backpack/item_categories")
@GET("api/v1/backpack/item_categories")
Call<List<Category>> getItemCategories();
@GET("/api/v1/backpack/sets")
@GET("api/v1/backpack/sets")
Call<List<Set>> getSets();
@GET("api/v1/backpack/updates/timestamp")
Call<Timestamp> getTimestamp();
@GET("api/v1/backpack/updates/all")
Call<Updates> getUpdates(@Query("timestamp") long timestamp);
}

View File

@ -0,0 +1,76 @@
package hikapro.com.backpack.model;
import android.os.Message;
import hikapro.com.backpack.model.entities.Item;
import hikapro.com.backpack.presenter.Presenter;
/**
* Created by tariel on 23/04/16.
*/
public class DetailModel implements Model.Detail {
private Presenter.ItemDetail presenter;
private Item item;
public DetailModel() {
}
// detail -->
@Override
public int getCount() {
return 1;
}
@Override
public Item findItem(int id) {
return item;
}
// detail <--
// events -->
@Override
public void notifyDataSetChanged() {
presenter.notifyDataSetChanged();
}
@Override
public void onDestroy(boolean isConfigurationChanging) {
if ( !isConfigurationChanging ) {
presenter = null;
}
}
private void sendMessage(String message) {
presenter.showMessage(message);
}
// events <--
// process -->
@Override
public void executeQuery() {
notifyDataSetChanged();
}
// process <--
@Override
public void setPresenter(Presenter.ItemDetail presenter) {
this.presenter = presenter;
this.item = presenter.getCurrentItem();
}
@Override
public Presenter.ItemDetail getPresenter() {
return presenter;
}
@Override
public void onEvent(Message event) {
}
}

View File

@ -1,49 +1,63 @@
package hikapro.com.backpack.model;
import android.os.Message;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Hashtable;
import java.util.List;
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.presenter.Presenter;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
/**
* Created by tariel on 22/04/16.
*/
public class ItemModel implements Model.Item {
private Api api;
private Presenter.ItemList presenter;
private List<Category> rawCategories;
private List<Category> sortedCategories;
private List<Item> rawItems;
private Hashtable<Category, List<Item>> items;
private DAO dao;
private int currentSet;
private Hashtable<Integer, Category> categoriesCache;
private List<Item> itemsCache;
private List<Item> itemsDiscardCache;
public ItemModel(Presenter.ItemList presenter) {
this.api = RestClient.getApi();
this.presenter = presenter;
private Hashtable<Category, List<Item>> items;
private Hashtable<Integer, List<Item>> cache;
public ItemModel() {
this.rawCategories = new ArrayList<>();
this.rawItems = new ArrayList<>();
this.sortedCategories = new ArrayList<>();
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();
dao.registerObserver(this);
}
// categories -->
@Override
public Category getCategoryByPosition(int position) {
return sortedCategories.get(position);
}
@Override
public int getCategoriesCount() {
return sortedCategories.size();
Category ret = null;
if (cache.containsKey(currentSet))
ret = categoriesCache.get(cache.get(currentSet).get(position).getCategory());
return ret;
}
// categories <--
@ -52,34 +66,108 @@ public class ItemModel implements Model.Item {
@Override
public int insertItem(Item item) {
List<Item> 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) { // TODO rename to find
return null;
public Item findItem(int id) {
List<Item> items = cache.get(currentSet);
Item item = null;
if (items != null)
{
for (Item i : items) {
if (i.getId() == id) {
item = i;
break;
}
}
}
return item;
}
@Override
public Item getItemByPosition(int categoryId, int position) {
public Item getItemByPosition(int position) {
Item ret = null;
Category category = findSortedCategory(categoryId);
if (category != null) {
ret = items.get(category).get(position);
}
if (cache.containsKey(currentSet))
ret = cache.get(currentSet).get(position);
return ret;
}
@Override
public int getItemsCount(int categoryId) {
int ret = 0;
Category category = findSortedCategory(categoryId);
if (category != null) {
ret = items.get(category).size();
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<Item> newList = new ArrayList<>(20);
List<Item> oldList = cache.get(currentSet);
for (Item item : oldList) {
name = item.getName().toLowerCase();
if (name.contains(query)) {
newList.add(item);
}
}
cache.put(currentSet, newList);
}
return ret;
}
@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 -->
@ -91,57 +179,93 @@ public class ItemModel implements Model.Item {
@Override
public void onDestroy(boolean isConfigurationChanging) {
if ( !isConfigurationChanging ) {
presenter = null;
}
}
@Override
public void sendMessage(String message) {
private void sendMessage(String message) {
presenter.showMessage(message);
}
// events <--
// process -->
@Override
public void executeQuery() {
loadCategories();
loadItems();
}
private void loadCategories() {
Call<List<Category>> call = api.getItemCategories();
call.enqueue(new Callback<List<Category>>() {
@Override
public void onResponse(Call<List<Category>> call, Response<List<Category>> response) {
int statusCode = response.code();
rawCategories = response.body();
}
@Override
public void onFailure(Call<List<Category>> call, Throwable t) {
Message command;
if (cache.contains(currentSet)) {
notifyDataSetChanged();
} else {
if (categoriesCache.isEmpty()) {
command = Message.obtain();
command.what = Command.ITEM_GET_CATEGORIES;
dao.executeCommand(command);
}
});
}
private void loadItems() {
Call<List<Item>> call2 = api.getItems();
call2.enqueue(new Callback<List<Item>>() {
@Override
public void onResponse(Call<List<Item>> call, Response<List<Item>> response) {
int statusCode = response.code();
rawItems = response.body();
initData();
}
@Override
public void onFailure(Call<List<Item>> call, Throwable t) {
command = Message.obtain();
command.what = Command.SET_GET_ITEMS;
command.arg1 = presenter.getCurrentSet().getId();
dao.executeCommand(command);
}
});
}
}
private void syncData() {
// TODO sync data here
@Override
public void onEvent(Message event) {
switch (event.what) {
case Event.SET_ITEMS_LOAD_ERROR :
break;
case Event.ITEM_FROM_SET_ERROR :
break;
case Event.ITEM_DELETE_ERROR :
break;
case Event.ITEM_PACK_ERROR :
break;
case Event.ITEM_UNPACK_ERROR :
break;
case Event.ITEM_INSERT_ERROR :
break;
case Event.ITEM_CATEGORY_LOAD_ERROR :
break;
case Event.SET_ITEMS_LOAD_COMPLETED :
List<Item> res = (List<Item>) event.obj;
cache.put(event.arg1, res);
notifyDataSetChanged();
break;
case Event.ITEM_CATEGORY_LOAD_COMPLETED :
categoriesCache = (Hashtable<Integer, Category>)event.obj;
break;
case Event.ITEM_FROM_SET_DELETED :
break;
case Event.ITEM_DELETED :
break;
case Event.ITEM_PACKED :
break;
case Event.ITEM_UNPACKED :
break;
case Event.ITEM_INSERTED :
break;
}
}
// process <--
// other -->
@Override
public void setPresenter(Presenter.ItemList presenter) {
this.presenter = presenter;
this.currentSet = presenter.getCurrentSet().getId();
}
@Override
public Presenter.ItemList getPresenter() {
return presenter;
}
private void initData() {
List<Integer> ids = presenter.getCurrentSet().getItems();
List<Item> sortedItems = new ArrayList<>(ids.size());
@ -180,7 +304,6 @@ public class ItemModel implements Model.Item {
}
}
return category;
}
private Category findCategory(int categoryId) {
Category category = null;

View File

@ -1,10 +1,13 @@
package hikapro.com.backpack.model;
import android.os.Message;
import java.util.List;
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;
/**
* Created by tariel on 19/04/16.
@ -14,25 +17,46 @@ public interface Model {
interface Base {
void onDestroy(boolean isConfigurationChanging);
void executeQuery();
void sendMessage(String message);
void notifyDataSetChanged();
void onEvent(Message event);
}
interface Set extends Base {
hikapro.com.backpack.model.entities.Set getSetByPosition(int position);
hikapro.com.backpack.model.entities.Set findSet(int id);
int getSetsCount();
void notifyDataSetChanged();
void setPresenter(Presenter.SetList presenter);
Presenter.SetList getPresenter();
//GLM
List<hikapro.com.backpack.model.entities.Set> getSets(); // tag renamed
void setsReorderNotify();
}
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 notifyDataSetChanged();
void setPresenter(Presenter.ItemList presenter);
Presenter.ItemList getPresenter();
}
interface Detail extends Base {
int getCount();
hikapro.com.backpack.model.entities.Item findItem(int id);
void setPresenter(Presenter.ItemDetail presenter);
Presenter.ItemDetail getPresenter();
}
}

View File

@ -11,7 +11,7 @@ import retrofit2.converter.gson.GsonConverterFactory;
*/
public class RestClient {
public static final String BASE_URL = "http://hikapro.com";
public static final String BASE_URL = "http://hikapro.com/";
public static Api getApi() {

View File

@ -1,39 +1,43 @@
package hikapro.com.backpack.model;
import android.os.Message;
import java.util.ArrayList;
import java.util.List;
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;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
/**
* Created by tariel on 20/04/16.
*/
public class SetModel implements Model.Set {
private List<hikapro.com.backpack.model.entities.Set> iList;
private Api api;
private List<hikapro.com.backpack.model.entities.Set> cache;
private Presenter.SetList presenter;
private DAO dao;
public SetModel(Presenter.SetList presenter) {
this.api = RestClient.getApi();
this.iList = new ArrayList<>();
this.presenter = presenter;
public SetModel() {
this.cache = new ArrayList<>();
this.dao = DAO.getInstance();
dao.registerObserver(this);
}
// sets -->
//region sets
@Override
public hikapro.com.backpack.model.entities.Set getSetByPosition(int position) {
return iList.get(position);
return cache.get(position);
}
@Override
public hikapro.com.backpack.model.entities.Set findSet(int id) {
Set ret = null;
for (Set s : iList) {
for (Set s : cache) {
if (s.getId() == id) {
ret = s;
break;
@ -44,59 +48,95 @@ public class SetModel implements Model.Set {
@Override
public int getSetsCount() {
return iList.size();
return cache.size();
}
// sets <--
//endregion
// events -->
//region events
@Override
public void onDestroy(boolean isConfigurationChanging) {
if ( !isConfigurationChanging ) {
presenter = null;
}
}
@Override
public void notifyDataSetChanged() {
presenter.notifyDataSetChanged();
}
@Override
public void sendMessage(String message) {
private void sendMessage(String message) {
presenter.showMessage(message);
}
// events <--
//endregion
// process -->
//region process
@Override
public void executeQuery() {
Call<List<hikapro.com.backpack.model.entities.Set>> call = api.getSets();
call.enqueue(new Callback<List<hikapro.com.backpack.model.entities.Set>>() {
@Override
public void onResponse(Call<List<hikapro.com.backpack.model.entities.Set>> call, Response<List<hikapro.com.backpack.model.entities.Set>> response) {
int statusCode = response.code();
iList = response.body();
Message command = Message.obtain();
command.what = Command.SYNC_IF_NOT_EXISTS;
dao.executeCommand(command);
command = Message.obtain();
command.what = Command.SET_GET_ALL;
dao.executeCommand(command);
}
@Override
public void onEvent(Message event) {
switch (event.what) {
case Event.SET_LOAD_ERROR :
break;
case Event.SET_ITEMS_LOAD_ERROR :
break;
case Event.SET_REORDER_ERROR :
break;
case Event.SET_LOAD_COMPLETED :
cache = (List<Set>) event.obj;
notifyDataSetChanged();
}
@Override
public void onFailure(Call<List<hikapro.com.backpack.model.entities.Set>> call, Throwable t) {
}
});
break;
case Event.SET_ITEMS_LOAD_COMPLETED :
break;
case Event.SET_REORDER_COMPLETED :
break;
}
}
private void syncData() {
@Override
public void setsReorderNotify() {
for (int i = 0; i < cache.size(); ++i) {
cache.get(i).setLineNumber(i);
}
Message command = Message.obtain();
command.what = Command.SET_REORDER;
command.obj = cache;
dao.executeCommand(command);
}
// process <--
// other -->
// other <--
//endregion
//region other
@Override
public void setPresenter(Presenter.SetList presenter) {
this.presenter = presenter;
}
@Override
public Presenter.SetList getPresenter() {
return presenter;
}
//endregion
//GLM
@Override
public List<hikapro.com.backpack.model.entities.Set> getSets()
{
return cache;
}
}

View File

@ -0,0 +1,33 @@
package hikapro.com.backpack.model.dao;
/**
* Created by tariel on 27/04/16.
*/
public interface Command {
int SET_SCOPE_END = 0x77;
int ITEM_SCOPE_END = 0x8B;
int MY_LIST_SCOPE_END = 0x9F;
int SET_GET_ALL = 0x64;
int SET_REORDER = 0x65;
int SET_GET_ITEMS = 0x66;
int ITEM_DELETE_FROM_SET = 0x78;
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;
int MY_LIST_ITEM_DELETE = 0x8E;
int MY_LIST_CLEAR = 0x8F;
int SYNC = 0xA0;
int SYNC_IF_NOT_EXISTS = 0xA1;
int TEST = 0xC8;
}

View File

@ -0,0 +1,744 @@
package hikapro.com.backpack.model.dao;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteException;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.os.Process;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import hikapro.com.backpack.App;
import hikapro.com.backpack.model.Api;
import hikapro.com.backpack.model.Model;
import hikapro.com.backpack.model.RestClient;
import hikapro.com.backpack.model.SetModel;
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.model.entities.Timestamp;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
/**
* Created by tariel on 20/04/16.
*/
public class DAO {
//region Constants
private static final int CORE_POOL_SIZE = 1;
private static final int CORE_MAX_POOL_SIZE = 1;
private static final int KEEP_ALIVE_TIME = 1;
private static final TimeUnit KEEP_ALIVE_TIME_UNIT = TimeUnit.SECONDS;
private static final int MY_LIST_ID = 15;
//endregion
private static DAO instance;
private final ThreadPoolExecutor threadPool;
private final Handler handler;
private SetModel setModel;
private final Api api;
private Context context;
private DbHelper helper;
private Map<String, Model.Base> observers;
private DAO() {
this.context = App.getAppContext();
this.helper = new DbHelper(this.context);
this.api = RestClient.getApi();
final BlockingQueue<Runnable> taskQueue = new LinkedBlockingQueue<>();
this.threadPool = initPool(taskQueue);
this.observers = Collections.synchronizedMap(new HashMap<String, Model.Base>());
this.handler = initHandler();
}
static {
instance = new DAO();
}
public static DAO getInstance() {
return instance;
}
public void registerObserver(Model.Base o) {
observers.put(o.getClass().getName(), o);
}
private ThreadPoolExecutor initPool (BlockingQueue<Runnable> taskQueue) {
ThreadPoolExecutor ret = new ThreadPoolExecutor(
CORE_POOL_SIZE
,CORE_MAX_POOL_SIZE
,KEEP_ALIVE_TIME
,KEEP_ALIVE_TIME_UNIT
,taskQueue
);
return ret;
}
private Handler initHandler() {
Handler ret = new Handler(Looper.getMainLooper()) {
@Override
public void handleMessage(Message msg) {
for (Map.Entry<String, Model.Base> entry : observers.entrySet()) {
entry.getValue().onEvent(msg);
}
}
};
return ret;
}
public void executeCommand(Message command) {
SetTask setTask;
ItemTask itemTask;
if (command != null) {
switch (command.what) {
case Command.SET_GET_ALL :
setTask = new SetTask(Command.SET_GET_ALL,
Process.THREAD_PRIORITY_MORE_FAVORABLE);
threadPool.execute(setTask);
break;
case Command.SET_REORDER :
setTask = new SetTask(Command.SET_REORDER,
Process.THREAD_PRIORITY_BACKGROUND);
setTask.setsToUpdate = (List<Set>) command.obj;
threadPool.execute(setTask);
break;
case Command.SET_GET_ITEMS :
setTask = new SetTask(Command.SET_GET_ITEMS,
Process.THREAD_PRIORITY_MORE_FAVORABLE);
setTask.setId = command.arg1;
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);
itemTask.item = (Item) command.obj;
itemTask.setId = command.arg1;
threadPool.execute(itemTask);
break;
case Command.ITEM_DELETE_FROM_SET :
itemTask = new ItemTask(Command.ITEM_DELETE_FROM_SET,
Process.THREAD_PRIORITY_BACKGROUND);
itemTask.setId = command.arg1;
itemTask.itemId = command.arg2;
threadPool.execute(itemTask);
break;
case Command.ITEM_PACK :
itemTask = new ItemTask(Command.ITEM_PACK,
Process.THREAD_PRIORITY_BACKGROUND);
itemTask.setId = command.arg1;
itemTask.itemId = command.arg2;
threadPool.execute(itemTask);
break;
case Command.ITEM_UNPACK :
itemTask = new ItemTask(Command.ITEM_UNPACK,
Process.THREAD_PRIORITY_BACKGROUND);
itemTask.setId = command.arg1;
itemTask.itemId = command.arg2;
threadPool.execute(itemTask);
break;
case Command.MY_LIST_ITEM_ADD :
break;
case Command.MY_LIST_ITEM_DELETE :
break;
case Command.MY_LIST_POST :
break;
case Command.MY_LIST_CLEAR :
break;
case Command.SYNC :
threadPool.execute(new SyncTask(Command.SYNC,
Process.THREAD_PRIORITY_BACKGROUND));
break;
case Command.SYNC_IF_NOT_EXISTS :
threadPool.execute(new SyncTask(Command.SYNC_IF_NOT_EXISTS,
Process.THREAD_PRIORITY_MORE_FAVORABLE));
break;
}
}
}
/////////////////////// DATABASE /////////////////////
//region Database
private SQLiteDatabase getReadDB(){
return helper.getReadableDatabase();
}
private SQLiteDatabase getWriteDB(){
return helper.getWritableDatabase();
}
// inserts
private void insertTimestamp(Timestamp timestamp) {
if (timestamp != null && timestamp.timestamp > 0) {
ContentValues values;
SQLiteDatabase db = getWriteDB();
try {
db.beginTransaction();
values = Db.LogTable.toContentValues(timestamp);
db.insert(Db.LogTable.TABLE_NAME, null, values);
db.setTransactionSuccessful();
} finally {
db.endTransaction();
db.close();
}
}
}
private void insertItems(List<Item> items) {
if (items != null && !items.isEmpty()) {
ContentValues values;
SQLiteDatabase db = getWriteDB();
try {
db.beginTransaction();
for (Item item : items) {
values = Db.ItemsTable.toContentValues(item);
db.insert(Db.ItemsTable.TABLE_NAME, null, values);
}
db.setTransactionSuccessful();
} finally {
db.endTransaction();
db.close();
}
}
}
private void insertCategories(List<Category> categories) {
if (categories != null && !categories.isEmpty()) {
ContentValues values;
SQLiteDatabase db = getWriteDB();
try {
db.beginTransaction();
for (Category category : categories) {
values = Db.CategoriesTable.toContentValues(category);
db.insert(Db.CategoriesTable.TABLE_NAME, null, values);
}
db.setTransactionSuccessful();
} finally {
db.endTransaction();
db.close();
}
}
}
private void insertSets(List<Set> sets) {
if (sets != null && !sets.isEmpty()) {
ContentValues values;
int i = 0;
SQLiteDatabase db = getWriteDB();
try {
db.beginTransaction();
for (Set set : sets) {
values = Db.SetsTable.toContentValues(set, i);
db.insert(Db.SetsTable.TABLE_NAME, null, values);
insertSetItems(set, db);
++i;
}
db.setTransactionSuccessful();
} finally {
db.endTransaction();
db.close();
}
}
}
private void insertSetItems(Set set, SQLiteDatabase db) {
if (set != null && db != null) {
if (!set.getItems().isEmpty()) {
ContentValues values;
int setId = set.getId();
try {
db.beginTransaction();
for (Integer itemid : set.getItems()) {
values = Db.SetItemsTable.toContentValues(setId, itemid);
db.insert(Db.SetItemsTable.TABLE_NAME, null, values);
}
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
}
}
}
private void insertSetItem(int setId, int itemId) {
ContentValues values;
SQLiteDatabase db = getWriteDB();
try {
db.beginTransaction();
values = Db.SetItemsTable.toContentValues(setId, itemId);
db.insert(Db.SetItemsTable.TABLE_NAME, null, values);
db.setTransactionSuccessful();
} finally {
db.endTransaction();
db.close();
}
}
// reads
private boolean LogExist() {
boolean ret;
SQLiteDatabase db = getReadDB();
Cursor cursor = db.query(Db.LogTable.TABLE_NAME,
new String[] {Db.LogTable.COLUMN_ID}, null, null, null, null, null, "1");
ret = cursor.moveToNext();
cursor.close();
db.close();
return ret;
}
private List<Item> readItems(int setId) {
List<Item> ret = new ArrayList<>(256);
Cursor cursor = null;
SQLiteDatabase db = null;
Item item;
String query = String.format(
"SELECT * FROM %s a INNER JOIN %s b ON a.%s = b.%s WHERE b.%s = ? AND b.%s <> 1 AND b.%s <> 1",
Db.ItemsTable.TABLE_NAME,
Db.SetItemsTable.TABLE_NAME,
Db.ItemsTable.COLUMN_ID,
Db.SetItemsTable.COLUMN_ITEM,
Db.SetItemsTable.COLUMN_SET,
Db.SetItemsTable.COLUMN_DELETED,
Db.SetItemsTable.COLUMN_PACKED);
try {
db = getReadDB();
cursor = db.rawQuery(query, new String[]{String.valueOf(setId)});
while (cursor.moveToNext()) {
item = Db.ItemsTable.parseCursor(cursor);
ret.add(item);
}
} catch (SQLiteException e) {
//TODO write to log here
} catch (Exception e) {
//TODO write to log here
} finally {
if (cursor != null)
cursor.close();
if (db != null)
db.close();
}
return ret;
}
private Hashtable<Integer, Category> readCategories() {
Hashtable<Integer, Category> ret = new Hashtable<>(20, 0.9f);
Cursor cursor = null;
SQLiteDatabase db = null;
Category category;
try {
db = getReadDB();
cursor = db.query(Db.CategoriesTable.TABLE_NAME,
new String[]{Db.CategoriesTable.COLUMN_ID,
Db.CategoriesTable.COLUMN_NAME},
null,null,null,null,null);
while (cursor.moveToNext()) {
category = Db.CategoriesTable.parseCursor(cursor);
ret.put(category.getId(), category);
}
} catch (SQLiteException e) {
//TODO write to log here
} catch (Exception e) {
//TODO write to log here
} finally {
if (cursor != null)
cursor.close();
if (db != null)
db.close();
}
return ret;
}
private List<Set> readSets() {
List<Set> ret = new ArrayList<>(12);
Cursor cursor = null;
SQLiteDatabase db = null;
Set set;
try {
db = getReadDB();
cursor = db.query(Db.SetsTable.TABLE_NAME,
new String[]{Db.SetsTable.COLUMN_ID,
Db.SetsTable.COLUMN_ITEMS,
Db.SetsTable.COLUMN_LINE_NUMBER,
Db.SetsTable.COLUMN_NAME,
Db.SetsTable.COLUMN_PHOTO_LOCAL,
Db.SetsTable.COLUMN_PHOTO_THUMB_LOCAL,
Db.SetsTable.COLUMN_PHOTO_THUMB_URL,
Db.SetsTable.COLUMN_PHOTO_THUMBNAIL_LOCAL,
Db.SetsTable.COLUMN_PHOTO_THUMBNAIL_URL,
Db.SetsTable.COLUMN_PHOTO_URL},
null,null,null,null,
Db.SetsTable.COLUMN_LINE_NUMBER);
while (cursor.moveToNext()) {
set = Db.SetsTable.parseCursor(cursor);
ret.add(set);
}
Collections.sort(ret);
} catch (SQLiteException e) {
//TODO write to log here
} catch (Exception e) {
//TODO write to log here
} finally {
if (cursor != null)
cursor.close();
if (db != null)
db.close();
}
return ret;
}
// updates
private int updateSetsOrder(List<Set> reorderedSet) {
int ret = 0;
SQLiteDatabase db = null;
ContentValues values;
try {
db = getWriteDB();
db.beginTransaction();
for (Set set : reorderedSet) {
values = new ContentValues();
values.put(Db.SetsTable.COLUMN_LINE_NUMBER, set.getLineNumber());
ret += db.update(Db.SetsTable.TABLE_NAME, values, "_id = ?",
new String[]{String.valueOf(set.getId())});
}
db.setTransactionSuccessful();
} catch (SQLiteException e) {
//TODO write to log here
} catch (Exception e) {
//TODO write to log here
} finally {
if (db != null) {
db.endTransaction();
db.close();
}
}
return ret;
}
private int updateSetItemDeleted(int setId, int itemId, boolean del) {
int ret = 0;
SQLiteDatabase db = null;
ContentValues values;
try {
db = getWriteDB();
db.beginTransaction();
values = new ContentValues();
values.put(Db.SetItemsTable.COLUMN_DELETED, del);
ret = db.update(Db.SetItemsTable.TABLE_NAME, values, String.format("%s = ? AND %s = ?",
Db.SetItemsTable.COLUMN_SET, Db.SetItemsTable.COLUMN_ITEM ),
new String[]{String.valueOf(setId), String.valueOf(itemId)});
db.setTransactionSuccessful();
} catch (SQLiteException e) {
//TODO write to log here
} catch (Exception e) {
//TODO write to log here
} finally {
if (db != null) {
db.endTransaction();
db.close();
}
}
return ret;
}
private int updateSetItemPacked(int setId, int itemId, boolean pack) {
int ret = 0;
SQLiteDatabase db = null;
ContentValues values;
try {
db = getWriteDB();
db.beginTransaction();
values = new ContentValues();
values.put(Db.SetItemsTable.COLUMN_PACKED, pack);
ret = db.update(Db.SetItemsTable.TABLE_NAME, values, String.format("%s = ? AND %s = ?",
Db.SetItemsTable.COLUMN_SET, Db.SetItemsTable.COLUMN_ITEM ),
new String[]{String.valueOf(setId), String.valueOf(itemId)});
if (pack) {
values = Db.SetItemsTable.toContentValues(MY_LIST_ID, itemId);
db.insert(Db.SetItemsTable.TABLE_NAME, null, values);
} else {
db.delete(Db.SetItemsTable.TABLE_NAME, String.format("%s = ? AND %s = ?",
Db.SetItemsTable.COLUMN_SET, Db.SetItemsTable.COLUMN_ITEM ),
new String[]{String.valueOf(MY_LIST_ID), String.valueOf(itemId) });
}
db.setTransactionSuccessful();
} catch (SQLiteException e) {
//TODO write to log here
} catch (Exception e) {
//TODO write to log here
} finally {
if (db != null) {
db.endTransaction();
db.close();
}
}
return ret;
}
//endregion
/////////////////////// TASK CLASSES //////////////////
//region Task classes
// MY LIST CLASS
private class MyListTask implements Runnable {
int currentCommand;
int priority;
public MyListTask(int command, int priority) {
this.currentCommand = command;
this.priority = priority;
}
@Override
public void run() {
android.os.Process.setThreadPriority(priority);
Message message = Message.obtain();
switch (currentCommand) {
}
handler.sendMessage(message);
}
}
// ITEM CLASS
private class ItemTask implements Runnable {
int currentCommand;
int priority;
int setId;
int itemId;
Item item;
public ItemTask(int command, int priority) {
this.currentCommand = command;
this.setId = -1;
this.itemId = -1;
this.priority = priority;
}
@Override
public void run() {
android.os.Process.setThreadPriority(priority);
Message message = Message.obtain();
switch (currentCommand) {
case Command.ITEM_DELETE_FROM_SET :
message.arg1 = updateSetItemDeleted(setId, itemId, true);
if (message.arg1 > 0)
message.what = Event.ITEM_FROM_SET_DELETED;
else
message.what = Event.ITEM_FROM_SET_ERROR;
break;
case Command.ITEM_INSERT :
List<Item> items = new ArrayList<>();
items.add(item);
if (items.isEmpty())
message.what = Event.ITEM_INSERT_ERROR;
else {
insertItems(items);
insertSetItem(setId, item.getId());
message.what = Event.ITEM_INSERTED;
message.arg1 = setId;
message.arg2 = item.getId();
}
break;
case Command.ITEM_PACK :
message.arg1 = updateSetItemPacked(setId, itemId, true);
if (message.arg1 > 0)
message.what = Event.ITEM_PACKED;
else
message.what = Event.ITEM_PACK_ERROR;
break;
case Command.ITEM_UNPACK :
message.arg1 = updateSetItemPacked(setId, itemId, false);
if (message.arg1 > 0)
message.what = Event.ITEM_UNPACKED;
else
message.what = Event.ITEM_UNPACK_ERROR;
break;
case Command.ITEM_GET_CATEGORIES :
Hashtable<Integer, Category> 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);
}
}
// SET CLASS
private class SetTask implements Runnable {
int currentCommand;
int priority;
int setId;
List<Set> setsToUpdate;
public SetTask(int command, int priority) {
this.currentCommand = command;
this.setId = -1;
this.setsToUpdate = new ArrayList<>();
this.priority = priority;
}
@Override
public void run() {
android.os.Process.setThreadPriority(priority);
Message message = Message.obtain();
switch (currentCommand) {
case Command.SET_GET_ALL :
List<Set> sets = readSets();
if (sets.isEmpty())
message.what = Event.SET_LOAD_ERROR;
else {
message.what = Event.SET_LOAD_COMPLETED;
message.obj = sets;
}
break;
case Command.SET_GET_ITEMS :
List<Item> items = readItems(setId);
if (items.isEmpty())
message.what = Event.SET_ITEMS_LOAD_ERROR;
else {
Collections.sort(items);
message.what = Event.SET_ITEMS_LOAD_COMPLETED;
message.obj = items;
message.arg1 = setId;
}
break;
case Command.SET_REORDER :
message.arg1 = updateSetsOrder(setsToUpdate);
if (message.arg1 > 0)
message.what = Event.SET_REORDER_COMPLETED;
else
message.what = Event.SET_REORDER_ERROR;
break;
}
handler.sendMessage(message);
}
}
// SYNC CLASS
private class SyncTask implements Runnable {
int currentCommand;
int priority;
int statusCode;
public SyncTask(int command, int priority) {
this.currentCommand = command;
this.priority = priority;
}
@Override
public void run() {
android.os.Process.setThreadPriority(priority);
Message message = Message.obtain();
switch (currentCommand) {
case Command.SYNC:
try {
Call<List<Set>> call = api.getSets();
call.enqueue(new Callback<List<Set>>() {
@Override
public void onResponse(Call<List<hikapro.com.backpack.model.entities.Set>> call, Response<List<Set>> response) {
statusCode = response.code();
// TODO
// check if first time
// if not check for updates else
// insert into database here
insertSets(response.body());
}
@Override
public void onFailure(Call<List<hikapro.com.backpack.model.entities.Set>> call, Throwable t) {
}
});
message.what = Event.SYNC_COMPLETED;
} catch (Exception e) {
message.what = Event.SYNC_FAILED;
}
finally {
message.arg1 = statusCode;
handler.sendMessage(message);
}
break;
case Command.SYNC_IF_NOT_EXISTS:
if (LogExist()) {
message.what = Event.SYNC_COMPLETED;
} else {
try {
Response<List<Set>> response0 = api.getSets().execute();
insertSets(response0.body());
statusCode = response0.code();
Response<List<Category>> response1 = api.getItemCategories().execute();
insertCategories(response1.body());
statusCode = response1.code();
Response<List<Item>> response2 = api.getItems().execute();
insertItems(response2.body());
statusCode = response2.code();
Response<Timestamp> response3 = api.getTimestamp().execute();
insertTimestamp(response3.body());
statusCode = response3.code();
message.what = Event.SYNC_COMPLETED;
} catch (IOException e ){
message.what = Event.SYNC_FAILED;
} finally {
message.arg1 = statusCode;
handler.sendMessage(message);
}
}
break;
}
}
}
//endregion
}

View File

@ -1,4 +1,4 @@
package hikapro.com.backpack.model.database;
package hikapro.com.backpack.model.dao;
import android.content.ContentValues;
import android.database.Cursor;
@ -12,6 +12,8 @@ import java.util.List;
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.model.entities.Timestamp;
import hikapro.com.backpack.model.entities.UpdateLog;
/**
* Created by tariel on 20/04/16.
@ -23,10 +25,10 @@ public class Db {
public abstract static class CategoriesTable {
public static final String TABLE_NAME = "CATEGORIES";
public static final String TABLE_NAME = "categories";
public static final String COLUMN_ID = "_id";
public static final String COLUMN_NAME = "NAME";
public static final String COLUMN_NAME = "name";
public static final String CREATE =
"CREATE TABLE " + TABLE_NAME + " (" +
@ -51,17 +53,17 @@ public class Db {
}
public abstract static class ItemsTable {
public static final String TABLE_NAME = "ITEMS";
public static final String TABLE_NAME = "items";
public static final String COLUMN_ID = "_id";
public static final String COLUMN_NAME = "NAME";
public static final String COLUMN_CATEGORY = "CATEGORY";
public static final String COLUMN_DESCRIPTION = "DESCRIPTION";
public static final String COLUMN_BUY_URLS = "BUY_URLS";
public static final String COLUMN_PHOTO_URL = "PHOTO_URL";
public static final String COLUMN_PHOTO_THUMB_URL = "PHOTO_THUMB_URL";
public static final String COLUMN_PHOTO_LOCAL = "PHOTO_LOCAL";
public static final String COLUMN_PHOTO_THUMB_LOCAL = "PHOTO_THUMB_LOCAL";
public static final String COLUMN_NAME = "name";
public static final String COLUMN_CATEGORY = "category";
public static final String COLUMN_DESCRIPTION = "description";
public static final String COLUMN_BUY_URLS = "buy_urls";
public static final String COLUMN_PHOTO_URL = "photo_url";
public static final String COLUMN_PHOTO_THUMB_URL = "photo_thumb_url";
public static final String COLUMN_PHOTO_LOCAL = "photo_local";
public static final String COLUMN_PHOTO_THUMB_LOCAL = "photo_thumb_local";
public static final String CREATE =
@ -124,17 +126,19 @@ public class Db {
}
public abstract static class SetsTable {
public static final String TABLE_NAME = "SETS";
public static final String TABLE_NAME = "sets";
public static final String COLUMN_ID = "_id";
public static final String COLUMN_NAME = "NAME";
public static final String COLUMN_ITEMS = "ITEMS";
public static final String COLUMN_PHOTO_URL = "PHOTO_URL";
public static final String COLUMN_PHOTO_THUMB_URL = "PHOTO_THUMB_URL";
public static final String COLUMN_PHOTO_LOCAL = "PHOTO_LOCAL";
public static final String COLUMN_PHOTO_THUMB_LOCAL = "PHOTO_THUMB_LOCAL";
public static final String COLUMN_PHOTO_THUMBNAIL_URL = "PHOTO_THUMBNAIL_URL";
public static final String COLUMN_PHOTO_THUMBNAIL_LOCAL = "PHOTO_THUMBNAIL_LOCAL";
public static final String COLUMN_NAME = "name";
public static final String COLUMN_ITEMS = "items";
public static final String COLUMN_PHOTO_URL = "photo_url";
public static final String COLUMN_PHOTO_THUMB_URL = "photo_thumb_url";
public static final String COLUMN_PHOTO_LOCAL = "photo_local";
public static final String COLUMN_PHOTO_THUMB_LOCAL = "photo_thumb_local";
public static final String COLUMN_PHOTO_THUMBNAIL_URL = "photo_thumbnail_url";
public static final String COLUMN_PHOTO_THUMBNAIL_LOCAL = "photo_thumbnail_local";
public static final String COLUMN_LINE_NUMBER = "line_num";
public static final String CREATE =
"CREATE TABLE " + TABLE_NAME + " (" +
@ -146,10 +150,11 @@ public class Db {
COLUMN_PHOTO_LOCAL + " TEXT, " +
COLUMN_PHOTO_THUMB_LOCAL + " TEXT, " +
COLUMN_PHOTO_THUMBNAIL_URL + " TEXT, " +
COLUMN_LINE_NUMBER + " INTEGER, " +
COLUMN_PHOTO_THUMBNAIL_LOCAL + " TEXT" +
" ); ";
public static ContentValues toContentValues(Set set) {
public static ContentValues toContentValues(Set set, int lineNumber) {
ContentValues values = new ContentValues();
values.put(COLUMN_ID, set.getId());
values.put(COLUMN_NAME, set.getName());
@ -164,6 +169,7 @@ public class Db {
values.put(COLUMN_PHOTO_URL, set.getPhoto());
values.put(COLUMN_PHOTO_THUMB_URL, set.getPhotoThumb());
values.put(COLUMN_PHOTO_THUMBNAIL_URL, set.getPhotoThumbnail());
values.put(COLUMN_LINE_NUMBER, lineNumber);
/*
values.put(COLUMN_PHOTO_LOCAL, "");
values.put(COLUMN_PHOTO_THUMB_LOCAL, "");
@ -187,9 +193,68 @@ public class Db {
set.setPhoto(cursor.getString(cursor.getColumnIndexOrThrow(COLUMN_PHOTO_URL)));
set.setPhotoThumb(cursor.getString(cursor.getColumnIndexOrThrow(COLUMN_PHOTO_THUMB_URL)));
set.setPhotoThumbnail(cursor.getString(cursor.getColumnIndexOrThrow(COLUMN_PHOTO_THUMBNAIL_URL)));
set.setLineNumber(cursor.getInt(cursor.getColumnIndexOrThrow(COLUMN_LINE_NUMBER)));
return set;
}
}
public abstract static class LogTable {
public static final String TABLE_NAME = "update_log";
public static final String COLUMN_ID = "_id";
public static final String COLUMN_MODIFIED_DATETIME = "modified_datetime";
public static final String COLUMN_TIMESTAMP = "timestamp";
public static final String CREATE =
"CREATE TABLE " + TABLE_NAME + " (" +
COLUMN_ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " +
COLUMN_TIMESTAMP + " INTEGER NOT NULL, " +
COLUMN_MODIFIED_DATETIME + " INTEGER NOT NULL DEFAULT current_timestamp" +
" ); ";
public static ContentValues toContentValues(Timestamp timestamp) {
ContentValues values = new ContentValues();
values.put(COLUMN_TIMESTAMP, timestamp.timestamp);
return values;
}
public static UpdateLog parseCursor(Cursor cursor) {
UpdateLog log = new UpdateLog();
log.setTimestamp(cursor.getInt(cursor.getColumnIndexOrThrow(COLUMN_TIMESTAMP)));
log.setModifiedDatetime(cursor.getInt(cursor.getColumnIndexOrThrow(COLUMN_MODIFIED_DATETIME)));
return log;
}
}
public abstract static class SetItemsTable {
public static final String TABLE_NAME = "set_items";
public static final String COLUMN_ID = "_id";
public static final String COLUMN_SET = "setId";
public static final String COLUMN_ITEM = "itemId";
public static final String COLUMN_DELETED = "deleted";
public static final String COLUMN_PACKED = "packed";
public static final String CREATE =
"CREATE TABLE " + TABLE_NAME + " (" +
COLUMN_ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " +
COLUMN_SET + " INTEGER NOT NULL, " +
COLUMN_DELETED + " NUMERIC, " +
COLUMN_PACKED + " NUMERIC, " +
COLUMN_ITEM + " INTEGER NOT NULL" +
" ); ";
public static ContentValues toContentValues(int setId, int itemId) {
ContentValues values = new ContentValues();
values.put(COLUMN_SET, setId);
values.put(COLUMN_ITEM, itemId);
values.put(COLUMN_DELETED, 0);
values.put(COLUMN_PACKED, 0);
return values;
}
}
}

View File

@ -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;
@ -38,6 +38,8 @@ public class DbHelper extends SQLiteOpenHelper {
db.execSQL(Db.ItemsTable.CREATE);
db.execSQL(Db.CategoriesTable.CREATE);
db.execSQL(Db.SetsTable.CREATE);
db.execSQL(Db.LogTable.CREATE);
db.execSQL(Db.SetItemsTable.CREATE);
if (oldVersion < 2) {
// place the logic here

View File

@ -0,0 +1,53 @@
package hikapro.com.backpack.model.dao;
/**
* Created by tariel on 27/04/16.
*/
public interface Event {
int SET_SCOPE_END = 0x13;
int ITEM_SCOPE_END = 0x27;
int MY_LIST_SCOPE_END = 0x3B;
int SET_LOAD_ERROR = -0x1;
int SET_REORDER_ERROR = -0x2;
int SET_ITEMS_LOAD_ERROR = -0x3;
int SET_LOAD_COMPLETED = 0x1;
int SET_REORDER_COMPLETED = 0x2;
int SET_ITEMS_LOAD_COMPLETED = 0x3;
int ITEM_FROM_SET_ERROR = -0x14;
int ITEM_INSERT_ERROR = -0x15;
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;
int MY_LIST_ITEM_DELETE_ERROR = -0x2A;
int MY_LIST_CLEAR_ERROR = -0x2B;
int MY_LIST_POSTED = 0x28;
int MY_LIST_ITEM_ADDED = 0x29;
int MY_LIST_ITEM_DELETED = 0x2A;
int MY_LIST_CLEARED = 0x2B;
int SYNC_FAILED = -0x3C;
int SYNC_COMPLETED = 0x3C;
int NOT_IMPLEMENTED = 0x50;
int NOT_UNDERSTAND = 0x51;
}

View File

@ -0,0 +1,15 @@
package hikapro.com.backpack.model.dao;
/**
* Created by tariel on 27/04/16.
*/
public class Test {
private static Test ourInstance = new Test();
public static Test getInstance() {
return ourInstance;
}
private Test() {
}
}

View File

@ -1,26 +0,0 @@
package hikapro.com.backpack.model.database;
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
/**
* Created by tariel on 20/04/16.
*/
public class DAO {
private Context context;
private DbHelper helper;
public DAO(Context context) {
this.context = context;
this.helper = new DbHelper(this.context);
}
private SQLiteDatabase getReadDB(){
return helper.getReadableDatabase();
}
private SQLiteDatabase getWriteDB(){
return helper.getWritableDatabase();
}
}

View File

@ -3,13 +3,14 @@ package hikapro.com.backpack.model.entities;
import com.google.gson.annotations.Expose;
import com.google.gson.annotations.SerializedName;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
/**
* Created by tariel on 02/04/16.
*/
public class Item implements Comparable<Item> {
public class Item implements Comparable<Item>, Serializable {
@SerializedName("id")
@Expose

View File

@ -10,7 +10,7 @@ import java.util.List;
/**
* Created by tariel on 02/04/16.
*/
public class Set implements Serializable {
public class Set implements Comparable<Set>, Serializable {
@SerializedName("id")
@Expose
@ -31,6 +31,8 @@ public class Set implements Serializable {
@Expose
private String photoThumbnail;
private int lineNumber;
public Set() {
}
@ -91,6 +93,14 @@ public class Set implements Serializable {
this.photoThumbnail = photoThumbnail;
}
public int getLineNumber() {
return lineNumber;
}
public void setLineNumber(int lineNumber) {
this.lineNumber = lineNumber;
}
@Override
public int hashCode() {
int result = name != null ? name.hashCode() : 0;
@ -109,4 +119,10 @@ public class Set implements Serializable {
return false;
return true;
}
@Override
public int compareTo(Set another) {
int cmp = Integer.valueOf(lineNumber).compareTo(Integer.valueOf(another.lineNumber));
return (cmp != 0 ? cmp : name.compareTo(another.name));
}
}

View File

@ -0,0 +1,20 @@
package hikapro.com.backpack.model.entities;
import com.google.gson.annotations.Expose;
import com.google.gson.annotations.SerializedName;
/**
* Created by tariel on 27/04/16.
*/
public class Timestamp {
@SerializedName("timestamp")
@Expose
public long timestamp;
public Timestamp() {
}
}

View File

@ -0,0 +1,29 @@
package hikapro.com.backpack.model.entities;
/**
* Created by tariel on 27/04/16.
*/
public class UpdateLog {
private long timestamp;
private long modifiedDatetime;
public UpdateLog() {
}
public long getTimestamp() {
return timestamp;
}
public void setTimestamp(long timestamp) {
this.timestamp = timestamp;
}
public long getModifiedDatetime() {
return modifiedDatetime;
}
public void setModifiedDatetime(long modifiedDatetime) {
this.modifiedDatetime = modifiedDatetime;
}
}

View File

@ -0,0 +1,41 @@
package hikapro.com.backpack.model.entities;
import com.google.gson.annotations.Expose;
import com.google.gson.annotations.SerializedName;
import java.util.ArrayList;
import java.util.List;
/**
* Created by tariel on 27/04/16.
*/
public class Updates {
@SerializedName("items")
@Expose
public List<Integer> items = new ArrayList<>();
@SerializedName("items_deleted_ids")
@Expose
public List<Integer> itemsDeletedIds = new ArrayList<>();
@SerializedName("item_categories")
@Expose
public List<Integer> itemCategories = new ArrayList<>();
@SerializedName("item_categories_deleted_ids")
@Expose
public List<Integer> itemCategoriesDeletedIds = new ArrayList<>();
@SerializedName("sets")
@Expose
public List<Integer> sets = new ArrayList<>();
@SerializedName("sets_deleted_ids")
@Expose
public List<Integer> setsDeletedIds = new ArrayList<>();
@SerializedName("timestamp")
@Expose
public long timestamp;
}

View File

@ -0,0 +1,147 @@
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.view.LayoutInflater;
import android.view.ViewGroup;
import android.widget.Toast;
import java.lang.ref.WeakReference;
import hikapro.com.backpack.R;
import hikapro.com.backpack.model.Model;
import hikapro.com.backpack.model.entities.Item;
import hikapro.com.backpack.presenter.adapters.ItemDetailAdapter;
import hikapro.com.backpack.view.View;
import hikapro.com.backpack.view.recycler.DetailViewHolder;
/**
* Created by tariel on 23/04/16.
*/
public class ItemDetailPresenter implements Presenter.ItemDetail {
private WeakReference<View.ItemDetail> view;
private Model.Detail model;
private ItemDetailAdapter adapter;
private Item item;
public ItemDetailPresenter() {
this.adapter = new ItemDetailAdapter(this);
}
// life cycle -->
@Override
public void onDestroy(boolean isChangingConfiguration) {
view = null;
model.onDestroy(isChangingConfiguration);
if ( !isChangingConfiguration ) {
model = null;
}
}
@Override
public android.view.View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
android.view.View view = inflater.inflate(R.layout.fragment_item_detail, container, false);
LinearLayoutManager llm = new LinearLayoutManager(getActivityContext());
RecyclerView detailRecycler = (RecyclerView) view.findViewById(R.id.item_detail_recycler);
detailRecycler.setLayoutManager(llm);
detailRecycler.setAdapter(adapter);
detailRecycler.setItemAnimator(new DefaultItemAnimator());
model.executeQuery();
return view;
}
@Override
public void onSaveInstanceState(Bundle outState) {
}
// life cycle <--
// recycler -->
@Override
public int getItemsCount() {
return model.getCount();
}
@Override
public void bindViewHolder(DetailViewHolder holder, int position) {
hikapro.com.backpack.model.entities.Item item = model.findItem(position);
holder.title.setText(item.getName());
holder.description.setText(item.getDescription());
holder.title.setOnClickListener(new android.view.View.OnClickListener() {
@Override
public void onClick(android.view.View view) {
showMessage("On detail click");
}
});
}
@Override
public DetailViewHolder createViewHolder(ViewGroup parent, int viewType) {
DetailViewHolder viewHolder;
android.view.View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_detail,
parent, false);
viewHolder = new DetailViewHolder(v);
return viewHolder;
}
// recycler <--
// process -->
@Override
public void notifyDataSetChanged() {
adapter.notifyDataSetChanged();
}
// process <--
// other impl -->
@Override
public void setView(View.ItemDetail view) {
this.view = new WeakReference<>(view);
this.item = getView().getItem();
}
@Override
public void setModel(Model.Detail model) {
this.model = model;
}
@Override
public Item getCurrentItem() {
return item;
}
@Override
public Context getAppContext() {
try {
return getView().getAppContext();
} catch (NullPointerException e) {
return null;
}
}
@Override
public Context getActivityContext() {
try {
return getView().getActivityContext();
} catch (NullPointerException e) {
return null;
}
}
@Override
public void showMessage(String message) {
Toast.makeText(getView().getAppContext(), message, Toast.LENGTH_SHORT).show();
}
// other impl <--
private View.ItemDetail getView() throws NullPointerException {
if ( view != null )
return view.get();
else
throw new NullPointerException("View is unavailable");
}
}

View File

@ -2,26 +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.
@ -33,13 +31,12 @@ public class ItemListPresenter implements Presenter.ItemList {
private WeakReference<View.ItemList> view;
private Model.Item model;
private Set set;
private CategoryListAdapter categoryListAdapter;
private ItemListAdapter adapter;
RecyclerView recycler;
public ItemListPresenter(View.ItemList view, Set set) {
this.view = new WeakReference<>(view);
this.set = set;
this.categoryListAdapter = new CategoryListAdapter(this);
setModel(new ItemModel(this));
public ItemListPresenter() {
this.adapter = new ItemListAdapter(this);
adapter.setHasStableIds(true);
}
// life cycle -->
@ -54,14 +51,30 @@ public class ItemListPresenter implements Presenter.ItemList {
}
@Override
public android.view.View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
/*
if (savedInstanceState != null)
set = (Set) savedInstanceState.getSerializable(BUNDLE_SET_LIST_KEY);
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;
}
@ -73,65 +86,21 @@ 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) {
showMessage("Position " + position + " Id " + holder.id);
}
});
}
@Override
public int getItemsCount(int categoryId) {
return model.getItemsCount(categoryId);//TODO category Id
}
// recycler <--
// process -->
@Override
public void notifyDataSetChanged() {
categoryListAdapter.notifyDataSetChanged();
categoryListAdapter.notifyItemAdapters();
adapter.notifyDataSetChanged();
}
@Override
public void clickItem(int itemId) {
Item item = model.findItem(itemId);
if (item != null)
getView().showItemDetail(item);
else
showMessage(String.format("Item with Id %d is not found.", itemId));
}
// process <--
// other impl -->
@ -139,7 +108,18 @@ public class ItemListPresenter implements Presenter.ItemList {
@Override
public void setView(View.ItemList view) {
this.view = new WeakReference<>(view);
this.set = getView().getSet();
}
@Override
public void setModel(Model.Item model) {
this.model = model;
}
@Override
public Model.Item getModel() {
return model;
}
@Override
public Context getAppContext() {
try {
@ -157,10 +137,6 @@ public class ItemListPresenter implements Presenter.ItemList {
}
}
@Override
public void setModel(Model.Item model) {
this.model = model;
}
@Override
public void showMessage(String message) {
Toast.makeText(getView().getAppContext(), message, Toast.LENGTH_SHORT).show();
}
@ -175,8 +151,9 @@ public class ItemListPresenter implements Presenter.ItemList {
}
@Override
public int getCategoriesCount() {
return model.getCategoriesCount();
public void filter(String query) {
adapter.filter(query);
recycler.scrollToPosition(0);
}
@Override

View File

@ -2,12 +2,14 @@ package hikapro.com.backpack.presenter;
import android.content.Context;
import android.os.Bundle;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
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;
@ -32,23 +34,42 @@ public interface Presenter {
void setModel(Model.Set model);
void notifyDataSetChanged();
void showMessage(String message);
//GLM_add_resources_SetList
void onItemDismiss(int position);
boolean onItemMove(int fromPosition, int toPosition);
void onStartDrag(RecyclerView.ViewHolder viewHolder);
}
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 {
void setView(hikapro.com.backpack.view.View.ItemDetail view);
void setModel(Model.Detail model);
void notifyDataSetChanged();
void showMessage(String message);
int getItemsCount();
void bindViewHolder(DetailViewHolder holder, int position);
DetailViewHolder createViewHolder(ViewGroup parent, int viewType);
void onDestroy(boolean isChangingConfiguration);
android.view.View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState);
void onSaveInstanceState(Bundle outState);
Item getCurrentItem();
}

View File

@ -5,17 +5,20 @@ 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.Collections;
import hikapro.com.backpack.R;
import hikapro.com.backpack.model.SetModel;
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.sets.OnStartDragListener;
import hikapro.com.backpack.presenter.adapters.helper.sets.SimpleItemTouchHelperCallback;
import hikapro.com.backpack.view.View;
import hikapro.com.backpack.view.recycler.SetViewHolder;
@ -28,13 +31,34 @@ public class SetListPresenter implements Presenter.SetList {
private Model.Set model;
private SetListAdapter adapter;
//GLM_add_resources_SetList
private OnStartDragListener mDragStartListener;
private ItemTouchHelper mItemTouchHelper;
public SetListPresenter() {
this.adapter = new SetListAdapter(this);
setModel(new SetModel(this));
}
// life cycle -->
//GLM_add_resources_SetList
@Override
public void onItemDismiss(int position) {
model.getSets().remove(position);
adapter.notifyItemRemoved(position);
}
//GLM_add_resources_SetList
@Override
public boolean onItemMove(int fromPosition, int toPosition) {
Collections.swap(model.getSets(), fromPosition, toPosition);
adapter.notifyItemMoved(fromPosition, toPosition);
model.setsReorderNotify();
return true;
}
@Override
public void onDestroy(boolean isChangingConfiguration) {
view = null;
@ -53,6 +77,12 @@ public class SetListPresenter implements Presenter.SetList {
setRecycler.setAdapter(adapter);
setRecycler.setItemAnimator(new DefaultItemAnimator());
model.executeQuery();
//GLM_add_resources_SetList
ItemTouchHelper.Callback callback = new SimpleItemTouchHelperCallback(adapter);
mItemTouchHelper = new ItemTouchHelper(callback);
mItemTouchHelper.attachToRecyclerView(setRecycler);
return view;
}
@ -82,6 +112,17 @@ public class SetListPresenter implements Presenter.SetList {
showMessage("There is no view in presenter");
}
});
//GLM_add_resources_SetList
holder.cardView.setOnLongClickListener(new android.view.View.OnLongClickListener() {
@Override
public boolean onLongClick(android.view.View v) {
mDragStartListener.onStartDrag(holder);
return false;
}
});
}
@Override
public int getSetsCount() {
@ -129,6 +170,7 @@ public class SetListPresenter implements Presenter.SetList {
@Override
public void setView(View.SetList view) {
this.view = new WeakReference<>(view);
this.mDragStartListener = getView().getOnStartDragListener();
}
@Override
public void setModel(Model.Set model) {
@ -148,5 +190,9 @@ public class SetListPresenter implements Presenter.SetList {
throw new NullPointerException("View is unavailable");
}
//GLM_add_resources_SetList
@Override
public void onStartDrag(RecyclerView.ViewHolder viewHolder) {
mItemTouchHelper.startDrag(viewHolder);
}
}

View File

@ -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<CategoryViewHolder> {
private Presenter.ItemList presenter;
private List<ItemListAdapter> 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();
}
}
}

View File

@ -0,0 +1,40 @@
package hikapro.com.backpack.presenter.adapters;
import android.support.v7.widget.RecyclerView;
import android.view.ViewGroup;
import hikapro.com.backpack.presenter.Presenter;
import hikapro.com.backpack.view.recycler.DetailViewHolder;
/**
* Created by tariel on 23/04/16.
*/
public class ItemDetailAdapter extends RecyclerView.Adapter<DetailViewHolder> {
private Presenter.ItemDetail presenter;
private int itemId;
public ItemDetailAdapter(Presenter.ItemDetail presenter) {
this.presenter = presenter;
}
@Override
public int getItemCount() {
return presenter.getItemsCount();
}
@Override
public void onBindViewHolder(DetailViewHolder holder, int position) {
presenter.bindViewHolder(holder, position);
}
@Override
public DetailViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
return presenter.createViewHolder(parent, viewType);
}
public void setItemId(int id) {
this.itemId = id;
}
}

View File

@ -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<ItemViewHolder> {
public class ItemListAdapter extends RecyclerView.Adapter<ItemViewHolder> implements StickyHeaderAdapter<HeaderViewHolder> {
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<Item, Runnable> 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,151 @@ public class ItemListAdapter extends RecyclerView.Adapter<ItemViewHolder> {
@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(final 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.title.setOnClickListener(new android.view.View.OnClickListener() {
@Override
public void onClick(View v) {
presenter.clickItem(holder.id);
}
});
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);
}
}

View File

@ -4,12 +4,13 @@ import android.support.v7.widget.RecyclerView;
import android.view.ViewGroup;
import hikapro.com.backpack.presenter.SetListPresenter;
import hikapro.com.backpack.presenter.adapters.helper.sets.ItemTouchHelperAdapter;
import hikapro.com.backpack.view.recycler.SetViewHolder;
/**
* Created by tariel on 20/04/16.
*/
public class SetListAdapter extends RecyclerView.Adapter<SetViewHolder> {
public class SetListAdapter extends RecyclerView.Adapter<SetViewHolder> implements ItemTouchHelperAdapter{
private SetListPresenter presenter;
@ -31,4 +32,16 @@ public class SetListAdapter extends RecyclerView.Adapter<SetViewHolder> {
public int getItemCount() {
return presenter.getSetsCount();
}
//GLM_add_resources_SetList
@Override
public void onItemDismiss(int position) {
presenter.onItemDismiss(position);
}
//GLM_add_resources_SetList
@Override
public boolean onItemMove(int fromPosition, int toPosition) {
return presenter.onItemMove(fromPosition, toPosition);
}
}

View File

@ -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
);
}
}

View File

@ -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);
}
}
}

View File

@ -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;
}
}
}

View File

@ -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();
}

View File

@ -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());
}
}

View File

@ -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<View> 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();
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}

View File

@ -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<VH extends RecyclerView.ViewHolder> {
long getHeaderId(int position);
VH onCreateHeaderViewHolder(ViewGroup parent);
void onBindHeaderViewHolder(VH holder, int position);
int getItemCount();
}

View File

@ -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<Rect> 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();
}
}

View File

@ -0,0 +1,12 @@
package hikapro.com.backpack.presenter.adapters.helper.sets;
/**
* Created by N551 on 25.04.2016.
*/
//GLM_add_resources_SetList
public interface ItemTouchHelperAdapter {
boolean onItemMove(int fromPosition, int toPosition);
void onItemDismiss(int position);
}

View File

@ -0,0 +1,12 @@
package hikapro.com.backpack.presenter.adapters.helper.sets;
/**
* Created by N551 on 25.04.2016.
*/
//GLM_add_resources_SetList
public interface ItemTouchHelperViewHolder {
void onItemSelected();
void onItemClear();
}

View File

@ -0,0 +1,12 @@
package hikapro.com.backpack.presenter.adapters.helper.sets;
import android.support.v7.widget.RecyclerView;
/**
* Created by N551 on 25.04.2016.
*/
//GLM_add_resources_SetList
public interface OnStartDragListener {
void onStartDrag(RecyclerView.ViewHolder viewHolder);
}

View File

@ -0,0 +1,93 @@
package hikapro.com.backpack.presenter.adapters.helper.sets;
import android.graphics.Canvas;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.helper.ItemTouchHelper;
/**
* Created by N551 on 25.04.2016.
*/
//GLM_add_resources_SetList
public class SimpleItemTouchHelperCallback extends ItemTouchHelper.Callback {
public static final float ALPHA_FULL = 1.0f;
private final ItemTouchHelperAdapter mAdapter;
public SimpleItemTouchHelperCallback(ItemTouchHelperAdapter adapter) {
mAdapter = adapter;
}
@Override
public boolean isLongPressDragEnabled() {
return true;
}
@Override
public boolean isItemViewSwipeEnabled() {
return false;
}
@Override
public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
final int dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN;
final int swipeFlags = ItemTouchHelper.START | ItemTouchHelper.END;
return makeMovementFlags(dragFlags, swipeFlags);
}
@Override
public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder source, RecyclerView.ViewHolder target) {
if (source.getItemViewType() != target.getItemViewType()) {
return false;
}
// Notify the adapter of the move
mAdapter.onItemMove(source.getAdapterPosition(), target.getAdapterPosition());
return true;
}
@Override
public void onSwiped(RecyclerView.ViewHolder viewHolder, int i) {
// Notify the adapter of the dismissal
mAdapter.onItemDismiss(viewHolder.getAdapterPosition());
}
@Override
public void onChildDraw(Canvas c, RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive) {
if (actionState == ItemTouchHelper.ACTION_STATE_SWIPE) {
// Fade out the view as it is swiped out of the parent's bounds
final float alpha = ALPHA_FULL - Math.abs(dX) / (float) viewHolder.itemView.getWidth();
viewHolder.itemView.setAlpha(alpha);
viewHolder.itemView.setTranslationX(dX);
} else {
super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive);
}
}
@Override
public void onSelectedChanged(RecyclerView.ViewHolder viewHolder, int actionState) {
// We only want the active item to change
if (actionState != ItemTouchHelper.ACTION_STATE_IDLE) {
if (viewHolder instanceof ItemTouchHelperViewHolder) {
// Let the view holder know that this item is being moved or dragged
ItemTouchHelperViewHolder itemViewHolder = (ItemTouchHelperViewHolder) viewHolder;
itemViewHolder.onItemSelected();
}
}
super.onSelectedChanged(viewHolder, actionState);
}
@Override
public void clearView(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
super.clearView(recyclerView, viewHolder);
viewHolder.itemView.setAlpha(ALPHA_FULL);
if (viewHolder instanceof ItemTouchHelperViewHolder) {
// Tell the view holder it's time to restore the idle state
ItemTouchHelperViewHolder itemViewHolder = (ItemTouchHelperViewHolder) viewHolder;
itemViewHolder.onItemClear();
}
}
}

View File

@ -1,11 +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.sets.OnStartDragListener;
/**
* Created by tariel on 19/04/16.
@ -19,18 +19,24 @@ public interface View {
interface SetList extends Base {
void showItemList(Set set);
void setPresenter(Presenter.SetList presenter);
//GLM_add_resources_SetList
OnStartDragListener getOnStartDragListener();
}
interface ItemList extends Base {
void showItemDetail(Item item);
void setPresenter(Presenter.ItemList presenter);
Set getSet();
}
interface ItemDetail extends Base {
void setPresenter(Presenter.ItemDetail presenter);
Item getItem();
}
interface ActivityCallback {
void startSetListFragment();
void startItemListFragment(Set set);
void startItemDetailFragment();
void startItemDetailFragment(Item item);
}
}

View File

@ -0,0 +1,145 @@
package hikapro.com.backpack.view.fragments;
import android.app.Activity;
import android.content.Context;
import android.os.Bundle;
import android.app.Fragment;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import hikapro.com.backpack.model.entities.Item;
import hikapro.com.backpack.presenter.Presenter;
/**
* A simple {@link Fragment} subclass.
*/
public class ItemDetailFragment extends Fragment implements hikapro.com.backpack.view.View.ItemDetail {
private static final String BUNDLE_ITEM_KEY = "BUNDLE_ITEM_KEY";
private Presenter.ItemDetail presenter;
private hikapro.com.backpack.view.View.ActivityCallback activityCallback;
public ItemDetailFragment() {
// Required empty public constructor
}
protected static ItemDetailFragment construct() {
return new ItemDetailFragment();
}
public static ItemDetailFragment newFromItem(Item item) {
ItemDetailFragment ret = ItemDetailFragment.construct();
Bundle args = new Bundle();
args.putSerializable(BUNDLE_ITEM_KEY, item);
ret.setArguments(args);
return ret;
}
// life cycle -->
@Override
public void onAttach(Context context) {
super.onAttach(context);
try {
activityCallback = (hikapro.com.backpack.view.View.ActivityCallback) context;
} catch (ClassCastException e) {
throw new ClassCastException(context.toString()
+ " must implement activityCallback");
}
}
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
try {
activityCallback = (hikapro.com.backpack.view.View.ActivityCallback) activity;
} catch (ClassCastException e) {
throw new ClassCastException(activity.toString()
+ " must implement activityCallback");
}
Log.i(this.toString(), "onAttach");
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.i(this.toString(), "onCreate");
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
View view = presenter.onCreateView(inflater, container, savedInstanceState);
presenter.setView(this);
Log.i(this.toString(), "onCreateView");
return view;
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
Log.i(this.toString(), "onActivityCreated");
}
@Override
public void onStart() {
super.onStart();
Log.i(this.toString(), "onStart");
}
@Override
public void onResume() {
super.onResume();
Log.i(this.toString(), "onResume");
}
@Override
public void onStop() {
super.onStop();
Log.i(this.toString(), "onStop");
}
@Override
public void onDestroyView() {
super.onDestroyView();
presenter.onDestroy(true); // TODO isChangingConfigurations
Log.i(this.toString(), "onDestroyView");
}
@Override
public void onDestroy() {
super.onDestroy();
presenter.onDestroy(false); // TODO isChangingConfigurations
Log.i(this.toString(), "onDestroy");
}
@Override
public void onDetach() {
super.onDetach();
Log.i(this.toString(), "onDetach");
}
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
Log.i(this.toString(), "onSaveInstanceState");
}
// life cycle <--
@Override
public void setPresenter(Presenter.ItemDetail presenter) {
this.presenter = presenter;
}
@Override
public Context getAppContext() {
return this.getActivity().getApplicationContext();
}
@Override
public Context getActivityContext() {
return this.getActivity();
}
@Override
public Item getItem() {
Bundle args = getArguments();
Item item = (Item) args.getSerializable(BUNDLE_ITEM_KEY);
return item;
}
}

View File

@ -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,12 +22,12 @@ 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;
private Presenter.ItemList presenter;
private Presenter.ItemList presenter;
public ItemListFragment() {
// Required empty public constructor
@ -38,10 +42,37 @@ public class ItemListFragment extends Fragment implements hikapro.com.backpack.v
Bundle args = new Bundle();
args.putSerializable(BUNDLE_SET_KEY, set);
ret.setArguments(args);
ret.setPresenter(new ItemListPresenter(ret, set));
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) {
@ -67,12 +98,14 @@ 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
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
presenter.setView(this);
View view = presenter.onCreateView(inflater, container, savedInstanceState);
Log.i(this.toString(), "onCreateView");
return view;
@ -106,6 +139,7 @@ public class ItemListFragment extends Fragment implements hikapro.com.backpack.v
@Override
public void onDestroy() {
super.onDestroy();
presenter.onDestroy(false); // TODO isChangingConfigurations
Log.i(this.toString(), "onDestroy");
}
@Override
@ -123,7 +157,7 @@ public class ItemListFragment extends Fragment implements hikapro.com.backpack.v
@Override
public void showItemDetail(Item item) {
//TODO not implemented
activityCallback.startItemDetailFragment(item);
}
@Override
@ -141,4 +175,9 @@ public class ItemListFragment extends Fragment implements hikapro.com.backpack.v
return this.getActivity();
}
public Set getSet() {
return (Set) getArguments().getSerializable(BUNDLE_SET_KEY);
}
}

View File

@ -1,22 +1,21 @@
package hikapro.com.backpack.view.fragments;
import android.app.Activity;
import android.app.Fragment;
import android.content.Context;
import android.os.Bundle;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Toast;
import hikapro.com.backpack.R;
import hikapro.com.backpack.model.entities.Set;
import hikapro.com.backpack.presenter.Presenter;
import hikapro.com.backpack.presenter.SetListPresenter;
import hikapro.com.backpack.presenter.adapters.helper.sets.OnStartDragListener;
public class SetListFragment extends Fragment implements hikapro.com.backpack.view.View.SetList {
public class SetListFragment extends Fragment implements hikapro.com.backpack.view.View.SetList,
OnStartDragListener {
private Presenter.SetList presenter;
private hikapro.com.backpack.view.View.ActivityCallback activityCallback;
@ -55,16 +54,17 @@ public class SetListFragment extends Fragment implements hikapro.com.backpack.vi
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setPresenter(new SetListPresenter());
Log.i(this.toString(), "onCreate");
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
View view = presenter.onCreateView(inflater, container, savedInstanceState);
presenter.setView(this);
Log.i(this.toString(), "onCreateView");
// Inflate the layout for this fragment
presenter.setView(this);
View view = presenter.onCreateView(inflater, container, savedInstanceState);
return view;
}
@Override
@ -101,6 +101,7 @@ public class SetListFragment extends Fragment implements hikapro.com.backpack.vi
@Override
public void onDestroy() {
super.onDestroy();
presenter.onDestroy(false); // TODO isChangingConfigurations
Log.i(this.toString(), "onDestroy");
}
@Override
@ -130,4 +131,17 @@ public class SetListFragment extends Fragment implements hikapro.com.backpack.vi
public void setPresenter(Presenter.SetList presenter) {
this.presenter = presenter;
}
//GLM_add_resources_SetList
@Override
public void onStartDrag(RecyclerView.ViewHolder viewHolder) {
presenter.onStartDrag(viewHolder);
}
//GLM_add_resources_SetList
@Override
public OnStartDragListener getOnStartDragListener() {
return this;
}
}

View File

@ -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);
}
}

View File

@ -0,0 +1,29 @@
package hikapro.com.backpack.view.recycler;
import android.support.v7.widget.RecyclerView;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
import hikapro.com.backpack.R;
/**
* Created by tariel on 23/04/16.
*/
public class DetailViewHolder extends RecyclerView.ViewHolder {
public TextView title;
public TextView description;
public ImageView photo;
public DetailViewHolder(View v) {
super(v);
setupViews(v);
}
private void setupViews(View view) {
title = (TextView) view.findViewById(R.id.item_title);
description = (TextView) view.findViewById(R.id.item_description);
photo = (ImageView) view.findViewById(R.id.item_photo);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -1,27 +1,53 @@
package hikapro.com.backpack.view.recycler;
import android.graphics.Color;
import android.graphics.drawable.Drawable;
import android.support.v7.widget.CardView;
import android.support.v7.widget.RecyclerView;
import android.view.View;
import android.widget.TextView;
import hikapro.com.backpack.R;
import hikapro.com.backpack.presenter.adapters.helper.sets.ItemTouchHelperViewHolder;
/**
* Created by tariel on 20/04/16.
*/
public class SetViewHolder extends RecyclerView.ViewHolder {
public class SetViewHolder extends RecyclerView.ViewHolder implements ItemTouchHelperViewHolder{
public CardView cardView;
public TextView textView;
//GLM_add_resources_SetList
private View view;
private Drawable drawable;
public SetViewHolder(View v) {
super(v);
//
this.view = v;
//
setupViews(v);
}
private void setupViews(View view) {
cardView = (CardView) view.findViewById(R.id.card_view_set);
textView = (TextView) view.findViewById(R.id.set_text);
this.view = view;
drawable = view.getBackground();
}
//GLM_add_resources_SetList
@Override
public void onItemClear() {
view.setBackground(drawable);
}
//GLM_add_resources_SetList
@Override
public void onItemSelected() {
view.setBackgroundColor(Color.GRAY);
}
}

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M19,6.41L17.59,5 12,10.59 6.41,5 5,6.41 10.59,12 5,17.59 6.41,19 12,13.41 17.59,19 19,17.59 13.41,12z"/>
</vector>

View File

@ -0,0 +1,17 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context="hikapro.com.backpack.view.fragments.ItemDetailFragment">
<android.support.v7.widget.RecyclerView
android:id="@+id/item_detail_recycler"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:scrollbars="vertical">
</android.support.v7.widget.RecyclerView>
</LinearLayout>

View File

@ -6,7 +6,7 @@
android:orientation="vertical">
<android.support.v7.widget.RecyclerView
android:id="@+id/categories_main_recycler"
android:id="@+id/items_recycler"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:scrollbars="vertical"

View File

@ -1,10 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:singleLine="true"
android:background="@android:color/transparent"
android:textSize="10sp"
android:id="@+id/item_text"
android:textStyle="bold" />

View File

@ -0,0 +1,38 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="16dp"
android:paddingLeft="16dp"
android:paddingRight="16dp"
android:paddingTop="16dp">
<TextView
android:id="@+id/item_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_centerHorizontal="true"
android:text="@string/hello_blank_fragment" />
<ImageView
android:id="@+id/item_photo"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/item_title"
android:layout_marginTop="25dp"
android:layout_centerInParent="true"
android:contentDescription="Photo is here"/>
<TextView
android:id="@+id/item_description"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/item_photo"
android:layout_centerHorizontal="true"
android:layout_marginTop="25dp"
android:text="@string/hello_blank_fragment" />
</RelativeLayout>

View File

@ -1,25 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/linear">
<TextView
android:layout_width="match_parent"
android:layout_height="48dp"
android:gravity="center_vertical"
android:paddingLeft="16dp"
android:singleLine="true"
android:textAllCaps="true"
android:background="@android:color/transparent"
android:textSize="16sp"
android:id="@+id/section_text"
android:textStyle="bold" />
<android.support.v7.widget.RecyclerView
android:id="@+id/category_inner_recycler"
android:layout_width="match_parent"
android:layout_height="wrap_content"
/>
</LinearLayout>

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/sticky_header_container"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>

View File

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<TextView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="16dp"
android:background="#001F3F"
android:textSize="28sp"
android:textStyle="bold"
android:textColor="@android:color/white"
tools:text="Animals starting with A"
android:id="@+id/header"
tools:context=".MainActivity"/>

View File

@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="56dp"
>
<TextView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="horizontal"
android:clickable="true"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="8dp"
android:textSize="14sp"
tools:text="Aardvark"
android:id="@+id/item_txt"
tools:context=".MainActivity"/>
<Button
android:id="@+id/undo_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Undo"
android:textAllCaps="true"
android:textColor="@android:color/white"
android:layout_gravity="end|center_vertical"
style="@style/Base.Widget.AppCompat.Button.Borderless"/>
</FrameLayout>

View File

@ -0,0 +1,20 @@
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
tools:context=".MainActivity">
<item
android:id="@+id/action_settings"
android:orderInCategory="1"
android:title="Settings"
android:showAsAction="never" />
app:showAsAction="never" />
<item
android:id="@+id/action_search"
android:icon="@android:drawable/ic_menu_search"
android:showAsAction="always|collapseActionView"
app:showAsAction="always|collapseActionView"
android:actionViewClass="android.widget.SearchView"
android:visible="true"
android:title="Search"/>
</menu>

View File

@ -0,0 +1,8 @@
<resources>
<!-- Base application theme. -->
<style name="AppTheme" parent="android:Theme.Material.Light.DarkActionBar">
</style>
</resources>

View File

@ -2,4 +2,5 @@
<!-- Default screen margins, per the Android Design guidelines. -->
<dimen name="activity_horizontal_margin">16dp</dimen>
<dimen name="activity_vertical_margin">16dp</dimen>
<dimen name="ic_clear_margin">16dp</dimen>
</resources>

View File

@ -1,3 +1,6 @@
<resources>
<string name="app_name">BackPack</string>
<!-- TODO: Remove or change this placeholder text -->
<string name="hello_blank_fragment">Hello blank fragment</string>
</resources>

View File

@ -1,7 +1,7 @@
<resources>
<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
<style name="AppTheme" parent="android:Theme.Holo.Light.DarkActionBar">
<!-- Customize your theme here. -->
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>