Fix task reordering

This commit is contained in:
Phillip Thelen 2017-11-14 21:04:38 +01:00
parent 41072d1cae
commit 6bdba57b6d
22 changed files with 820 additions and 992 deletions

View file

@ -1,7 +1,7 @@
<android.support.v4.widget.SwipeRefreshLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/refresh_layout"
android:id="@+id/refreshLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#fff"

View file

@ -4,7 +4,7 @@
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v4.widget.SwipeRefreshLayout
android:id="@+id/refresh_layout"
android:id="@+id/refreshLayout"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1">

View file

@ -2,7 +2,7 @@
<android.support.v4.widget.SwipeRefreshLayout 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"
android:id="@+id/refresh_layout"
android:id="@+id/refreshLayout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v4.widget.NestedScrollView

View file

@ -2,7 +2,7 @@
<android.support.v4.widget.SwipeRefreshLayout 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"
android:id="@+id/refresh_layout"
android:id="@+id/refreshLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
@ -37,13 +37,13 @@
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/empty_view_title"
android:id="@+id/emptyViewTitle"
tools:text="No Items"
android:textSize="@dimen/card_medium_text"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/empty_view_description"
android:id="@+id/emptyViewDescription"
tools:text="No Items" />
</LinearLayout>
<ImageView

View file

@ -40,7 +40,7 @@ interface TaskRepository : BaseRepository {
fun swapTaskPosition(firstPosition: Int, secondPosition: Int)
fun updateTaskPosition(oldPosition: Int, newPosition: Int): Observable<List<String>>
fun updateTaskPosition(taskType: String, oldPosition: Int, newPosition: Int): Observable<List<String>>
fun getUnmanagedTask(taskid: String): Observable<Task>

View file

@ -175,8 +175,8 @@ class TaskRepositoryImpl(localRepository: TaskLocalRepository, apiClient: ApiCli
localRepository.swapTaskPosition(firstPosition, secondPosition)
}
override fun updateTaskPosition(oldPosition: Int, newPosition: Int): Observable<List<String>> {
return localRepository.getTaskAtPosition(oldPosition)
override fun updateTaskPosition(taskType: String, oldPosition: Int, newPosition: Int): Observable<List<String>> {
return localRepository.getTaskAtPosition(taskType, oldPosition)
.first()
.flatMap { task ->
if (task.isValid) {

View file

@ -27,7 +27,7 @@ interface TaskLocalRepository : BaseLocalRepository {
fun swapTaskPosition(firstPosition: Int, secondPosition: Int)
fun getTaskAtPosition(currentPosition: Int): Observable<Task>
fun getTaskAtPosition(taskType: String, position: Int): Observable<Task>
fun updateIsdue(daily: TaskList): Observable<TaskList>

View file

@ -20,14 +20,15 @@ class RealmTaskLocalRepository(realm: Realm) : RealmBaseLocalRepository(realm),
.equalTo("type", taskType)
.equalTo("userId", userID)
.findAllSorted("position")
.sort("dateCreated", Sort.DESCENDING)
.asObservable()
.filter({ it.isLoaded })
.retry(1)
}
override fun getTasks(userId: String): Observable<RealmResults<Task>> {
return realm.where(Task::class.java).equalTo("userId", userId).findAll().asObservable()
return realm.where(Task::class.java).equalTo("userId", userId)
.findAllSorted("position")
.asObservable()
.filter({ it.isLoaded })
}
@ -146,8 +147,8 @@ class RealmTaskLocalRepository(realm: Realm) : RealmBaseLocalRepository(realm),
}
}
override fun getTaskAtPosition(currentPosition: Int): Observable<Task> {
return realm.where(Task::class.java).equalTo("position", currentPosition).findFirstAsync().asObservable<RealmObject>()
override fun getTaskAtPosition(taskType: String, position: Int): Observable<Task> {
return realm.where(Task::class.java).equalTo("type", taskType).equalTo("position", position).findFirstAsync().asObservable<RealmObject>()
.filter { realmObject -> realmObject.isLoaded }
.cast(Task::class.java)
}

View file

@ -10,7 +10,7 @@ open class Equipment : RealmObject() {
var value: Double = 0.toDouble()
var type: String = ""
@PrimaryKey
var key: String = ""
var key: String? = ""
var klass: String = ""
var specialClass: String = ""
var index: String = ""

View file

@ -11,6 +11,7 @@ public interface TaskRecyclerViewAdapter {
void filter();
void notifyItemMoved(int adapterPosition, int adapterPosition1);
void notifyDataSetChanged();
int getItemViewType(int position);
void setIgnoreUpdates(boolean ignoreUpdates);

View file

@ -56,7 +56,7 @@ class EquipmentOverviewFragment : BaseMainFragment() {
if (this.nameMapping.isEmpty()) {
compositeSubscription.add(inventoryRepository.ownedEquipment.subscribe(Action1 {
for (gear in it) {
this.nameMapping.put(gear.key, gear.text)
this.nameMapping.put(gear.key ?: "", gear.text)
}
setEquipmentNames()

View file

@ -54,7 +54,7 @@ public class ChatListFragment extends BaseFragment implements SwipeRefreshLayout
public boolean isTavern;
@BindView(R.id.recyclerView)
RecyclerView recyclerView;
@BindView(R.id.refresh_layout)
@BindView(R.id.refreshLayout)
SwipeRefreshLayout swipeRefreshLayout;
@BindView(R.id.emojiButton)
ImageButton emojiButton;

View file

@ -42,7 +42,7 @@ public class ChallengeListFragment extends BaseMainFragment implements SwipeRefr
@Named(AppModule.NAMED_USER_ID)
String userId;
@BindView(R.id.refresh_layout)
@BindView(R.id.refreshLayout)
SwipeRefreshLayout swipeRefreshLayout;
@BindView(R.id.recyclerView)
RecyclerViewEmptySupport recyclerView;

View file

@ -51,7 +51,7 @@ public class PartyDetailFragment extends BaseFragment {
@Named(AppModule.NAMED_USER_ID)
String userId;
@BindView(R.id.refresh_layout)
@BindView(R.id.refreshLayout)
SwipeRefreshLayout refreshLayout;
@BindView(R.id.party_invitation_wrapper)

View file

@ -32,7 +32,7 @@ public class PartyMemberListFragment extends BaseFragment {
@BindView(R.id.recyclerView)
RecyclerView recyclerView;
@BindView(R.id.refresh_layout)
@BindView(R.id.refreshLayout)
SwipeRefreshLayout refreshLayout;
private PartyMemberRecyclerViewAdapter adapter;
private View view;

View file

@ -1,108 +0,0 @@
package com.habitrpg.android.habitica.ui.fragments.tasks;
import android.content.Context;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.app.FragmentActivity;
import android.support.v4.content.ContextCompat;
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.LinearLayoutManager;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import com.habitrpg.android.habitica.R;
import com.habitrpg.android.habitica.helpers.RxErrorHandler;
import com.habitrpg.android.habitica.models.user.User;
import com.habitrpg.android.habitica.ui.adapter.tasks.RewardsRecyclerViewAdapter;
import com.habitrpg.android.habitica.ui.helpers.SafeDefaultItemAnimator;
import java.util.ArrayList;
import java.util.Arrays;
public class RewardsRecyclerviewFragment extends TaskRecyclerViewFragment {
public static RewardsRecyclerviewFragment newInstance(Context context, @Nullable User user, String classType) {
RewardsRecyclerviewFragment fragment = new RewardsRecyclerviewFragment();
fragment.setRetainInstance(true);
fragment.user = user;
fragment.classType = classType;
fragment.tutorialStepIdentifier = "rewards";
fragment.tutorialTexts = new ArrayList<>(Arrays.asList(context.getString(R.string.tutorial_rewards_1),
context.getString(R.string.tutorial_rewards_2)));
fragment.tutorialCanBeDeferred = false;
return fragment;
}
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View view = super.onCreateView(inflater, container, savedInstanceState);
if (layoutManager != null) {
((GridLayoutManager)layoutManager).setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
@Override
public int getSpanSize(int position) {
if (recyclerAdapter.getItemViewType(position) < 2) {
return ((GridLayoutManager)layoutManager).getSpanCount();
} else {
return 1;
}
}
});
}
inventoryRepository.retrieveInAppRewards().subscribe(shopItems -> {}, RxErrorHandler.handleEmptyError());
return view;
}
@Override
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
final View finalView = view;
finalView.post(() -> setGridSpanCount(finalView.getWidth()));
recyclerView.setBackgroundColor(ContextCompat.getColor(getContext(), R.color.white));
recyclerView.setItemAnimator(new SafeDefaultItemAnimator());
inventoryRepository.getInAppRewards().subscribe(shopItems -> {
if (recyclerAdapter != null) {
((RewardsRecyclerViewAdapter)recyclerAdapter).updateItemRewards(shopItems);
}
}, RxErrorHandler.handleEmptyError());
}
@NonNull
@Override
protected LinearLayoutManager getLayoutManager(FragmentActivity context) {
return new GridLayoutManager(context, 4);
}
@Override
public void onRefresh() {
swipeRefreshLayout.setRefreshing(true);
userRepository.retrieveUser(true, true)
.flatMap(user -> inventoryRepository.retrieveInAppRewards())
.doOnTerminate(() -> {
if (swipeRefreshLayout != null) {
swipeRefreshLayout.setRefreshing(false);
}
}).subscribe(user1 -> {}, RxErrorHandler.handleEmptyError());
}
private void setGridSpanCount(int width) {
int spanCount = 0;
if (getContext() != null && getContext().getResources() != null) {
float itemWidth;
itemWidth = getContext().getResources().getDimension(R.dimen.reward_width);
spanCount = (int) (width / itemWidth);
}
if (spanCount == 0) {
spanCount = 1;
}
((GridLayoutManager)layoutManager).setSpanCount(spanCount);
}
}

View file

@ -0,0 +1,99 @@
package com.habitrpg.android.habitica.ui.fragments.tasks
import android.content.Context
import android.os.Bundle
import android.support.v4.content.ContextCompat
import android.support.v7.widget.GridLayoutManager
import android.support.v7.widget.LinearLayoutManager
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.helpers.RxErrorHandler
import com.habitrpg.android.habitica.models.shops.ShopItem
import com.habitrpg.android.habitica.models.user.User
import com.habitrpg.android.habitica.ui.adapter.tasks.RewardsRecyclerViewAdapter
import com.habitrpg.android.habitica.ui.helpers.SafeDefaultItemAnimator
import kotlinx.android.synthetic.main.fragment_refresh_recyclerview.*
import rx.functions.Action1
import java.util.*
class RewardsRecyclerviewFragment : TaskRecyclerViewFragment() {
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
val view = super.onCreateView(inflater, container, savedInstanceState)
inventoryRepository.retrieveInAppRewards().subscribe(Action1 { }, RxErrorHandler.handleEmptyError())
return view
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
(layoutManager as GridLayoutManager).spanSizeLookup = object : GridLayoutManager.SpanSizeLookup() {
override fun getSpanSize(position: Int): Int {
return if (recyclerAdapter?.getItemViewType(position) ?: 0 < 2) {
(getLayoutManager(context) as GridLayoutManager).spanCount
} else {
1
}
}
}
view.post { setGridSpanCount(view.width) }
val context = context
if (context != null) {
recyclerView.setBackgroundColor(ContextCompat.getColor(context, R.color.white))
}
recyclerView.itemAnimator = SafeDefaultItemAnimator()
inventoryRepository.inAppRewards.subscribe(Action1 {
(recyclerAdapter as RewardsRecyclerViewAdapter?)?.updateItemRewards(it)
}, RxErrorHandler.handleEmptyError())
}
override fun getLayoutManager(context: Context?): LinearLayoutManager =
GridLayoutManager(context, 4)
override fun onRefresh() {
refreshLayout.isRefreshing = true
userRepository.retrieveUser(true, true)
.flatMap<List<ShopItem>> { inventoryRepository.retrieveInAppRewards() }
.doOnTerminate {
refreshLayout.isRefreshing = false
}.subscribe(Action1 { }, RxErrorHandler.handleEmptyError())
}
private fun setGridSpanCount(width: Int) {
var spanCount = 0
if (context != null && context?.resources != null) {
val itemWidth: Float = context?.resources?.getDimension(R.dimen.reward_width) ?: 0f
spanCount = (width / itemWidth).toInt()
}
if (spanCount == 0) {
spanCount = 1
}
(layoutManager as GridLayoutManager).spanCount = spanCount
}
companion object {
fun newInstance(context: Context?, user: User?, classType: String): RewardsRecyclerviewFragment {
val fragment = RewardsRecyclerviewFragment()
fragment.retainInstance = true
fragment.user = user
fragment.classType = classType
if (context != null) {
fragment.tutorialStepIdentifier = "rewards"
fragment.tutorialTexts = ArrayList(Arrays.asList(context.getString(R.string.tutorial_rewards_1),
context.getString(R.string.tutorial_rewards_2)))
}
fragment.tutorialCanBeDeferred = false
return fragment
}
}
}

View file

@ -1,388 +0,0 @@
package com.habitrpg.android.habitica.ui.fragments.tasks;
import android.content.Context;
import android.graphics.Color;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.app.FragmentActivity;
import android.support.v4.widget.SwipeRefreshLayout;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.helper.ItemTouchHelper;
import android.util.TypedValue;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import com.habitrpg.android.habitica.R;
import com.habitrpg.android.habitica.components.AppComponent;
import com.habitrpg.android.habitica.data.ApiClient;
import com.habitrpg.android.habitica.data.InventoryRepository;
import com.habitrpg.android.habitica.data.TaskRepository;
import com.habitrpg.android.habitica.data.UserRepository;
import com.habitrpg.android.habitica.events.commands.AddNewTaskCommand;
import com.habitrpg.android.habitica.helpers.RxErrorHandler;
import com.habitrpg.android.habitica.helpers.TaskFilterHelper;
import com.habitrpg.android.habitica.models.tasks.Task;
import com.habitrpg.android.habitica.models.user.User;
import com.habitrpg.android.habitica.modules.AppModule;
import com.habitrpg.android.habitica.ui.activities.MainActivity;
import com.habitrpg.android.habitica.ui.adapter.tasks.DailiesRecyclerViewHolder;
import com.habitrpg.android.habitica.ui.adapter.tasks.HabitsRecyclerViewAdapter;
import com.habitrpg.android.habitica.ui.adapter.tasks.RewardsRecyclerViewAdapter;
import com.habitrpg.android.habitica.ui.adapter.tasks.TaskRecyclerViewAdapter;
import com.habitrpg.android.habitica.ui.adapter.tasks.TodosRecyclerViewAdapter;
import com.habitrpg.android.habitica.ui.fragments.BaseFragment;
import com.habitrpg.android.habitica.ui.helpers.RecyclerViewEmptySupport;
import com.habitrpg.android.habitica.ui.helpers.SafeDefaultItemAnimator;
import org.greenrobot.eventbus.EventBus;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.TimeUnit;
import javax.inject.Inject;
import javax.inject.Named;
import butterknife.BindView;
import butterknife.ButterKnife;
/**
* TaskRecyclerViewFragment
* - Creates the View only once
* - Adds FAB Icon
* - Handles the ScrollPosition - if anyone has a better solution please share it
*/
public class TaskRecyclerViewFragment extends BaseFragment implements View.OnClickListener, SwipeRefreshLayout.OnRefreshListener {
private static final String CLASS_TYPE_KEY = "CLASS_TYPE_KEY";
public TaskRecyclerViewAdapter recyclerAdapter;
@Inject
@Named(AppModule.NAMED_USER_ID)
String userID;
@Inject
ApiClient apiClient;
@Inject
TaskFilterHelper taskFilterHelper;
@Inject
UserRepository userRepository;
@Inject
InventoryRepository inventoryRepository;
@Inject
TaskRepository taskRepository;
RecyclerView.LayoutManager layoutManager = null;
@BindView(R.id.refresh_layout)
SwipeRefreshLayout swipeRefreshLayout;
@BindView(R.id.recyclerView)
public RecyclerViewEmptySupport recyclerView;
@BindView(R.id.emptyView)
ViewGroup emptyView;
@BindView(R.id.empty_view_title)
TextView emptyViewTitle;
@BindView(R.id.empty_view_description)
TextView emptyViewDescription;
@Nullable
String classType;
@Nullable
User user;
private View view;
@Nullable
private ItemTouchHelper.Callback mItemTouchCallback;
public static TaskRecyclerViewFragment newInstance(Context context, @Nullable User user, String classType) {
TaskRecyclerViewFragment fragment = new TaskRecyclerViewFragment();
fragment.setRetainInstance(true);
fragment.user = user;
fragment.classType = classType;
List<String> tutorialTexts = null;
switch (fragment.classType) {
case Task.TYPE_HABIT: {
fragment.tutorialStepIdentifier = "habits";
tutorialTexts = Arrays.asList(context.getString(R.string.tutorial_overview),
context.getString(R.string.tutorial_habits_1),
context.getString(R.string.tutorial_habits_2),
context.getString(R.string.tutorial_habits_3),
context.getString(R.string.tutorial_habits_4));
break;
}
case Task.FREQUENCY_DAILY: {
fragment.tutorialStepIdentifier = "dailies";
tutorialTexts = Arrays.asList(context.getString(R.string.tutorial_dailies_1),
context.getString(R.string.tutorial_dailies_2));
break;
}
case Task.TYPE_TODO: {
fragment.tutorialStepIdentifier = "todos";
tutorialTexts = Arrays.asList(context.getString(R.string.tutorial_todos_1),
context.getString(R.string.tutorial_todos_2));
break;
}
}
if (tutorialTexts != null) {
fragment.tutorialTexts = new ArrayList<>(tutorialTexts);
}
fragment.tutorialCanBeDeferred = false;
return fragment;
}
// TODO needs a bit of cleanup
public void setInnerAdapter() {
if (this.classType != null) {
taskRepository.getTasks(this.classType, userID).first().subscribe(tasks -> {
int layoutOfType;
switch (this.classType) {
case Task.TYPE_HABIT:
layoutOfType = R.layout.habit_item_card;
this.recyclerAdapter = new HabitsRecyclerViewAdapter(tasks, true, layoutOfType, taskFilterHelper);
allowReordering();
break;
case Task.TYPE_DAILY:
layoutOfType = R.layout.daily_item_card;
int dailyResetOffset = 0;
if (user != null) {
dailyResetOffset = user.getPreferences().getDayStart();
}
this.recyclerAdapter = new DailiesRecyclerViewHolder(tasks, true, layoutOfType, dailyResetOffset, taskFilterHelper);
allowReordering();
break;
case Task.TYPE_TODO:
layoutOfType = R.layout.todo_item_card;
this.recyclerAdapter = new TodosRecyclerViewAdapter(tasks, true, layoutOfType, taskFilterHelper);
allowReordering();
return;
case Task.TYPE_REWARD:
layoutOfType = R.layout.reward_item_card;
this.recyclerAdapter = new RewardsRecyclerViewAdapter(tasks, getContext(), layoutOfType, user);
break;
}
}, RxErrorHandler.handleEmptyError());
}
}
private void allowReordering() {
ItemTouchHelper itemTouchHelper = new ItemTouchHelper(mItemTouchCallback);
itemTouchHelper.attachToRecyclerView(recyclerView);
}
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
super.onCreateView(inflater, container, savedInstanceState);
if (Task.TYPE_DAILY.equals(classType)) {
if (user != null && user.getPreferences().getDailyDueDefaultView()) {
taskFilterHelper.setActiveFilter(Task.TYPE_DAILY, Task.FILTER_ACTIVE);
}
}
mItemTouchCallback = new ItemTouchHelper.Callback() {
private Integer fromPosition = null;
@Override
public void onSelectedChanged(RecyclerView.ViewHolder viewHolder, int actionState) {
super.onSelectedChanged(viewHolder, actionState);
if (viewHolder != null) {
viewHolder.itemView.setBackgroundColor(Color.LTGRAY);
if (fromPosition == null) {
fromPosition = viewHolder.getAdapterPosition();
}
}
if (swipeRefreshLayout != null) {
swipeRefreshLayout.setEnabled(false);
}
}
public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
recyclerAdapter.notifyItemMoved(viewHolder.getAdapterPosition(), target.getAdapterPosition());
//taskRepository.swapTaskPosition(viewHolder.getAdapterPosition(), target.getAdapterPosition());
return true;
}
@Override
public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
}
//defines the enabled move directions in each state (idle, swiping, dragging).
@Override
public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
return makeFlag(ItemTouchHelper.ACTION_STATE_DRAG,
ItemTouchHelper.DOWN | ItemTouchHelper.UP);
}
@Override
public boolean isItemViewSwipeEnabled() {
return false;
}
@Override
public boolean isLongPressDragEnabled() {
return true;
}
@Override
public void clearView(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
super.clearView(recyclerView, viewHolder);
if (swipeRefreshLayout != null) {
swipeRefreshLayout.setEnabled(true);
}
if (fromPosition != null) {
recyclerAdapter.setIgnoreUpdates(true);
taskRepository.updateTaskPosition(fromPosition, viewHolder.getAdapterPosition())
.delay(2, TimeUnit.SECONDS)
.subscribe(taskPositions -> recyclerAdapter.setIgnoreUpdates(false), RxErrorHandler.handleEmptyError());
fromPosition = null;
}
}
};
if (view == null) {
view = inflater.inflate(R.layout.fragment_refresh_recyclerview, container, false);
ButterKnife.bind(this, view);
android.support.v4.app.FragmentActivity context = getActivity();
layoutManager = recyclerView.getLayoutManager();
if (layoutManager == null) {
layoutManager = getLayoutManager(context);
recyclerView.setLayoutManager(layoutManager);
}
if (recyclerView.getAdapter() == null) {
this.setInnerAdapter();
}
int bottomPadding = (int) (recyclerView.getPaddingBottom() + TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 60, getResources().getDisplayMetrics()));
recyclerView.setPadding(0, 0, 0, bottomPadding);
recyclerView.setItemAnimator(new SafeDefaultItemAnimator());
swipeRefreshLayout.setOnRefreshListener(this);
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
if (newState == RecyclerView.SCROLL_STATE_IDLE && swipeRefreshLayout != null) {
swipeRefreshLayout.setEnabled(((MainActivity)getActivity()).isAppBarExpanded());
}
}
});
if (this.classType != null) {
switch (this.classType) {
case Task.TYPE_HABIT: {
this.emptyViewTitle.setText(R.string.empty_title_habits);
this.emptyViewDescription.setText(R.string.empty_description_habits);
break;
}
case Task.TYPE_DAILY: {
this.emptyViewTitle.setText(R.string.empty_title_dailies);
this.emptyViewDescription.setText(R.string.empty_description_dailies);
break;
}
case Task.TYPE_TODO: {
this.emptyViewTitle.setText(R.string.empty_title_todos);
this.emptyViewDescription.setText(R.string.empty_description_todos);
break;
}
case Task.TYPE_REWARD: {
this.emptyViewTitle.setText(R.string.empty_title_rewards);
break;
}
}
}
}
if (savedInstanceState != null) {
this.classType = savedInstanceState.getString(CLASS_TYPE_KEY, "");
}
return view;
}
@NonNull
protected LinearLayoutManager getLayoutManager(FragmentActivity context) {
return new LinearLayoutManager(context);
}
@Override
public void onDestroy() {
userRepository.close();
inventoryRepository.close();
super.onDestroy();
}
@Override
public void injectFragment(AppComponent component) {
component.inject(this);
}
@Override
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
recyclerView.setAdapter((RecyclerView.Adapter) recyclerAdapter);
if (recyclerAdapter != null) {
recyclerAdapter.filter();
}
if (Task.TYPE_REWARD.equals(getClassName())) {
compositeSubscription.add(taskRepository.getTasks(this.getClassName(), userID)
.subscribe(tasks -> {
if (recyclerAdapter != null) {
recyclerAdapter.updateData(tasks);
}
}, RxErrorHandler.handleEmptyError()));
}
}
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putString(CLASS_TYPE_KEY, this.classType);
}
@Override
public void onClick(View v) {
AddNewTaskCommand event = new AddNewTaskCommand();
event.taskType = this.classType;
EventBus.getDefault().post(event);
}
@Override
public String getDisplayedClassName() {
return this.classType + super.getDisplayedClassName();
}
String getClassName() {
return this.classType != null ? this.classType : "";
}
@Override
public void onRefresh() {
swipeRefreshLayout.setRefreshing(true);
userRepository.retrieveUser(true, true)
.doOnTerminate(() -> {
if (swipeRefreshLayout != null) {
swipeRefreshLayout.setRefreshing(false);
}
}).subscribe(user1 -> {}, RxErrorHandler.handleEmptyError());
}
public void setActiveFilter(String activeFilter) {
if (classType != null) {
taskFilterHelper.setActiveFilter(classType, activeFilter);
}
recyclerAdapter.filter();
}
}

View file

@ -0,0 +1,308 @@
package com.habitrpg.android.habitica.ui.fragments.tasks
import android.content.Context
import android.graphics.Color
import android.os.Bundle
import android.support.v4.widget.SwipeRefreshLayout
import android.support.v7.widget.LinearLayoutManager
import android.support.v7.widget.RecyclerView
import android.support.v7.widget.helper.ItemTouchHelper
import android.util.TypedValue
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.components.AppComponent
import com.habitrpg.android.habitica.data.ApiClient
import com.habitrpg.android.habitica.data.InventoryRepository
import com.habitrpg.android.habitica.data.TaskRepository
import com.habitrpg.android.habitica.data.UserRepository
import com.habitrpg.android.habitica.events.commands.AddNewTaskCommand
import com.habitrpg.android.habitica.helpers.RxErrorHandler
import com.habitrpg.android.habitica.helpers.TaskFilterHelper
import com.habitrpg.android.habitica.models.tasks.Task
import com.habitrpg.android.habitica.models.user.User
import com.habitrpg.android.habitica.modules.AppModule
import com.habitrpg.android.habitica.ui.activities.MainActivity
import com.habitrpg.android.habitica.ui.adapter.tasks.DailiesRecyclerViewHolder
import com.habitrpg.android.habitica.ui.adapter.tasks.HabitsRecyclerViewAdapter
import com.habitrpg.android.habitica.ui.adapter.tasks.RewardsRecyclerViewAdapter
import com.habitrpg.android.habitica.ui.adapter.tasks.TaskRecyclerViewAdapter
import com.habitrpg.android.habitica.ui.adapter.tasks.TodosRecyclerViewAdapter
import com.habitrpg.android.habitica.ui.fragments.BaseFragment
import com.habitrpg.android.habitica.ui.helpers.SafeDefaultItemAnimator
import kotlinx.android.synthetic.main.fragment_refresh_recyclerview.*
import org.greenrobot.eventbus.EventBus
import rx.android.schedulers.AndroidSchedulers
import rx.functions.Action1
import java.util.*
import java.util.concurrent.TimeUnit
import javax.inject.Inject
import javax.inject.Named
open class TaskRecyclerViewFragment : BaseFragment(), View.OnClickListener, SwipeRefreshLayout.OnRefreshListener {
var recyclerAdapter: TaskRecyclerViewAdapter? = null
@field:[Inject Named(AppModule.NAMED_USER_ID)]
lateinit var userID: String
@Inject
lateinit var apiClient: ApiClient
@Inject
lateinit var taskFilterHelper: TaskFilterHelper
@Inject
lateinit var userRepository: UserRepository
@Inject
lateinit var inventoryRepository: InventoryRepository
@Inject
lateinit var taskRepository: TaskRepository
internal var layoutManager: RecyclerView.LayoutManager? = null
internal var classType: String? = null
internal var user: User? = null
private var mItemTouchCallback: ItemTouchHelper.Callback? = null
internal val className: String
get() = this.classType ?: ""
// TODO needs a bit of cleanup
private fun setInnerAdapter() {
val adapter: RecyclerView.Adapter<*>? = when (this.classType) {
Task.TYPE_HABIT -> {
HabitsRecyclerViewAdapter(null, true, R.layout.habit_item_card, taskFilterHelper)
}
Task.TYPE_DAILY -> {
var dailyResetOffset = 0
if (user != null) {
dailyResetOffset = user?.preferences?.dayStart ?: 0
}
DailiesRecyclerViewHolder(null, true, R.layout.daily_item_card, dailyResetOffset, taskFilterHelper)
}
Task.TYPE_TODO -> {
TodosRecyclerViewAdapter(null, true, R.layout.todo_item_card, taskFilterHelper)
}
Task.TYPE_REWARD -> {
RewardsRecyclerViewAdapter(null, context, R.layout.reward_item_card, user)
}
else -> null
}
if (classType != Task.TYPE_REWARD) {
allowReordering()
}
recyclerAdapter = adapter as TaskRecyclerViewAdapter
recyclerView.adapter = adapter
if (this.classType != null) {
taskRepository.getTasks(this.classType ?: "", userID).first().subscribe(Action1 { this.recyclerAdapter?.updateData(it) }, RxErrorHandler.handleEmptyError())
}
}
private fun allowReordering() {
val itemTouchHelper = ItemTouchHelper(mItemTouchCallback)
itemTouchHelper.attachToRecyclerView(recyclerView)
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
super.onCreateView(inflater, container, savedInstanceState)
if (Task.TYPE_DAILY == classType) {
if (user != null && user?.preferences?.dailyDueDefaultView == true) {
taskFilterHelper.setActiveFilter(Task.TYPE_DAILY, Task.FILTER_ACTIVE)
}
}
mItemTouchCallback = object : ItemTouchHelper.Callback() {
private var fromPosition: Int? = null
override fun onSelectedChanged(viewHolder: RecyclerView.ViewHolder?, actionState: Int) {
super.onSelectedChanged(viewHolder, actionState)
if (viewHolder != null) {
viewHolder.itemView.setBackgroundColor(Color.LTGRAY)
if (fromPosition == null) {
fromPosition = viewHolder.adapterPosition
}
}
refreshLayout.isEnabled = false
}
override fun onMove(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder, target: RecyclerView.ViewHolder): Boolean {
recyclerAdapter?.notifyItemMoved(viewHolder.adapterPosition, target.adapterPosition)
//taskRepository.swapTaskPosition(viewHolder.getAdapterPosition(), target.getAdapterPosition());
return true
}
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {}
//defines the enabled move directions in each state (idle, swiping, dragging).
override fun getMovementFlags(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder): Int {
return ItemTouchHelper.Callback.makeFlag(ItemTouchHelper.ACTION_STATE_DRAG,
ItemTouchHelper.DOWN or ItemTouchHelper.UP)
}
override fun isItemViewSwipeEnabled(): Boolean = false
override fun isLongPressDragEnabled(): Boolean = true
override fun clearView(recyclerView: RecyclerView?, viewHolder: RecyclerView.ViewHolder) {
super.clearView(recyclerView, viewHolder)
refreshLayout?.isEnabled = true
val fromPosition = fromPosition
if (fromPosition != null) {
recyclerAdapter?.ignoreUpdates = true
taskRepository.updateTaskPosition(classType ?: "", fromPosition, viewHolder.adapterPosition)
.delay(1, TimeUnit.SECONDS)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(Action1 { recyclerAdapter?.ignoreUpdates = false
recyclerAdapter?.notifyDataSetChanged()}, RxErrorHandler.handleEmptyError())
this.fromPosition = null
}
}
}
if (savedInstanceState != null) {
this.classType = savedInstanceState.getString(CLASS_TYPE_KEY, "")
}
return inflater.inflate(R.layout.fragment_refresh_recyclerview, container, false)
}
protected open fun getLayoutManager(context: Context?): LinearLayoutManager = LinearLayoutManager(context)
override fun onDestroy() {
userRepository.close()
inventoryRepository.close()
super.onDestroy()
}
override fun injectFragment(component: AppComponent) {
component.inject(this)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
recyclerView.adapter = recyclerAdapter as RecyclerView.Adapter<*>?
recyclerAdapter?.filter()
layoutManager = recyclerView.layoutManager
if (layoutManager == null) {
layoutManager = getLayoutManager(context)
recyclerView.layoutManager = layoutManager
}
if (recyclerView.adapter == null) {
this.setInnerAdapter()
}
val bottomPadding = (recyclerView.paddingBottom + TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 60f, resources.displayMetrics)).toInt()
recyclerView.setPadding(0, 0, 0, bottomPadding)
recyclerView.itemAnimator = SafeDefaultItemAnimator()
refreshLayout.setOnRefreshListener(this)
recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrollStateChanged(recyclerView: RecyclerView?, newState: Int) {
super.onScrollStateChanged(recyclerView, newState)
if (newState == RecyclerView.SCROLL_STATE_IDLE) {
refreshLayout?.isEnabled = (activity as MainActivity).isAppBarExpanded
}
}
})
if (this.classType != null) {
when (this.classType) {
Task.TYPE_HABIT -> {
this.emptyViewTitle.setText(R.string.empty_title_habits)
this.emptyViewDescription.setText(R.string.empty_description_habits)
}
Task.TYPE_DAILY -> {
this.emptyViewTitle.setText(R.string.empty_title_dailies)
this.emptyViewDescription.setText(R.string.empty_description_dailies)
}
Task.TYPE_TODO -> {
this.emptyViewTitle.setText(R.string.empty_title_todos)
this.emptyViewDescription.setText(R.string.empty_description_todos)
}
Task.TYPE_REWARD -> {
this.emptyViewTitle.setText(R.string.empty_title_rewards)
}
}
}
if (Task.TYPE_REWARD == className) {
compositeSubscription.add(taskRepository.getTasks(this.className, userID)
.subscribe(Action1 { recyclerAdapter?.updateData(it) }, RxErrorHandler.handleEmptyError()))
}
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
outState.putString(CLASS_TYPE_KEY, this.classType)
}
override fun onClick(v: View) {
val event = AddNewTaskCommand()
event.taskType = this.classType
EventBus.getDefault().post(event)
}
override fun getDisplayedClassName(): String = this.classType + super.getDisplayedClassName()
override fun onRefresh() {
refreshLayout.isRefreshing = true
userRepository.retrieveUser(true, true)
.doOnTerminate {
refreshLayout.isRefreshing = false
}.subscribe(Action1 { }, RxErrorHandler.handleEmptyError())
}
fun setActiveFilter(activeFilter: String) {
if (classType != null) {
taskFilterHelper.setActiveFilter(classType, activeFilter)
}
recyclerAdapter?.filter()
}
companion object {
private val CLASS_TYPE_KEY = "CLASS_TYPE_KEY"
fun newInstance(context: Context?, user: User?, classType: String): TaskRecyclerViewFragment {
val fragment = TaskRecyclerViewFragment()
fragment.retainInstance = true
fragment.user = user
fragment.classType = classType
var tutorialTexts: List<String>? = null
if (context != null) {
when (fragment.classType) {
Task.TYPE_HABIT -> {
fragment.tutorialStepIdentifier = "habits"
tutorialTexts = Arrays.asList(context.getString(R.string.tutorial_overview),
context.getString(R.string.tutorial_habits_1),
context.getString(R.string.tutorial_habits_2),
context.getString(R.string.tutorial_habits_3),
context.getString(R.string.tutorial_habits_4))
}
Task.FREQUENCY_DAILY -> {
fragment.tutorialStepIdentifier = "dailies"
tutorialTexts = Arrays.asList(context.getString(R.string.tutorial_dailies_1),
context.getString(R.string.tutorial_dailies_2))
}
Task.TYPE_TODO -> {
fragment.tutorialStepIdentifier = "todos"
tutorialTexts = Arrays.asList(context.getString(R.string.tutorial_todos_1),
context.getString(R.string.tutorial_todos_2))
}
}
}
if (tutorialTexts != null) {
fragment.tutorialTexts = ArrayList(tutorialTexts)
}
fragment.tutorialCanBeDeferred = false
return fragment
}
}
}

View file

@ -1,475 +0,0 @@
package com.habitrpg.android.habitica.ui.fragments.tasks;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentPagerAdapter;
import android.support.v4.view.ViewPager;
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 com.github.clans.fab.FloatingActionButton;
import com.github.clans.fab.FloatingActionMenu;
import com.habitrpg.android.habitica.HabiticaBaseApplication;
import com.habitrpg.android.habitica.R;
import com.habitrpg.android.habitica.components.AppComponent;
import com.habitrpg.android.habitica.data.TagRepository;
import com.habitrpg.android.habitica.events.TaskTappedEvent;
import com.habitrpg.android.habitica.events.commands.AddNewTaskCommand;
import com.habitrpg.android.habitica.helpers.RxErrorHandler;
import com.habitrpg.android.habitica.helpers.TaskFilterHelper;
import com.habitrpg.android.habitica.models.TutorialStep;
import com.habitrpg.android.habitica.models.tasks.Task;
import com.habitrpg.android.habitica.models.user.User;
import com.habitrpg.android.habitica.ui.activities.MainActivity;
import com.habitrpg.android.habitica.ui.activities.TaskFormActivity;
import com.habitrpg.android.habitica.ui.fragments.BaseMainFragment;
import com.habitrpg.android.habitica.ui.views.tasks.TaskFilterDialog;
import com.roughike.bottombar.BottomBarTab;
import org.greenrobot.eventbus.Subscribe;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.WeakHashMap;
import javax.inject.Inject;
public class TasksFragment extends BaseMainFragment {
private static final int TASK_CREATED_RESULT = 1;
private static final int TASK_UPDATED_RESULT = 2;
public ViewPager viewPager;
@Inject
public TaskFilterHelper taskFilterHelper; // This will be used for this fragment. Currently being used to help filtering
@Inject
TagRepository tagRepository;
MenuItem refreshItem;
FloatingActionMenu floatingMenu;
Map<Integer, TaskRecyclerViewFragment> viewFragmentsDictionary = new WeakHashMap<>();
private boolean displayingTaskForm;
@Nullable
private MenuItem filterMenuItem;
public void setActivity(MainActivity activity) {
super.setActivity(activity);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
this.usesTabLayout = false;
this.usesBottomNavigation = true;
this.displayingTaskForm = false;
super.onCreateView(inflater, container, savedInstanceState);
View v = inflater.inflate(R.layout.fragment_viewpager, container, false);
viewPager = v.findViewById(R.id.viewPager);
View view = inflater.inflate(R.layout.floating_menu_tasks, floatingMenuWrapper, true);
if (FloatingActionMenu.class.equals(view.getClass())) {
floatingMenu = (FloatingActionMenu) view;
} else {
ViewGroup frame = (ViewGroup) view;
floatingMenu = frame.findViewById(R.id.fab_menu);
}
FloatingActionButton habit_fab = floatingMenu.findViewById(R.id.fab_new_habit);
habit_fab.setOnClickListener(v1 -> openNewTaskActivity(Task.TYPE_HABIT));
FloatingActionButton daily_fab = floatingMenu.findViewById(R.id.fab_new_daily);
daily_fab.setOnClickListener(v1 -> openNewTaskActivity(Task.TYPE_DAILY));
FloatingActionButton todo_fab = floatingMenu.findViewById(R.id.fab_new_todo);
todo_fab.setOnClickListener(v1 -> openNewTaskActivity(Task.TYPE_TODO));
FloatingActionButton reward_fab = floatingMenu.findViewById(R.id.fab_new_reward);
reward_fab.setOnClickListener(v1 -> openNewTaskActivity(Task.TYPE_REWARD));
floatingMenu.setOnMenuButtonLongClickListener(this::onFloatingMenuLongClicked);
loadTaskLists();
if (bottomNavigation != null) {
bottomNavigation.setBadgesHideWhenActive(true);
bottomNavigation.setOnTabSelectListener(tabId -> {
if (tabId == R.id.tab_habits) {
viewPager.setCurrentItem(0);
} else if (tabId == R.id.tab_dailies) {
viewPager.setCurrentItem(1);
} else if (tabId == R.id.tab_todos) {
viewPager.setCurrentItem(2);
} else if (tabId == R.id.tab_rewards) {
viewPager.setCurrentItem(3);
}
updateBottomBarBadges();
});
}
return v;
}
@Override
public void onDestroy() {
tagRepository.close();
if (bottomNavigation != null) {
bottomNavigation.removeOnTabSelectListener();
}
super.onDestroy();
}
private boolean onFloatingMenuLongClicked(View view) {
TaskRecyclerViewFragment currentFragment = getActiveFragment();
if (currentFragment != null) {
String className = currentFragment.getClassName();
openNewTaskActivity(className);
}
return true;
}
@Override
public void injectFragment(AppComponent component) {
component.inject(this);
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
inflater.inflate(R.menu.menu_main_activity, menu);
filterMenuItem = menu.findItem(R.id.action_search);
updateFilterIcon();
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
switch (id) {
case R.id.action_search:
showFilterDialog();
return true;
case R.id.action_reload:
refreshItem = item;
refresh();
return true;
}
return super.onOptionsItemSelected(item);
}
private void showFilterDialog() {
TaskFilterDialog dialog = new TaskFilterDialog(getContext(), HabiticaBaseApplication.getComponent());
if (user != null) {
dialog.setTags(user.getTags().createSnapshot());
}
dialog.setActiveTags(taskFilterHelper.getTags());
if (getActiveFragment() != null) {
String taskType = getActiveFragment().classType;
if (taskType != null) {
dialog.setTaskType(taskType, taskFilterHelper.getActiveFilter(taskType));
}
}
dialog.setListener((activeTaskFilter, activeTags) -> {
if (viewFragmentsDictionary == null) {
return;
}
int activePos = viewPager.getCurrentItem();
if (activePos >= 1 && viewFragmentsDictionary.get(activePos-1) != null && activePos >= 1 && viewFragmentsDictionary.get(activePos-1).recyclerAdapter != null) {
viewFragmentsDictionary.get(activePos-1).recyclerAdapter.filter();
}
if (activePos < viewPager.getAdapter().getCount()-1 && viewFragmentsDictionary.get(activePos+1) != null && viewFragmentsDictionary.get(activePos+1).recyclerAdapter != null) {
viewFragmentsDictionary.get(activePos+1).recyclerAdapter.filter();
}
if (getActiveFragment() != null) {
getActiveFragment().setActiveFilter(activeTaskFilter);
}
taskFilterHelper.setTags(activeTags);
updateFilterIcon();
});
dialog.show();
}
public void refresh() {
if (getActiveFragment() != null) {
getActiveFragment().onRefresh();
}
}
public void loadTaskLists() {
android.support.v4.app.FragmentManager fragmentManager = getChildFragmentManager();
viewPager.setAdapter(new FragmentPagerAdapter(fragmentManager) {
@Override
public Fragment getItem(int position) {
TaskRecyclerViewFragment fragment;
switch (position) {
case 0:
fragment = TaskRecyclerViewFragment.newInstance(getContext(), user, Task.TYPE_HABIT);
break;
case 1:
fragment = TaskRecyclerViewFragment.newInstance(getContext(), user, Task.TYPE_DAILY);
break;
case 3:
fragment = RewardsRecyclerviewFragment.newInstance(getContext(), user, Task.TYPE_REWARD);
break;
default:
fragment = TaskRecyclerViewFragment.newInstance(getContext(), user, Task.TYPE_TODO);
}
viewFragmentsDictionary.put(position, fragment);
return fragment;
}
@Override
public int getCount() {
return 4;
}
@Override
public CharSequence getPageTitle(int position) {
if (activity != null) {
switch (position) {
case 0:
return activity.getString(R.string.habits);
case 1:
return activity.getString(R.string.dailies);
case 2:
return activity.getString(R.string.todos);
case 3:
return activity.getString(R.string.rewards);
}
}
return "";
}
});
viewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
}
@Override
public void onPageSelected(int position) {
if (bottomNavigation != null) {
bottomNavigation.selectTabAtPosition(position);
}
updateFilterIcon();
}
@Override
public void onPageScrollStateChanged(int state) {
}
});
}
private void updateFilterIcon() {
if (filterMenuItem == null) {
return;
}
int filterCount = 0;
if (getActiveFragment() != null) {
filterCount = taskFilterHelper.howMany(getActiveFragment().classType);
}
if (filterCount == 0) {
filterMenuItem.setIcon(R.drawable.ic_action_filter_list);
} else {
filterMenuItem.setIcon(R.drawable.ic_filters_active);
}
}
private void updateBottomBarBadges() {
if (bottomNavigation == null) {
return;
}
tutorialRepository.getTutorialSteps(Arrays.asList("habits", "dailies", "todos", "rewards")).subscribe(tutorialSteps -> {
List<String> activeTutorialFragments = new ArrayList<>();
for (TutorialStep step : tutorialSteps) {
int id = -1;
String taskType = null;
switch (step.getIdentifier()) {
case "habits":
id = R.id.tab_habits;
taskType = Task.TYPE_HABIT;
break;
case "dailies":
id = R.id.tab_dailies;
taskType = Task.TYPE_DAILY;
break;
case "todos":
id = R.id.tab_todos;
taskType = Task.TYPE_TODO;
break;
case "rewards":
id = R.id.tab_rewards;
taskType = Task.TYPE_REWARD;
break;
}
BottomBarTab tab = bottomNavigation.getTabWithId(id);
if (step.shouldDisplay()) {
tab.setBadgeCount(1);
activeTutorialFragments.add(taskType);
} else {
tab.removeBadge();
}
}
if (activeTutorialFragments.size() == 1) {
TaskRecyclerViewFragment fragment = viewFragmentsDictionary.get(indexForTaskType(activeTutorialFragments.get(0)));
if (fragment != null && fragment.tutorialTexts != null && getContext() != null) {
String finalText = getContext().getString(R.string.tutorial_tasks_complete);
if (!fragment.tutorialTexts.contains(finalText)) {
fragment.tutorialTexts.add(finalText);
}
}
}
}, RxErrorHandler.handleEmptyError());
BottomBarTab tab = bottomNavigation.getTabWithId(R.id.tab_dailies);
}
// endregion
//region Events
public void updateUserData(User user) {
super.updateUserData(user);
}
private void openNewTaskActivity(String type) {
if (this.displayingTaskForm) {
return;
}
boolean allocationMode = false;
if (user != null && user.getPreferences() != null) {
allocationMode = user.getPreferences().hasTaskBasedAllocation();
}
Bundle bundle = new Bundle();
bundle.putString(TaskFormActivity.TASK_TYPE_KEY, type);
bundle.putString(TaskFormActivity.USER_ID_KEY, this.user != null ? this.user.getId() : null);
bundle.putBoolean(TaskFormActivity.ALLOCATION_MODE_KEY, allocationMode);
Intent intent = new Intent(activity, TaskFormActivity.class);
intent.putExtras(bundle);
intent.setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
if (this.isAdded()) {
this.displayingTaskForm = true;
startActivityForResult(intent, TASK_CREATED_RESULT);
}
}
@Nullable
private TaskRecyclerViewFragment getActiveFragment() {
return viewFragmentsDictionary.get(viewPager.getCurrentItem());
}
@Subscribe
public void onEvent(TaskTappedEvent event) {
if (this.displayingTaskForm) {
return;
}
boolean allocationMode = false;
if (user != null && user.getPreferences() != null) {
allocationMode = user.getPreferences().hasTaskBasedAllocation();
}
Bundle bundle = new Bundle();
bundle.putString(TaskFormActivity.TASK_TYPE_KEY, event.Task.getType());
bundle.putString(TaskFormActivity.TASK_ID_KEY, event.Task.getId());
bundle.putString(TaskFormActivity.USER_ID_KEY, this.user != null ? this.user.getId() : null);
bundle.putBoolean(TaskFormActivity.ALLOCATION_MODE_KEY, allocationMode);
Intent intent = new Intent(activity, TaskFormActivity.class);
intent.putExtras(bundle);
this.displayingTaskForm = true;
if (isAdded()) {
startActivityForResult(intent, TASK_UPDATED_RESULT);
}
}
@Subscribe
public void onEvent(AddNewTaskCommand event) {
openNewTaskActivity(event.taskType.toLowerCase(Locale.US));
}
//endregion Events
@Override
public void onDestroyView() {
super.onDestroyView();
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
switch (requestCode) {
case (TASK_CREATED_RESULT):
this.displayingTaskForm = false;
onTaskCreatedResult(resultCode, data);
break;
case (TASK_UPDATED_RESULT):
this.displayingTaskForm = false;
break;
}
if (floatingMenu != null) {
floatingMenu.close(true);
}
}
private void onTaskCreatedResult(int resultCode, Intent data) {
if (resultCode == Activity.RESULT_OK) {
String taskType = data.getStringExtra(TaskFormActivity.TASK_TYPE_KEY);
switchToTaskTab(taskType);
}
}
private void switchToTaskTab(String taskType) {
int index = indexForTaskType(taskType);
if (viewPager != null && index != -1) {
viewPager.setCurrentItem(index);
updateBottomBarBadges();
}
}
private int indexForTaskType(String taskType) {
if (taskType != null) {
for (int index = 0; index < viewFragmentsDictionary.size(); index++) {
TaskRecyclerViewFragment fragment = viewFragmentsDictionary.get(index);
if (fragment != null && taskType.equals(fragment.getClassName())) {
return index;
}
}
}
return -1;
}
@Nullable
@Override
public String getDisplayedClassName() {
return null;
}
@Nullable
@Override
public String customTitle() {
return null;
}
@Override
public boolean addToBackStack() {
return false;
}
}

View file

@ -0,0 +1,389 @@
package com.habitrpg.android.habitica.ui.fragments.tasks
import android.app.Activity
import android.content.Intent
import android.os.Bundle
import android.support.v4.app.Fragment
import android.support.v4.app.FragmentPagerAdapter
import android.support.v4.view.ViewPager
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 com.github.clans.fab.FloatingActionButton
import com.github.clans.fab.FloatingActionMenu
import com.habitrpg.android.habitica.HabiticaBaseApplication
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.components.AppComponent
import com.habitrpg.android.habitica.data.TagRepository
import com.habitrpg.android.habitica.events.TaskTappedEvent
import com.habitrpg.android.habitica.events.commands.AddNewTaskCommand
import com.habitrpg.android.habitica.helpers.RxErrorHandler
import com.habitrpg.android.habitica.helpers.TaskFilterHelper
import com.habitrpg.android.habitica.models.tasks.Task
import com.habitrpg.android.habitica.ui.activities.TaskFormActivity
import com.habitrpg.android.habitica.ui.fragments.BaseMainFragment
import com.habitrpg.android.habitica.ui.views.tasks.TaskFilterDialog
import org.greenrobot.eventbus.Subscribe
import rx.functions.Action1
import java.util.*
import javax.inject.Inject
class TasksFragment : BaseMainFragment() {
var viewPager: ViewPager? = null
@Inject
lateinit var taskFilterHelper: TaskFilterHelper
@Inject
lateinit var tagRepository: TagRepository
internal var refreshItem: MenuItem? = null
internal var floatingMenu: FloatingActionMenu? = null
internal var viewFragmentsDictionary: MutableMap<Int, TaskRecyclerViewFragment>? = WeakHashMap()
private var displayingTaskForm: Boolean = false
private var filterMenuItem: MenuItem? = null
private val activeFragment: TaskRecyclerViewFragment?
get() = viewFragmentsDictionary?.get(viewPager?.currentItem)
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
this.usesTabLayout = false
this.usesBottomNavigation = true
this.displayingTaskForm = false
super.onCreateView(inflater, container, savedInstanceState)
val v = inflater.inflate(R.layout.fragment_viewpager, container, false)
viewPager = v.findViewById(R.id.viewPager)
val view = inflater.inflate(R.layout.floating_menu_tasks, floatingMenuWrapper, true)
floatingMenu = if (FloatingActionMenu::class.java == view.javaClass) {
view as FloatingActionMenu
} else {
val frame = view as ViewGroup
frame.findViewById(R.id.fab_menu)
}
val habitFab = floatingMenu?.findViewById<FloatingActionButton>(R.id.fab_new_habit)
habitFab?.setOnClickListener { openNewTaskActivity(Task.TYPE_HABIT) }
val dailyFab = floatingMenu?.findViewById<FloatingActionButton>(R.id.fab_new_daily)
dailyFab?.setOnClickListener { openNewTaskActivity(Task.TYPE_DAILY) }
val todoFab = floatingMenu?.findViewById<FloatingActionButton>(R.id.fab_new_todo)
todoFab?.setOnClickListener { openNewTaskActivity(Task.TYPE_TODO) }
val rewardFab = floatingMenu?.findViewById<FloatingActionButton>(R.id.fab_new_reward)
rewardFab?.setOnClickListener { openNewTaskActivity(Task.TYPE_REWARD) }
floatingMenu?.setOnMenuButtonLongClickListener { this.onFloatingMenuLongClicked(it) }
loadTaskLists()
bottomNavigation?.setBadgesHideWhenActive(true)
bottomNavigation?.setOnTabSelectListener { tabId ->
when (tabId) {
R.id.tab_habits -> viewPager?.currentItem = 0
R.id.tab_dailies -> viewPager?.currentItem = 1
R.id.tab_todos -> viewPager?.currentItem = 2
R.id.tab_rewards -> viewPager?.currentItem = 3
}
updateBottomBarBadges()
}
return v
}
override fun onDestroy() {
tagRepository.close()
bottomNavigation?.removeOnTabSelectListener()
super.onDestroy()
}
private fun onFloatingMenuLongClicked(view: View): Boolean {
val currentFragment = activeFragment
if (currentFragment != null) {
val className = currentFragment.className
openNewTaskActivity(className)
}
return true
}
override fun injectFragment(component: AppComponent) {
component.inject(this)
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
inflater.inflate(R.menu.menu_main_activity, menu)
filterMenuItem = menu.findItem(R.id.action_search)
updateFilterIcon()
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
val id = item.itemId
when (id) {
R.id.action_search -> {
showFilterDialog()
return true
}
R.id.action_reload -> {
refreshItem = item
refresh()
return true
}
}
return super.onOptionsItemSelected(item)
}
private fun showFilterDialog() {
val dialog = TaskFilterDialog(context, HabiticaBaseApplication.getComponent())
if (user != null) {
dialog.setTags(user?.tags?.createSnapshot())
}
dialog.setActiveTags(taskFilterHelper.tags)
if (activeFragment != null) {
val taskType = activeFragment?.classType
if (taskType != null) {
dialog.setTaskType(taskType, taskFilterHelper.getActiveFilter(taskType))
}
}
dialog.setListener { activeTaskFilter, activeTags ->
if (viewFragmentsDictionary == null) {
return@setListener
}
val activePos = viewPager?.currentItem ?: 0
if (activePos >= 1 && viewFragmentsDictionary?.get(activePos - 1) != null && activePos >= 1 && viewFragmentsDictionary?.get(activePos - 1)?.recyclerAdapter != null) {
viewFragmentsDictionary?.get(activePos - 1)?.recyclerAdapter?.filter()
}
if (activePos < viewPager?.adapter?.count ?: 0 - 1 && viewFragmentsDictionary?.get(activePos + 1) != null && viewFragmentsDictionary?.get(activePos + 1)?.recyclerAdapter != null) {
viewFragmentsDictionary?.get(activePos + 1)?.recyclerAdapter?.filter()
}
activeFragment?.setActiveFilter(activeTaskFilter)
taskFilterHelper.tags = activeTags
updateFilterIcon()
}
dialog.show()
}
private fun refresh() {
activeFragment?.onRefresh()
}
private fun loadTaskLists() {
val fragmentManager = childFragmentManager
viewPager?.adapter = object : FragmentPagerAdapter(fragmentManager) {
override fun getItem(position: Int): Fragment {
val fragment: TaskRecyclerViewFragment = when (position) {
0 -> TaskRecyclerViewFragment.newInstance(context, user, Task.TYPE_HABIT)
1 -> TaskRecyclerViewFragment.newInstance(context, user, Task.TYPE_DAILY)
3 -> RewardsRecyclerviewFragment.newInstance(context, user, Task.TYPE_REWARD)
else -> TaskRecyclerViewFragment.newInstance(context, user, Task.TYPE_TODO)
}
viewFragmentsDictionary?.put(position, fragment)
return fragment
}
override fun getCount(): Int = 4
override fun getPageTitle(position: Int): CharSequence? = when (position) {
0 -> activity?.getString(R.string.habits)
1 -> activity?.getString(R.string.dailies)
2 -> activity?.getString(R.string.todos)
3 -> activity?.getString(R.string.rewards)
else -> ""
}
}
viewPager?.addOnPageChangeListener(object : ViewPager.OnPageChangeListener {
override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {
}
override fun onPageSelected(position: Int) {
bottomNavigation?.selectTabAtPosition(position)
updateFilterIcon()
}
override fun onPageScrollStateChanged(state: Int) {
}
})
}
private fun updateFilterIcon() {
if (filterMenuItem == null) {
return
}
var filterCount = 0
if (activeFragment != null) {
filterCount = taskFilterHelper.howMany(activeFragment?.classType)
}
if (filterCount == 0) {
filterMenuItem?.setIcon(R.drawable.ic_action_filter_list)
} else {
filterMenuItem?.setIcon(R.drawable.ic_filters_active)
}
}
private fun updateBottomBarBadges() {
if (bottomNavigation == null) {
return
}
tutorialRepository.getTutorialSteps(Arrays.asList("habits", "dailies", "todos", "rewards")).subscribe(Action1 { tutorialSteps ->
val activeTutorialFragments = ArrayList<String>()
for (step in tutorialSteps) {
var id = -1
val taskType = when (step.identifier) {
"habits" -> {
id = R.id.tab_habits
Task.TYPE_HABIT
}
"dailies" -> {
id = R.id.tab_dailies
Task.TYPE_DAILY
}
"todos" -> {
id = R.id.tab_todos
Task.TYPE_TODO
}
"rewards" -> {
id = R.id.tab_rewards
Task.TYPE_REWARD
}
else -> ""
}
val tab = bottomNavigation?.getTabWithId(id)
if (step.shouldDisplay()) {
tab?.setBadgeCount(1)
activeTutorialFragments.add(taskType)
} else {
tab?.removeBadge()
}
}
if (activeTutorialFragments.size == 1) {
val fragment = viewFragmentsDictionary?.get(indexForTaskType(activeTutorialFragments[0]))
if (fragment?.tutorialTexts != null && context != null) {
val finalText = context?.getString(R.string.tutorial_tasks_complete)
if (!fragment.tutorialTexts.contains(finalText)) {
fragment.tutorialTexts.add(finalText)
}
}
}
}, RxErrorHandler.handleEmptyError())
}
// endregion
private fun openNewTaskActivity(type: String) {
if (this.displayingTaskForm) {
return
}
val allocationMode = user?.preferences?.hasTaskBasedAllocation() ?: false
val bundle = Bundle()
bundle.putString(TaskFormActivity.TASK_TYPE_KEY, type)
bundle.putString(TaskFormActivity.USER_ID_KEY, if (this.user != null) this.user?.id else null)
bundle.putBoolean(TaskFormActivity.ALLOCATION_MODE_KEY, allocationMode)
val intent = Intent(activity, TaskFormActivity::class.java)
intent.putExtras(bundle)
intent.flags = Intent.FLAG_ACTIVITY_REORDER_TO_FRONT
if (this.isAdded) {
this.displayingTaskForm = true
startActivityForResult(intent, TASK_CREATED_RESULT)
}
}
@Subscribe
fun onEvent(event: TaskTappedEvent) {
if (this.displayingTaskForm) {
return
}
val allocationMode = user?.preferences?.hasTaskBasedAllocation() ?: false
val bundle = Bundle()
bundle.putString(TaskFormActivity.TASK_TYPE_KEY, event.Task.type)
bundle.putString(TaskFormActivity.TASK_ID_KEY, event.Task.id)
bundle.putString(TaskFormActivity.USER_ID_KEY, if (this.user != null) this.user?.id else null)
bundle.putBoolean(TaskFormActivity.ALLOCATION_MODE_KEY, allocationMode)
val intent = Intent(activity, TaskFormActivity::class.java)
intent.putExtras(bundle)
this.displayingTaskForm = true
if (isAdded) {
startActivityForResult(intent, TASK_UPDATED_RESULT)
}
}
@Subscribe
fun onEvent(event: AddNewTaskCommand) {
openNewTaskActivity(event.taskType.toLowerCase(Locale.US))
}
//endregion Events
override fun onDestroyView() {
super.onDestroyView()
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
when (requestCode) {
TASK_CREATED_RESULT -> {
this.displayingTaskForm = false
onTaskCreatedResult(resultCode, data)
}
TASK_UPDATED_RESULT -> this.displayingTaskForm = false
}
floatingMenu?.close(true)
}
private fun onTaskCreatedResult(resultCode: Int, data: Intent?) {
if (resultCode == Activity.RESULT_OK) {
val taskType = data?.getStringExtra(TaskFormActivity.TASK_TYPE_KEY)
if (taskType != null) {
switchToTaskTab(taskType)
}
}
}
private fun switchToTaskTab(taskType: String) {
val index = indexForTaskType(taskType)
if (viewPager != null && index != -1) {
viewPager?.currentItem = index
updateBottomBarBadges()
}
}
private fun indexForTaskType(taskType: String?): Int {
if (taskType != null) {
for (index in 0 until (viewFragmentsDictionary?.size ?: 0)) {
val fragment = viewFragmentsDictionary?.get(index)
if (fragment != null && taskType == fragment.className) {
return index
}
}
}
return -1
}
override fun getDisplayedClassName(): String? = null
override fun customTitle(): String? = null
override fun addToBackStack(): Boolean = false
companion object {
private val TASK_CREATED_RESULT = 1
private val TASK_UPDATED_RESULT = 2
}
}

View file

@ -92,6 +92,7 @@ public abstract class BaseTaskViewHolder extends RecyclerView.ViewHolder impleme
public void bindHolder(Task newTask, int position) {
this.task = newTask;
itemView.setBackgroundResource(R.color.white);
if (this.canContainMarkdown()) {
if (this.task.getParsedText() != null) {
this.titleTextView.setText(this.task.getParsedText());
@ -120,7 +121,7 @@ public abstract class BaseTaskViewHolder extends RecyclerView.ViewHolder impleme
this.titleTextView.setText(this.task.getText());
this.notesTextView.setText(this.task.getNotes());
}
if (this.task.getNotes() != null && this.task.getNotes().length() > 0) {
if (this.task.getNotes().length() > 0) {
this.notesTextView.setVisibility(View.VISIBLE);
} else {
this.notesTextView.setVisibility(View.GONE);