diff --git a/Habitica/AndroidManifest.xml b/Habitica/AndroidManifest.xml index 8f4587413..52758b2ef 100644 --- a/Habitica/AndroidManifest.xml +++ b/Habitica/AndroidManifest.xml @@ -141,6 +141,17 @@ android:name="android.support.PARENT_ACTIVITY" android:value=".ui.activities.MainActivity" /> + + + + + + diff --git a/Habitica/res/drawable/challenge_gem_add_button.xml b/Habitica/res/drawable/challenge_gem_add_button.xml new file mode 100644 index 000000000..bc8d2cca9 --- /dev/null +++ b/Habitica/res/drawable/challenge_gem_add_button.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/Habitica/res/drawable/challenge_gem_add_button_disabled.xml b/Habitica/res/drawable/challenge_gem_add_button_disabled.xml new file mode 100644 index 000000000..5cee4da70 --- /dev/null +++ b/Habitica/res/drawable/challenge_gem_add_button_disabled.xml @@ -0,0 +1,8 @@ + + + + + + \ No newline at end of file diff --git a/Habitica/res/drawable/challenge_gem_add_button_enabled.xml b/Habitica/res/drawable/challenge_gem_add_button_enabled.xml new file mode 100644 index 000000000..0ece5a328 --- /dev/null +++ b/Habitica/res/drawable/challenge_gem_add_button_enabled.xml @@ -0,0 +1,8 @@ + + + + + + \ No newline at end of file diff --git a/Habitica/res/drawable/challenge_gem_remove_button.xml b/Habitica/res/drawable/challenge_gem_remove_button.xml new file mode 100644 index 000000000..6b0b2d3e5 --- /dev/null +++ b/Habitica/res/drawable/challenge_gem_remove_button.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/Habitica/res/drawable/challenge_gem_remove_button_disabled.xml b/Habitica/res/drawable/challenge_gem_remove_button_disabled.xml new file mode 100644 index 000000000..c8356c480 --- /dev/null +++ b/Habitica/res/drawable/challenge_gem_remove_button_disabled.xml @@ -0,0 +1,8 @@ + + + + + + \ No newline at end of file diff --git a/Habitica/res/drawable/challenge_gem_remove_button_enabled.xml b/Habitica/res/drawable/challenge_gem_remove_button_enabled.xml new file mode 100644 index 000000000..7bbf3519b --- /dev/null +++ b/Habitica/res/drawable/challenge_gem_remove_button_enabled.xml @@ -0,0 +1,8 @@ + + + + + + \ No newline at end of file diff --git a/Habitica/res/drawable/ic_close_white_24dp.xml b/Habitica/res/drawable/ic_close_white_24dp.xml new file mode 100644 index 000000000..4881cf5be --- /dev/null +++ b/Habitica/res/drawable/ic_close_white_24dp.xml @@ -0,0 +1,4 @@ + + + + diff --git a/Habitica/res/drawable/ic_info_outline_black_24dp.xml b/Habitica/res/drawable/ic_info_outline_black_24dp.xml new file mode 100644 index 000000000..cb30cd7c3 --- /dev/null +++ b/Habitica/res/drawable/ic_info_outline_black_24dp.xml @@ -0,0 +1,4 @@ + + + + diff --git a/Habitica/res/layout/activity_create_challenge.xml b/Habitica/res/layout/activity_create_challenge.xml new file mode 100644 index 000000000..ab0246ab7 --- /dev/null +++ b/Habitica/res/layout/activity_create_challenge.xml @@ -0,0 +1,220 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Habitica/res/layout/fragment_challengeslist.xml b/Habitica/res/layout/fragment_challengeslist.xml index 7819e15a7..a8a5e2b65 100644 --- a/Habitica/res/layout/fragment_challengeslist.xml +++ b/Habitica/res/layout/fragment_challengeslist.xml @@ -1,4 +1,4 @@ - @@ -54,62 +55,65 @@ android:scrollbars="vertical" android:paddingBottom="?attr/actionBarSize" /> - - + + + android:layout_height="wrap_content" + android:layout_marginLeft="48dp" + android:layout_marginRight="48dp" + android:layout_marginTop="65dp" + android:orientation="vertical"> - + android:layout_height="match_parent" + android:gravity="center" + android:maxLines="2" + android:text="@string/not_part_of_a_challenge" + android:textAlignment="center" + android:textColor="#8a000000" /> - + - + - + - - - + - \ No newline at end of file + + + \ No newline at end of file diff --git a/Habitica/res/menu/menu_challenge_details.xml b/Habitica/res/menu/menu_challenge_details.xml index 44057f439..d3751094d 100644 --- a/Habitica/res/menu/menu_challenge_details.xml +++ b/Habitica/res/menu/menu_challenge_details.xml @@ -5,9 +5,15 @@ + app:showAsAction="always" /> + + + diff --git a/Habitica/res/menu/menu_create_challenge.xml b/Habitica/res/menu/menu_create_challenge.xml new file mode 100644 index 000000000..6475f0388 --- /dev/null +++ b/Habitica/res/menu/menu_create_challenge.xml @@ -0,0 +1,11 @@ + + + + + diff --git a/Habitica/res/menu/menu_list_challenges.xml b/Habitica/res/menu/menu_list_challenges.xml new file mode 100644 index 000000000..1b553db6a --- /dev/null +++ b/Habitica/res/menu/menu_list_challenges.xml @@ -0,0 +1,10 @@ + + + + + diff --git a/Habitica/res/values/strings.xml b/Habitica/res/values/strings.xml index b6c4fee45..8cb3a614f 100644 --- a/Habitica/res/values/strings.xml +++ b/Habitica/res/values/strings.xml @@ -571,4 +571,18 @@ With this option set, the Dailies tasks will default to ‘due’ instead of ‘all’ Your device does not have any of the supported payment methods. Please use the habitica website if you want to purchase gems. Your device does not have any of the supported payment methods. Please use the habitica website if you want to purchase a subscription. + Save + Location + Gem reward + Tasks + Create challenge + Edit Challenge + You need at least 1 gem to create a challenge in Tavern. + You don\'t have enough gems to create a challenge. + Identify your challenge with a tag .. + You need a tag to create this Challenge. + You need to add at least one task to create this Challenge. + You need a title to create this Challenge. + Description (optional) + New challenge title diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/api/ApiService.java b/Habitica/src/main/java/com/habitrpg/android/habitica/api/ApiService.java index e2e6abf35..9d9fb79d7 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/api/ApiService.java +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/api/ApiService.java @@ -287,6 +287,22 @@ public interface ApiService { @POST("challenges/{challengeId}/leave") Observable> leaveChallenge(@Path("challengeId") String challengeId, @Body LeaveChallengeBody body); + @POST("challenges") + Observable> createChallenge(@Body Challenge challenge); + + @POST("tasks/challenge/{challengeId}") + Observable> createChallengeTask(@Path("challengeId") String challengeId, @Body Task task); + + @POST("tasks/challenge/{challengeId}") + Observable>> createChallengeTasks(@Path("challengeId") String challengeId, @Body List tasks); + + @PUT("challenges/{challengeId}") + Observable> updateChallenge(@Path("challengeId") String challengeId, @Body Challenge challenge); + + @DELETE("challenges/{challengeId}") + Observable> deleteChallenge(@Path("challengeId") String challengeId); + + //DEBUG: These calls only work on a local development server @POST("debug/add-ten-gems") diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/components/AppComponent.java b/Habitica/src/main/java/com/habitrpg/android/habitica/components/AppComponent.java index 6f5087c1f..e57e9d9e9 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/components/AppComponent.java +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/components/AppComponent.java @@ -19,6 +19,7 @@ import com.habitrpg.android.habitica.receivers.LocalNotificationActionReceiver; import com.habitrpg.android.habitica.ui.activities.AboutActivity; import com.habitrpg.android.habitica.ui.activities.ChallengeDetailActivity; import com.habitrpg.android.habitica.ui.activities.ClassSelectionActivity; +import com.habitrpg.android.habitica.ui.activities.CreateChallengeActivity; import com.habitrpg.android.habitica.ui.activities.FullProfileActivity; import com.habitrpg.android.habitica.ui.activities.GemPurchaseActivity; import com.habitrpg.android.habitica.ui.activities.GroupFormActivity; @@ -33,6 +34,7 @@ import com.habitrpg.android.habitica.ui.activities.SetupActivity; import com.habitrpg.android.habitica.ui.activities.SkillMemberActivity; import com.habitrpg.android.habitica.ui.activities.SkillTasksActivity; import com.habitrpg.android.habitica.ui.activities.TaskFormActivity; +import com.habitrpg.android.habitica.ui.adapter.social.challenges.ChallengeTasksRecyclerViewAdapter; 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; @@ -231,7 +233,7 @@ public interface AppComponent { void inject(SubscriptionFragment subscriptionFragment); - void inject(ChallengeTasksRecyclerViewFragment.ChallengeTasksRecyclerViewAdapter challengeTasksRecyclerViewAdapter); + void inject(ChallengeTasksRecyclerViewAdapter challengeTasksRecyclerViewAdapter); void inject(ChallengeTasksRecyclerViewFragment challengeTasksRecyclerViewFragment); @@ -248,4 +250,6 @@ public interface AppComponent { void inject(HabiticaFirebaseInstanceIDService habiticaFirebaseInstanceIDService); void inject(HabiticaFirebaseMessagingService habiticaFirebaseMessagingService); + + void inject(CreateChallengeActivity createChallengeActivity); } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/data/ApiClient.java b/Habitica/src/main/java/com/habitrpg/android/habitica/data/ApiClient.java index 8fcfa3d6d..a3814d417 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/data/ApiClient.java +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/data/ApiClient.java @@ -200,6 +200,13 @@ public interface ApiClient { Observable leaveChallenge(String challengeId, LeaveChallengeBody body); + Observable createChallenge(Challenge challenge); + + Observable createChallengeTask(String challengeId, Task task); + Observable> createChallengeTasks(String challengeId, List tasks); + Observable updateChallenge(Challenge challenge); + Observable deleteChallenge(String challengeId); + //DEBUG: These calls only work on a local development server Observable debugAddTenGems(); diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/data/ChallengeRepository.java b/Habitica/src/main/java/com/habitrpg/android/habitica/data/ChallengeRepository.java new file mode 100644 index 000000000..e46148601 --- /dev/null +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/data/ChallengeRepository.java @@ -0,0 +1,34 @@ +package com.habitrpg.android.habitica.data; + +import com.habitrpg.android.habitica.models.social.Challenge; +import com.habitrpg.android.habitica.models.social.Group; +import com.habitrpg.android.habitica.models.tasks.Task; +import com.habitrpg.android.habitica.models.tasks.TaskList; + +import java.util.List; + +import rx.Observable; + +public interface ChallengeRepository extends BaseRepository { + Observable getChallenge(String challengeId); + Observable getChallengeTasks(String challengeId); + + Observable createChallenge(Challenge challenge, List taskList); + + /** + * + * @param challenge + * @param fullTaskList lists all tasks of the current challenge, to create the taskOrders + * @param addedTaskList only the tasks to be added online + * @param updatedTaskList only the updated ones + * @param removedTaskList tasks that has be to be removed + * @return + */ + Observable updateChallenge(Challenge challenge, List fullTaskList, + List addedTaskList, List updatedTaskList, List removedTaskList); + Observable deleteChallenge(String challengeId); + + void setUsersGroups(List groups); + + Observable> getLocalGroups(); +} diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/ApiClientImpl.java b/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/ApiClientImpl.java index 6c36fb390..f5b55a893 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/ApiClientImpl.java +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/ApiClientImpl.java @@ -65,7 +65,7 @@ import com.habitrpg.android.habitica.models.tasks.Task; import com.habitrpg.android.habitica.models.tasks.TaskList; import com.habitrpg.android.habitica.models.tasks.TaskTag; import com.habitrpg.android.habitica.utils.BooleanAsIntAdapter; -import com.habitrpg.android.habitica.utils.ChallengeDeserializer; +import com.habitrpg.android.habitica.utils.ChallengeSerializer; import com.habitrpg.android.habitica.utils.ChatMessageDeserializer; import com.habitrpg.android.habitica.utils.ChecklistItemSerializer; import com.habitrpg.android.habitica.utils.ContentDeserializer; @@ -267,7 +267,7 @@ public class ApiClientImpl implements Action1, ApiClient { .registerTypeAdapter(Task.class, new TaskSerializer()) .registerTypeAdapter(ContentResult.class, new ContentDeserializer()) .registerTypeAdapter(FeedResponse.class, new FeedResponseDeserializer()) - .registerTypeAdapter(Challenge.class, new ChallengeDeserializer()) + .registerTypeAdapter(Challenge.class, new ChallengeSerializer()) .setDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'") .create(); return GsonConverterFactory.create(gson); @@ -826,6 +826,32 @@ public class ApiClientImpl implements Action1, ApiClient { return apiService.leaveChallenge(challengeId, body).compose(configureApiCallObserver()); } + @Override + public Observable createChallenge(Challenge challenge) { + return apiService.createChallenge(challenge).compose(configureApiCallObserver()); + } + + + @Override + public Observable createChallengeTask(String challengeId, Task task) { + return apiService.createChallengeTask(challengeId, task).compose(configureApiCallObserver()); + } + + @Override + public Observable> createChallengeTasks(String challengeId, List tasks) { + return apiService.createChallengeTasks(challengeId, tasks).compose(configureApiCallObserver()); + } + + @Override + public Observable updateChallenge(Challenge challenge) { + return apiService.updateChallenge(challenge.id, challenge).compose(configureApiCallObserver()); + } + + @Override + public Observable deleteChallenge(String challengeId) { + return apiService.deleteChallenge(challengeId).compose(configureApiCallObserver()); + } + @Override public Observable debugAddTenGems() { return apiService.debugAddTenGems().compose(configureApiCallObserver()); diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/ChallengeRepositoryImpl.java b/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/ChallengeRepositoryImpl.java new file mode 100644 index 000000000..83d364c03 --- /dev/null +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/ChallengeRepositoryImpl.java @@ -0,0 +1,121 @@ +package com.habitrpg.android.habitica.data.implementation; + +import com.github.underscore.$; +import com.habitrpg.android.habitica.data.ApiClient; +import com.habitrpg.android.habitica.data.ChallengeRepository; +import com.habitrpg.android.habitica.data.local.ChallengeLocalRepository; +import com.habitrpg.android.habitica.models.social.Challenge; +import com.habitrpg.android.habitica.models.social.Group; +import com.habitrpg.android.habitica.models.tasks.Task; +import com.habitrpg.android.habitica.models.tasks.TaskList; +import com.habitrpg.android.habitica.models.tasks.TasksOrder; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import rx.Observable; +import rx.schedulers.Schedulers; + + +public class ChallengeRepositoryImpl extends BaseRepositoryImpl implements ChallengeRepository { + + public ChallengeRepositoryImpl(ChallengeLocalRepository localRepository, ApiClient apiClient) { + super(localRepository, apiClient); + } + + @Override + public Observable getChallenge(String challengeId) { + return apiClient.getChallenge(challengeId); + } + + @Override + public Observable getChallengeTasks(String challengeId) { + return apiClient.getChallengeTasks(challengeId); + } + + + private TasksOrder getTaskOrders(List taskList) { + Map> stringListMap = $.groupBy(taskList, t -> t.getType()); + + TasksOrder tasksOrder = new TasksOrder(); + + for (Map.Entry> entry : stringListMap.entrySet()) { + List taskIdList = $.map(entry.getValue(), t -> t.getId()); + + switch (entry.getKey()) { + case Task.TYPE_HABIT: + tasksOrder.setHabits(taskIdList); + break; + case Task.TYPE_DAILY: + tasksOrder.setDailys(taskIdList); + break; + case Task.TYPE_TODO: + tasksOrder.setTodos(taskIdList); + break; + case Task.TYPE_REWARD: + tasksOrder.setRewards(taskIdList); + break; + } + } + + return tasksOrder; + } + + private Observable addChallengeTasks(String challengeId, List addedTaskList) { + if (addedTaskList.size() == 1) { + return apiClient.createChallengeTask(challengeId, addedTaskList.get(0)); + } else { + return apiClient.createChallengeTasks(challengeId, addedTaskList); + } + } + + @Override + public Observable createChallenge(Challenge challenge, List taskList) { + challenge.tasksOrder = getTaskOrders(taskList); + + return Observable.create(subscriber -> { + apiClient.createChallenge(challenge).subscribe(challenge1 -> { + addChallengeTasks(challenge1.id, taskList).subscribe(task -> { + subscriber.onNext(challenge1); + subscriber.onCompleted(); + }, throwable -> subscriber.onError((Throwable) throwable)); + + }, throwable -> subscriber.onError(throwable)); + }); + } + + @Override + public Observable updateChallenge(Challenge challenge, List fullTaskList, + List addedTaskList, List updatedTaskList, List removedTaskList) { + + ArrayList observablesToWait = new ArrayList<>($.map(updatedTaskList, t -> apiClient.updateTask(t.getId(), t))); + observablesToWait.addAll($.map(removedTaskList, apiClient::deleteTask)); + + if (addedTaskList.size() != 0) { + observablesToWait.add(addChallengeTasks(challenge.id, addedTaskList)); + } + + challenge.tasksOrder = getTaskOrders(fullTaskList); + + return Observable.from(observablesToWait) + .flatMap(task -> task.subscribeOn(Schedulers.computation())) + .toList() + .flatMap(tasks -> apiClient.updateChallenge(challenge)); + } + + @Override + public Observable deleteChallenge(String challengeId) { + return apiClient.deleteChallenge(challengeId); + } + + @Override + public void setUsersGroups(List groups) { + localRepository.setUsersGroups(groups); + } + + @Override + public Observable> getLocalGroups() { + return localRepository.getGroups(); + } +} diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/ChallengeLocalRepository.java b/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/ChallengeLocalRepository.java new file mode 100644 index 000000000..42c3e530f --- /dev/null +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/ChallengeLocalRepository.java @@ -0,0 +1,19 @@ +package com.habitrpg.android.habitica.data.local; + + +import com.habitrpg.android.habitica.models.social.Challenge; +import com.habitrpg.android.habitica.models.social.Group; +import com.habitrpg.android.habitica.models.tasks.Task; + +import java.util.List; + +import rx.Observable; + +public interface ChallengeLocalRepository extends BaseLocalRepository { + Observable getChallenge(String id); + Observable> getTasks(Challenge challenge); + + void setUsersGroups(List group); + + Observable> getGroups(); +} diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/implementation/DbFlowChallengeLocalRepository.java b/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/implementation/DbFlowChallengeLocalRepository.java new file mode 100644 index 000000000..1b346e444 --- /dev/null +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/implementation/DbFlowChallengeLocalRepository.java @@ -0,0 +1,51 @@ +package com.habitrpg.android.habitica.data.local.implementation; + +import com.habitrpg.android.habitica.data.local.ChallengeLocalRepository; +import com.habitrpg.android.habitica.models.social.Challenge; +import com.habitrpg.android.habitica.models.social.Group; +import com.habitrpg.android.habitica.models.tasks.Task; +import com.raizlabs.android.dbflow.runtime.transaction.BaseTransaction; +import com.raizlabs.android.dbflow.runtime.transaction.TransactionListener; +import com.raizlabs.android.dbflow.sql.builder.Condition; +import com.raizlabs.android.dbflow.sql.language.Delete; +import com.raizlabs.android.dbflow.sql.language.Select; + +import java.util.List; + +import rx.Observable; + +public class DbFlowChallengeLocalRepository implements ChallengeLocalRepository { + + @Override + public Observable getChallenge(String id) { + return Observable.defer(() -> Observable.just(new Select().from(Challenge.class).where(Condition.column("id").is(id)).querySingle())); + } + + @Override + public Observable> getTasks(Challenge challenge) { + return null; + } + + @Override + public void setUsersGroups(List groups) { + + new Delete().from(Group.class).query(); + + + for (Group group : groups) { + group.isMember = true; + group.async().save(); + } + } + + @Override + public Observable> getGroups() { + return Observable.defer(() -> Observable.just(new Select().from(Group.class).where(Condition.column("isMember").is(true)).queryList())); + } + + + @Override + public void close() { + + } +} diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/events/TaskSaveEvent.java b/Habitica/src/main/java/com/habitrpg/android/habitica/events/TaskSaveEvent.java index 1de08eed9..e96a5e617 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/events/TaskSaveEvent.java +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/events/TaskSaveEvent.java @@ -8,5 +8,5 @@ import com.habitrpg.android.habitica.models.tasks.Task; public class TaskSaveEvent { public Task task; public boolean created; - + public boolean ignoreEvent; } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/events/commands/DeleteTaskCommand.java b/Habitica/src/main/java/com/habitrpg/android/habitica/events/commands/DeleteTaskCommand.java index 451abb810..f280d5525 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/events/commands/DeleteTaskCommand.java +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/events/commands/DeleteTaskCommand.java @@ -5,8 +5,14 @@ package com.habitrpg.android.habitica.events.commands; */ public class DeleteTaskCommand { public String TaskIdToDelete; + public boolean ignoreEvent; public DeleteTaskCommand(String id) { + this(id, false); + } + + public DeleteTaskCommand(String id, boolean ignore) { TaskIdToDelete = id; + ignoreEvent = ignore; } } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/models/shops/ShopItem.java b/Habitica/src/main/java/com/habitrpg/android/habitica/models/shops/ShopItem.java index df51c4908..455bc2886 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/models/shops/ShopItem.java +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/models/shops/ShopItem.java @@ -134,7 +134,7 @@ public class ShopItem { if (getCurrency().equals("gold")) { return getValue() <= user.getStats().getGp(); } else if (getCurrency().equals("gems")) { - return getValue() <= (user.getBalance() * 4); + return getValue() <= (user.getGemCount()); } else { return false; } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/models/social/Challenge.java b/Habitica/src/main/java/com/habitrpg/android/habitica/models/social/Challenge.java index 71f373348..0ce22ce2a 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/models/social/Challenge.java +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/models/social/Challenge.java @@ -1,7 +1,7 @@ package com.habitrpg.android.habitica.models.social; import com.habitrpg.android.habitica.HabitDatabase; -import com.habitrpg.android.habitica.models.social.Group; +import com.habitrpg.android.habitica.models.tasks.TasksOrder; import com.habitrpg.android.habitica.models.user.HabitRPGUser; import com.raizlabs.android.dbflow.annotation.Column; import com.raizlabs.android.dbflow.annotation.NotNull; @@ -9,8 +9,6 @@ import com.raizlabs.android.dbflow.annotation.PrimaryKey; import com.raizlabs.android.dbflow.annotation.Table; import com.raizlabs.android.dbflow.structure.BaseModel; -import java.util.HashMap; - @Table(databaseName = HabitDatabase.NAME) public class Challenge extends BaseModel { @@ -73,25 +71,5 @@ public class Challenge extends BaseModel { public HabitRPGUser leader; - public HashMap getTasksOrder() { - HashMap map = new HashMap(); - - if (!dailyList.isEmpty()) { - map.put(TASK_ORDER_DAILYS, dailyList.split(",")); - } - - if (!habitList.isEmpty()) { - map.put(TASK_ORDER_HABITS, habitList.split(",")); - } - - if (!rewardList.isEmpty()) { - map.put(TASK_ORDER_REWARDS, rewardList.split(",")); - } - - if (!todoList.isEmpty()) { - map.put(TASK_ORDER_TODOS, todoList.split(",")); - } - - return map; - } + public TasksOrder tasksOrder; } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/models/social/Group.java b/Habitica/src/main/java/com/habitrpg/android/habitica/models/social/Group.java index dccc4d78e..5b1dec96c 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/models/social/Group.java +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/models/social/Group.java @@ -4,51 +4,66 @@ import com.google.gson.annotations.SerializedName; import com.habitrpg.android.habitica.models.user.HabitRPGUser; import com.habitrpg.android.habitica.models.inventory.Quest; +import com.habitrpg.android.habitica.HabitDatabase; +import com.raizlabs.android.dbflow.annotation.Column; +import com.raizlabs.android.dbflow.annotation.NotNull; +import com.raizlabs.android.dbflow.annotation.PrimaryKey; +import com.raizlabs.android.dbflow.annotation.Table; import com.raizlabs.android.dbflow.structure.BaseModel; import java.util.List; -/** - * Created by Negue on 16.09.2015. - */ +@Table(databaseName = HabitDatabase.NAME, tableName = "_group") public class Group extends BaseModel { + @Column + @PrimaryKey + @NotNull @SerializedName("_id") public String id; + @Column public double balance; + @Column public String description; + @Column public String leaderID; + @Column public String leaderName; + @Column public String name; + @Column public int memberCount; + @Column public Boolean isMember; + @Column public String type; + @Column public String logo; public Quest quest; public String privacy; + public List chat; public List members; + @Column public int challengeCount; + @Column public String leaderMessage; - // TODO Challenges - - @Override public boolean equals(Object o) { if (this == o) { diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/models/tasks/Days.java b/Habitica/src/main/java/com/habitrpg/android/habitica/models/tasks/Days.java index 823803a21..94721d3fc 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/models/tasks/Days.java +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/models/tasks/Days.java @@ -1,5 +1,8 @@ package com.habitrpg.android.habitica.models.tasks; +import android.os.Parcel; +import android.os.Parcelable; + import com.habitrpg.android.habitica.HabitDatabase; import com.raizlabs.android.dbflow.annotation.Column; import com.raizlabs.android.dbflow.annotation.ModelContainer; @@ -13,7 +16,7 @@ import com.raizlabs.android.dbflow.structure.BaseModel; */ @ModelContainer @Table(databaseName = HabitDatabase.NAME) -public class Days extends BaseModel { +public class Days extends BaseModel implements Parcelable { @Column @PrimaryKey @@ -108,4 +111,44 @@ public class Days extends BaseModel { } return false; } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(this.task_id); + dest.writeByte(this.m ? (byte) 1 : (byte) 0); + dest.writeByte(this.t ? (byte) 1 : (byte) 0); + dest.writeByte(this.w ? (byte) 1 : (byte) 0); + dest.writeByte(this.th ? (byte) 1 : (byte) 0); + dest.writeByte(this.f ? (byte) 1 : (byte) 0); + dest.writeByte(this.s ? (byte) 1 : (byte) 0); + dest.writeByte(this.su ? (byte) 1 : (byte) 0); + } + + protected Days(Parcel in) { + this.task_id = in.readString(); + this.m = in.readByte() != 0; + this.t = in.readByte() != 0; + this.w = in.readByte() != 0; + this.th = in.readByte() != 0; + this.f = in.readByte() != 0; + this.s = in.readByte() != 0; + this.su = in.readByte() != 0; + } + + public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { + @Override + public Days createFromParcel(Parcel source) { + return new Days(source); + } + + @Override + public Days[] newArray(int size) { + return new Days[size]; + } + }; } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/models/tasks/Task.java b/Habitica/src/main/java/com/habitrpg/android/habitica/models/tasks/Task.java index 4324e6f80..a4632744b 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/models/tasks/Task.java +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/models/tasks/Task.java @@ -20,6 +20,8 @@ import com.raizlabs.android.dbflow.structure.BaseModel; import org.greenrobot.eventbus.EventBus; +import android.os.Parcel; +import android.os.Parcelable; import android.support.annotation.Nullable; import java.util.ArrayList; @@ -34,7 +36,7 @@ import java.util.concurrent.TimeUnit; */ @ModelContainer @Table(databaseName = HabitDatabase.NAME) -public class Task extends BaseModel { +public class Task extends BaseModel implements Parcelable { public static final String TYPE_HABIT = "habit"; public static final String TYPE_TODO = "todo"; public static final String TYPE_DAILY = "daily"; @@ -686,4 +688,83 @@ public class Task extends BaseModel { this.parsedNotes = this.getNotes(); } } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(this.user_id); + dest.writeValue(this.priority); + dest.writeString(this.text); + dest.writeString(this.notes); + dest.writeString(this.attribute); + dest.writeString(this.type); + dest.writeDouble(this.value); + dest.writeList(this.tags); + dest.writeLong(this.dateCreated != null ? this.dateCreated.getTime() : -1); + dest.writeInt(this.position); + dest.writeValue(this.up); + dest.writeValue(this.down); + dest.writeByte(this.completed ? (byte) 1 : (byte) 0); + dest.writeList(this.checklist); + dest.writeList(this.reminders); + dest.writeString(this.frequency); + dest.writeValue(this.everyX); + dest.writeValue(this.streak); + dest.writeLong(this.startDate != null ? this.startDate.getTime() : -1); + dest.writeParcelable(this.repeat, flags); + dest.writeLong(this.duedate != null ? this.duedate.getTime() : -1); + dest.writeString(this.specialTag); + dest.writeString(this.id); + } + + public Task() { + } + + protected Task(Parcel in) { + this.user_id = in.readString(); + this.priority = (Float) in.readValue(Float.class.getClassLoader()); + this.text = in.readString(); + this.notes = in.readString(); + this.attribute = in.readString(); + this.type = in.readString(); + this.value = in.readDouble(); + this.tags = new ArrayList(); + in.readList(this.tags, TaskTag.class.getClassLoader()); + long tmpDateCreated = in.readLong(); + this.dateCreated = tmpDateCreated == -1 ? null : new Date(tmpDateCreated); + this.position = in.readInt(); + this.up = (Boolean) in.readValue(Boolean.class.getClassLoader()); + this.down = (Boolean) in.readValue(Boolean.class.getClassLoader()); + this.completed = in.readByte() != 0; + this.checklist = new ArrayList(); + in.readList(this.checklist, ChecklistItem.class.getClassLoader()); + this.reminders = new ArrayList(); + in.readList(this.reminders, RemindersItem.class.getClassLoader()); + this.frequency = in.readString(); + this.everyX = (Integer) in.readValue(Integer.class.getClassLoader()); + this.streak = (Integer) in.readValue(Integer.class.getClassLoader()); + long tmpStartDate = in.readLong(); + this.startDate = tmpStartDate == -1 ? null : new Date(tmpStartDate); + this.repeat = in.readParcelable(Days.class.getClassLoader()); + long tmpDuedate = in.readLong(); + this.duedate = tmpDuedate == -1 ? null : new Date(tmpDuedate); + this.specialTag = in.readString(); + this.id = in.readString(); + } + + public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { + @Override + public Task createFromParcel(Parcel source) { + return new Task(source); + } + + @Override + public Task[] newArray(int size) { + return new Task[size]; + } + }; } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/models/user/HabitRPGUser.java b/Habitica/src/main/java/com/habitrpg/android/habitica/models/user/HabitRPGUser.java index c694b66c4..243ddef38 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/models/user/HabitRPGUser.java +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/models/user/HabitRPGUser.java @@ -220,6 +220,10 @@ public class HabitRPGUser extends BaseModel { return this.balance; } + public int getGemCount(){ + return (int)(this.balance * 4); + } + public void setBalance(double balance) { this.balance = balance; } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/modules/RepositoryModule.java b/Habitica/src/main/java/com/habitrpg/android/habitica/modules/RepositoryModule.java index 0444ddf7a..e3edcb54e 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/modules/RepositoryModule.java +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/modules/RepositoryModule.java @@ -1,17 +1,21 @@ package com.habitrpg.android.habitica.modules; +import com.habitrpg.android.habitica.data.ChallengeRepository; import com.habitrpg.android.habitica.data.SetupCustomizationRepository; import com.habitrpg.android.habitica.data.TagRepository; import com.habitrpg.android.habitica.data.TaskRepository; import com.habitrpg.android.habitica.data.UserRepository; +import com.habitrpg.android.habitica.data.implementation.ChallengeRepositoryImpl; import com.habitrpg.android.habitica.data.implementation.SetupCustomizationRepositoryImpl; import com.habitrpg.android.habitica.data.implementation.TagRepositoryImpl; import com.habitrpg.android.habitica.data.implementation.TaskRepositoryImpl; import com.habitrpg.android.habitica.data.implementation.UserRepositoryImpl; +import com.habitrpg.android.habitica.data.local.ChallengeLocalRepository; import com.habitrpg.android.habitica.data.local.TagLocalRepository; import com.habitrpg.android.habitica.data.local.TaskLocalRepository; import com.habitrpg.android.habitica.data.local.UserLocalRepository; +import com.habitrpg.android.habitica.data.local.implementation.DbFlowChallengeLocalRepository; import com.habitrpg.android.habitica.data.local.implementation.DbFlowTaskLocalRepository; import com.habitrpg.android.habitica.data.local.implementation.DbFlowTagLocalRepository; import com.habitrpg.android.habitica.data.ApiClient; @@ -33,6 +37,12 @@ public class RepositoryModule { } + @Provides + @Singleton + ChallengeLocalRepository provideChallengeLocalRepository(){ + return new DbFlowChallengeLocalRepository(); + } + @Provides TaskLocalRepository providesTaskLocalRepository() { return new DbFlowTaskLocalRepository(); @@ -44,6 +54,13 @@ public class RepositoryModule { return new TaskRepositoryImpl(localRepository, apiClient); } + @Provides + @Singleton + ChallengeRepository providesChallengeRepository(ChallengeLocalRepository localRepository, ApiClient apiClient) { + return new ChallengeRepositoryImpl(localRepository, apiClient); + } + + @Provides TagLocalRepository providesTagLocalRepository() { return new DbFlowTagLocalRepository(); diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/AvatarWithBarsViewModel.java b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/AvatarWithBarsViewModel.java index 7d15c9ef5..5ba3e0a47 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/AvatarWithBarsViewModel.java +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/AvatarWithBarsViewModel.java @@ -153,8 +153,8 @@ public class AvatarWithBarsViewModel implements View.OnClickListener { goldText.setText(String.valueOf(gp)); silverText.setText(String.valueOf(sp)); - Double gems = user.getBalance() * 4; - gemsText.setText(String.valueOf(gems.intValue())); + int gems = user.getGemCount(); + gemsText.setText(String.valueOf(gems)); } public void setHpBarData(float value, int valueMax) { @@ -186,9 +186,9 @@ public class AvatarWithBarsViewModel implements View.OnClickListener { @Subscribe public void onEvent(BoughtGemsEvent gemsEvent) { - Double gems = userObject.getBalance() * 4; + int gems = userObject.getGemCount(); gems += gemsEvent.NewGemsToAdd; - gemsText.setText(String.valueOf(gems.intValue())); + gemsText.setText(String.valueOf(gems)); } @Override diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/ChallengeDetailActivity.java b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/ChallengeDetailActivity.java index edd5e833a..486cd9363 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/ChallengeDetailActivity.java +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/ChallengeDetailActivity.java @@ -34,11 +34,13 @@ import org.greenrobot.eventbus.EventBus; import org.greenrobot.eventbus.Subscribe; import android.app.AlertDialog; +import android.content.Intent; import android.databinding.ObservableArrayList; import android.databinding.ObservableList; import android.os.Bundle; import android.support.design.widget.TabLayout; import android.support.v4.app.FragmentTransaction; +import android.support.v7.app.ActionBar; import android.support.v7.widget.RecyclerView; import android.support.v7.widget.Toolbar; import android.view.Menu; @@ -103,6 +105,7 @@ public class ChallengeDetailActivity extends BaseActivity { private Challenge challenge; + @Override protected int getLayoutResId() { return R.layout.activity_challenge_detail; @@ -112,6 +115,11 @@ public class ChallengeDetailActivity extends BaseActivity { public boolean onCreateOptionsMenu(Menu menu) { MenuInflater inflater = getMenuInflater(); inflater.inflate(R.menu.menu_challenge_details, menu); + + if(!challenge.leaderId.equals(HabiticaApplication.User.getId())){ + menu.setGroupVisible(R.id.challenge_edit_action_group, false); + } + return true; } @@ -225,6 +233,7 @@ public class ChallengeDetailActivity extends BaseActivity { ChallengeViewHolder challengeViewHolder = new ChallengeViewHolder(findViewById(R.id.challenge_header)); challengeViewHolder.bind(challenge); + } @Override @@ -240,11 +249,23 @@ public class ChallengeDetailActivity extends BaseActivity { return true; + case R.id.action_edit: + openChallengeEditActivity(); + return true; + default: return super.onOptionsItemSelected(item); } } + private void openChallengeEditActivity(){ + Intent intent = new Intent(this, CreateChallengeActivity.class); + intent.putExtra(CreateChallengeActivity.CHALLENGE_ID_KEY, challenge.id); + + startActivity(intent); + + } + private void showChallengeLeaveDialog(){ new AlertDialog.Builder(this) .setTitle(this.getString(R.string.challenge_leave_title)) diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/CreateChallengeActivity.java b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/CreateChallengeActivity.java new file mode 100644 index 000000000..99da457b2 --- /dev/null +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/CreateChallengeActivity.java @@ -0,0 +1,581 @@ +package com.habitrpg.android.habitica.ui.activities; + +import android.app.ProgressDialog; +import android.content.Context; +import android.content.Intent; +import android.content.res.Resources; +import android.graphics.drawable.ColorDrawable; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.design.widget.TextInputLayout; +import android.support.v7.app.ActionBar; +import android.support.v7.app.AlertDialog; +import android.support.v7.widget.AppCompatCheckedTextView; +import android.support.v7.widget.AppCompatTextView; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewGroup; +import android.widget.AdapterView; +import android.widget.ArrayAdapter; +import android.widget.Button; +import android.widget.EditText; +import android.widget.Spinner; +import android.widget.TextView; + +import com.github.underscore.$; +import com.habitrpg.android.habitica.HabiticaApplication; +import com.habitrpg.android.habitica.R; +import com.habitrpg.android.habitica.components.AppComponent; +import com.habitrpg.android.habitica.data.ChallengeRepository; +import com.habitrpg.android.habitica.events.TaskSaveEvent; +import com.habitrpg.android.habitica.events.TaskTappedEvent; +import com.habitrpg.android.habitica.events.commands.DeleteTaskCommand; +import com.habitrpg.android.habitica.models.social.Challenge; +import com.habitrpg.android.habitica.models.social.Group; +import com.habitrpg.android.habitica.models.tasks.Task; +import com.habitrpg.android.habitica.ui.adapter.social.challenges.ChallengeTasksRecyclerViewAdapter; + +import org.greenrobot.eventbus.EventBus; +import org.greenrobot.eventbus.Subscribe; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.UUID; + +import javax.inject.Inject; + +import butterknife.BindView; +import butterknife.OnClick; +import rx.Observable; + +public class CreateChallengeActivity extends BaseActivity { + public static final String CHALLENGE_ID_KEY = "challengeId"; + + @BindView(R.id.create_challenge_title_input_layout) + TextInputLayout createChallengeTitleInputLayout; + + @BindView(R.id.create_challenge_title) + EditText createChallengeTitle; + + @BindView(R.id.create_challenge_description_input_layout) + TextInputLayout createChallengeDescriptionInputLayout; + + @BindView(R.id.create_challenge_description) + EditText createChallengeDescription; + + @BindView(R.id.create_challenge_prize) + EditText createChallengePrize; + + + @BindView(R.id.create_challenge_tag_input_layout) + TextInputLayout createChallengeTagInputLayout; + + @BindView(R.id.create_challenge_tag) + EditText createChallengeTag; + + @BindView(R.id.create_challenge_gem_error) + TextView createChallengeGemError; + + @BindView(R.id.create_challenge_task_error) + TextView createChallengeTaskError; + + @BindView(R.id.challenge_location_spinner) + Spinner challengeLocationSpinner; + + @BindView(R.id.challenge_add_gem_btn) + Button challengeAddGemBtn; + + @BindView(R.id.challenge_remove_gem_btn) + Button challengeRemoveGemBtn; + + @BindView(R.id.create_challenge_task_list) + RecyclerView createChallengeTaskList; + + @Inject + ChallengeRepository challengeRepository; + + private ChallengeTasksRecyclerViewAdapter challengeTasks; + + private GroupArrayAdapter locationAdapter; + private String challengeId; + private boolean editMode; + + private HashMap addedTasks = new HashMap<>(); + private HashMap updatedTasks = new HashMap<>(); + private HashMap removedTasks = new HashMap<>(); + + // Add {*} Items + Task addHabit; + Task addDaily; + Task addTodo; + Task addReward; + + @Override + protected int getLayoutResId() { + return R.layout.activity_create_challenge; + } + + @Override + protected void injectActivity(AppComponent component) { + component.inject(this); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + MenuInflater inflater = getMenuInflater(); + inflater.inflate(R.menu.menu_create_challenge, menu); + return true; + } + + private boolean savingInProgress = false; + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + if (item.getItemId() == R.id.action_save && !savingInProgress && validateAllFields()) { + savingInProgress = true; + ProgressDialog dialog = ProgressDialog.show(this, "", "Saving challenge data. Please wait...", true, false); + + Observable observable; + + if (editMode) { + observable = updateChallenge(); + } else { + observable = createChallenge(); + } + + observable.subscribe(challenge -> { + dialog.dismiss(); + savingInProgress = false; + finish(); + }, throwable -> { + dialog.dismiss(); + savingInProgress = false; + }); + } else if(item.getItemId() == android.R.id.home){ + finish(); + + return true; + } + + return super.onOptionsItemSelected(item); + } + + private boolean validateAllFields() { + ArrayList errorMessages = new ArrayList<>(); + + if (getEditTextString(createChallengeTitle).isEmpty()) { + String titleEmptyError = getString(R.string.challenge_create_error_title); + createChallengeTitleInputLayout.setError(titleEmptyError); + errorMessages.add(titleEmptyError); + } else { + createChallengeTitleInputLayout.setErrorEnabled(false); + } + + if (getEditTextString(createChallengeTag).isEmpty()) { + String tagEmptyError = getString(R.string.challenge_create_error_tag); + + createChallengeTagInputLayout.setError(tagEmptyError); + errorMessages.add(tagEmptyError); + } else { + createChallengeTagInputLayout.setErrorEnabled(false); + } + + String prizeError = checkPrizeAndMinimumForTavern(); + + if(!prizeError.isEmpty()){ + errorMessages.add(prizeError); + } + + // all "Add {*}"-Buttons are one task itself, so we need atleast more than 4 + if (challengeTasks.getTaskList().size() <= 4) { + createChallengeTaskError.setVisibility(View.VISIBLE); + errorMessages.add(getString(R.string.challenge_create_error_no_tasks)); + } else { + createChallengeTaskError.setVisibility(View.GONE); + } + + AlertDialog.Builder builder = new AlertDialog.Builder(this) + .setMessage($.join(errorMessages, "\n")); + + AlertDialog alert = builder.create(); + alert.show(); + + return errorMessages.size() == 0; + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + Intent intent = getIntent(); + Bundle bundle = intent.getExtras(); + + if (bundle != null) { + challengeId = bundle.getString(CHALLENGE_ID_KEY, null); + } + + EventBus.getDefault().register(this); + + fillControls(); + + if (challengeId != null) { + fillControlsByChallenge(); + } + } + + + @Override + public void onDestroy() { + EventBus.getDefault().unregister(this); + super.onDestroy(); + } + + @Subscribe + public void onEvent(DeleteTaskCommand deleteTask) { + String taskIdToDelete = deleteTask.TaskIdToDelete; + challengeTasks.removeTask(taskIdToDelete); + + if (editMode) { + if (addedTasks.containsKey(taskIdToDelete)) { + addedTasks.remove(taskIdToDelete); + } else { + removedTasks.put(taskIdToDelete, null); + + if (updatedTasks.containsKey(taskIdToDelete)) { + updatedTasks.remove(taskIdToDelete); + } + } + } + } + + @Subscribe + public void onEvent(TaskTappedEvent tappedEvent) { + openNewTaskActivity(null, tappedEvent.Task); + } + + @Subscribe + public void onEvent(TaskSaveEvent saveEvent) { + + if (saveEvent.task.getId() == null) { + saveEvent.task.setId(UUID.randomUUID().toString()); + } + + addOrUpdateTaskInList(saveEvent.task); + } + + @OnClick(R.id.challenge_add_gem_btn) + public void onAddGem() { + int currentVal = Integer.parseInt(createChallengePrize.getText().toString()); + currentVal++; + + createChallengePrize.setText("" + currentVal); + + checkPrizeAndMinimumForTavern(); + } + + @OnClick(R.id.challenge_remove_gem_btn) + public void onRemoveGem() { + + int currentVal = Integer.parseInt(createChallengePrize.getText().toString()); + currentVal--; + + createChallengePrize.setText("" + currentVal); + + checkPrizeAndMinimumForTavern(); + } + + private String checkPrizeAndMinimumForTavern() { + String errorResult = ""; + + String inputValue = createChallengePrize.getText().toString(); + + if (inputValue.isEmpty()) { + inputValue = "0"; + } + + int currentVal = Integer.parseInt(inputValue); + + // 0 is Tavern + int selectedLocation = challengeLocationSpinner.getSelectedItemPosition(); + + double gemCount = HabiticaApplication.User.getGemCount(); + + if (selectedLocation == 0 && currentVal == 0) { + createChallengeGemError.setVisibility(View.VISIBLE); + String error = getString(R.string.challenge_create_error_tavern_one_gem); + createChallengeGemError.setText(error); + errorResult = error; + } else if (currentVal > gemCount) { + createChallengeGemError.setVisibility(View.VISIBLE); + String error = getString(R.string.challenge_create_error_enough_gems); + createChallengeGemError.setText(error); + errorResult = error; + } else { + createChallengeGemError.setVisibility(View.GONE); + } + + challengeRemoveGemBtn.setEnabled(currentVal != 0); + + return errorResult; + } + + private void fillControls() { + Resources resources = getResources(); + + ActionBar supportActionBar = getSupportActionBar(); + if (supportActionBar != null) { + supportActionBar.setDisplayShowHomeEnabled(true); + supportActionBar.setHomeAsUpIndicator(R.drawable.ic_close_white_24dp); + + supportActionBar.setTitle(""); + supportActionBar.setBackgroundDrawable(new ColorDrawable(resources.getColor(R.color.brand_200))); + supportActionBar.setElevation(0); + } + + locationAdapter = new GroupArrayAdapter(this); + locationAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); + challengeRepository.getLocalGroups().subscribe(groups -> { + Group tavern = new Group(); + tavern.id = "00000000-0000-4000-A000-000000000000"; + tavern.name = getString(R.string.sidebar_tavern); + + locationAdapter.add(tavern); + + groups.forEach(group -> locationAdapter.add(group)); + }, Throwable::printStackTrace); + + challengeLocationSpinner.setAdapter(locationAdapter); + challengeLocationSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { + @Override + public void onItemSelected(AdapterView adapterView, View view, int i, long l) { + checkPrizeAndMinimumForTavern(); + } + + @Override + public void onNothingSelected(AdapterView adapterView) { + + } + }); + createChallengePrize.setOnKeyListener((view, i, keyEvent) -> { + checkPrizeAndMinimumForTavern(); + + return false; + }); + + addHabit = createTask(ChallengeTasksRecyclerViewAdapter.TASK_TYPE_ADD_ITEM, resources.getString(R.string.add_habit)); + addDaily = createTask(ChallengeTasksRecyclerViewAdapter.TASK_TYPE_ADD_ITEM, resources.getString(R.string.add_daily)); + addTodo = createTask(ChallengeTasksRecyclerViewAdapter.TASK_TYPE_ADD_ITEM, resources.getString(R.string.add_todo)); + addReward = createTask(ChallengeTasksRecyclerViewAdapter.TASK_TYPE_ADD_ITEM, resources.getString(R.string.add_reward)); + + + ArrayList taskList = new ArrayList<>(); + taskList.add(addHabit); + taskList.add(addDaily); + taskList.add(addTodo); + taskList.add(addReward); + + challengeTasks = new ChallengeTasksRecyclerViewAdapter(null, 0, this, "", null, false, true); + challengeTasks.setTasks(taskList); + challengeTasks.addItemObservable().subscribe(t -> { + if (t.equals(addHabit)) { + openNewTaskActivity(Task.TYPE_HABIT, null); + } else if (t.equals(addDaily)) { + openNewTaskActivity(Task.TYPE_DAILY, null); + } else if (t.equals(addTodo)) { + openNewTaskActivity(Task.TYPE_TODO, null); + } else if (t.equals(addReward)) { + openNewTaskActivity(Task.TYPE_REWARD, null); + } + }); + + createChallengeTaskList.addOnItemTouchListener(new RecyclerView.SimpleOnItemTouchListener() { + @Override + public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) { + // Stop only scrolling. + return rv.getScrollState() == RecyclerView.SCROLL_STATE_DRAGGING; + } + }); + createChallengeTaskList.setAdapter(challengeTasks); + createChallengeTaskList.setLayoutManager(new LinearLayoutManager(this)); + } + + private static Task createTask(String taskType, String taskName) { + Task t = new Task(); + + t.setId(UUID.randomUUID().toString()); + t.setType(taskType); + t.setText(taskName); + + if (taskType.equals(Task.TYPE_HABIT)) { + t.setUp(true); + t.setDown(false); + } + + return t; + } + + private void fillControlsByChallenge() { + challengeRepository.getChallenge(challengeId).subscribe(challenge -> { + + createChallengeTitle.setText(challenge.name); + createChallengeDescription.setText(challenge.description); + createChallengeTag.setText(challenge.shortName); + createChallengePrize.setText(challenge.prize + ""); + + for (int i = 0; i < locationAdapter.getCount(); i++) { + Group group = locationAdapter.getItem(i); + + if (group.id == challenge.groupId) { + challengeLocationSpinner.setSelection(i); + break; + } + } + + checkPrizeAndMinimumForTavern(); + + challengeRepository.getChallengeTasks(challengeId).subscribe(tasks -> { + tasks.tasks.forEach((s, task) -> addOrUpdateTaskInList(task)); + }, Throwable::printStackTrace, () -> { + // activate editMode to track taskChanges + editMode = true; + }); + }); + } + + private void openNewTaskActivity(String type, Task task) { + Bundle bundle = new Bundle(); + + if (task == null) { + bundle.putString(TaskFormActivity.TASK_TYPE_KEY, type); + } else { + bundle.putParcelable(TaskFormActivity.PARCELABLE_TASK, task); + } + + bundle.putBoolean(TaskFormActivity.SAVE_TO_DB, false); + bundle.putBoolean(TaskFormActivity.SET_IGNORE_FLAG, true); + bundle.putBoolean(TaskFormActivity.SHOW_TAG_SELECTION, false); + bundle.putBoolean(TaskFormActivity.SHOW_CHECKLIST, false); + + if (HabiticaApplication.User != null && HabiticaApplication.User.getPreferences() != null) { + String allocationMode = HabiticaApplication.User.getPreferences().getAllocationMode(); + + bundle.putString(TaskFormActivity.USER_ID_KEY, HabiticaApplication.User.getId()); + bundle.putString(TaskFormActivity.ALLOCATION_MODE_KEY, allocationMode); + } + + Intent intent = new Intent(this, TaskFormActivity.class); + intent.putExtras(bundle); + + startActivityForResult(intent, 1); + } + + private Challenge getChallengeData() { + Challenge c = new Challenge(); + + int locationPos = challengeLocationSpinner.getSelectedItemPosition(); + Group locationGroup = locationAdapter.getItem(locationPos); + + if (challengeId != null) { + c.id = challengeId; + } + + c.groupId = locationGroup.id; + c.name = createChallengeTitle.getText().toString(); + c.description = createChallengeDescription.getText().toString(); + c.shortName = createChallengeTag.getText().toString(); + c.prize = Integer.parseInt(createChallengePrize.getText().toString()); + + return c; + } + + private Observable createChallenge() { + Challenge c = getChallengeData(); + + List taskList = challengeTasks.getTaskList(); + taskList.remove(addHabit); + taskList.remove(addDaily); + taskList.remove(addTodo); + taskList.remove(addReward); + + return challengeRepository.createChallenge(c, taskList); + } + + private Observable updateChallenge() { + Challenge c = getChallengeData(); + + List taskList = challengeTasks.getTaskList(); + taskList.remove(addHabit); + taskList.remove(addDaily); + taskList.remove(addTodo); + taskList.remove(addReward); + + return challengeRepository.updateChallenge(c, taskList, new ArrayList<>(addedTasks.values()), + new ArrayList<>(updatedTasks.values()), + new ArrayList<>(removedTasks.keySet()) + ); + } + + private void addOrUpdateTaskInList(Task task) { + if (!challengeTasks.replaceTask(task)) { + Task taskAbove; + + switch (task.getType()) { + case Task.TYPE_HABIT: + taskAbove = addHabit; + break; + case Task.TYPE_DAILY: + taskAbove = addDaily; + break; + case Task.TYPE_TODO: + taskAbove = addTodo; + break; + default: + taskAbove = addReward; + break; + } + + challengeTasks.addTaskUnder(task, taskAbove); + + if (editMode) { + addedTasks.put(task.getId(), task); + } + } else { + // don't need to add the task to updatedTasks if its already been added right now + if (editMode && !addedTasks.containsKey(task.getId())) { + updatedTasks.put(task.getId(), task); + } + } + } + + private String getEditTextString(EditText editText) { + return editText.getText().toString(); + } + + private class GroupArrayAdapter extends ArrayAdapter { + public GroupArrayAdapter(@NonNull Context context) { + super(context, android.R.layout.simple_spinner_item); + } + + @NonNull + @Override + public View getView(int position, View convertView, ViewGroup parent) { + AppCompatTextView checkedTextView = (AppCompatTextView) super.getView(position, convertView, parent); + checkedTextView.setText(getItem(position).name); + return checkedTextView; + } + + @Override + public View getDropDownView(int position, @Nullable View convertView, @NonNull ViewGroup parent) { + AppCompatCheckedTextView checkedTextView = (AppCompatCheckedTextView) super.getDropDownView(position, convertView, parent); + checkedTextView.setText(getItem(position).name); + return checkedTextView; + } + } +} diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/MainActivity.java b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/MainActivity.java index a507cfd4e..5ae518fa6 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/MainActivity.java +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/MainActivity.java @@ -850,6 +850,10 @@ public class MainActivity extends BaseActivity implements Action1, Ha @Subscribe public void onEvent(final DeleteTaskCommand cmd) { + if(cmd.ignoreEvent) { + return; + } + taskRepository.deleteTask(cmd.TaskIdToDelete) .subscribe(aVoid -> EventBus.getDefault().post(new TaskRemovedEvent(cmd.TaskIdToDelete)), throwable -> {}); } @@ -1210,6 +1214,9 @@ public class MainActivity extends BaseActivity implements Action1, Ha @Subscribe public void onEvent(final TaskSaveEvent event) { + if(event.ignoreEvent) + return; + Task task = event.task; if (event.created) { this.taskRepository.createTask(task).subscribe(task1 -> {}, throwable -> {}); diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/TaskFormActivity.java b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/TaskFormActivity.java index 3fa63ec30..f7c6b0e59 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/TaskFormActivity.java +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/TaskFormActivity.java @@ -86,7 +86,14 @@ public class TaskFormActivity extends BaseActivity implements AdapterView.OnItem public static final String TASK_ID_KEY = "taskId"; public static final String USER_ID_KEY = "userId"; public static final String TASK_TYPE_KEY = "type"; + public static final String SHOW_TAG_SELECTION = "show_tag_selection"; + public static final String SHOW_CHECKLIST = "show_checklist"; + public static final String PARCELABLE_TASK = "parcelable_task"; public static final String ALLOCATION_MODE_KEY = "allocationModeKey"; + public static final String SAVE_TO_DB = "saveToDb"; + + // in order to disable the event handler in MainActivity + public static final String SET_IGNORE_FLAG = "ignoreFlag"; @BindView(R.id.task_value_edittext) EditText taskValue; @@ -200,6 +207,9 @@ public class TaskFormActivity extends BaseActivity implements AdapterView.OnItem private String taskType; private String taskId; private String userId; + private boolean showTagSelection; + private boolean showChecklist; + private boolean setIgnoreFlag; private Task task; private String allocationMode; private List weekdayCheckboxes = new ArrayList<>(); @@ -212,6 +222,7 @@ public class TaskFormActivity extends BaseActivity implements AdapterView.OnItem private RemindersManager remindersManager; private FirstDayOfTheWeekHelper firstDayOfTheWeekHelper; + private boolean saveToDb; @Override protected int getLayoutResId() { @@ -224,10 +235,23 @@ public class TaskFormActivity extends BaseActivity implements AdapterView.OnItem Intent intent = getIntent(); Bundle bundle = intent.getExtras(); + taskType = bundle.getString(TASK_TYPE_KEY); taskId = bundle.getString(TASK_ID_KEY); userId = bundle.getString(USER_ID_KEY); + showTagSelection = bundle.getBoolean(SHOW_TAG_SELECTION, true); + showChecklist = bundle.getBoolean(SHOW_CHECKLIST, true); allocationMode = bundle.getString(ALLOCATION_MODE_KEY); + saveToDb = bundle.getBoolean(SAVE_TO_DB, true); + setIgnoreFlag = bundle.getBoolean(SET_IGNORE_FLAG, false); + + tagsWrapper.setVisibility(showTagSelection ? View.VISIBLE : View.GONE); + + if(bundle.containsKey(PARCELABLE_TASK)){ + task = bundle.getParcelable(PARCELABLE_TASK); + taskType = task.type; + } + tagCheckBoxList = new ArrayList<>(); selectedTags = new ArrayList<>(); if (taskType == null) { @@ -251,7 +275,13 @@ public class TaskFormActivity extends BaseActivity implements AdapterView.OnItem finish(); dismissKeyboard(); - EventBus.getDefault().post(new DeleteTaskCommand(taskId)); + String taskToDelete = this.taskId; + + if(taskToDelete == null && task != null){ + taskToDelete = task.getId(); + } + + EventBus.getDefault().post(new DeleteTaskCommand(taskToDelete, setIgnoreFlag)); }).setNegativeButton(getString(R.string.no), (dialog, which) -> { dialog.dismiss(); }).show()); @@ -320,6 +350,10 @@ public class TaskFormActivity extends BaseActivity implements AdapterView.OnItem attributeWrapper.setVisibility(View.GONE); } + if(!showChecklist){ + mainWrapper.removeView(checklistWrapper); + } + if (taskId != null) { Task task = new Select().from(Task.class).byIds(taskId).querySingle(); this.task = task; @@ -329,6 +363,11 @@ public class TaskFormActivity extends BaseActivity implements AdapterView.OnItem setTitle(task); + btnDelete.setEnabled(true); + } else if(task != null) { + populate(task); + taskText.requestFocus(); + btnDelete.setEnabled(true); } else { setTitle((Task) null); @@ -349,7 +388,10 @@ public class TaskFormActivity extends BaseActivity implements AdapterView.OnItem // If it's a to-do, change the emojiToggle2 to the actual emojiToggle2 (prevents NPEs when not a to-do task) if (isTodo) { emojiToggle2 = (ImageButton) findViewById(R.id.emoji_toggle_btn2); - } else { + } + + // if showChecklist is inactive the wrapper is wrapper, so the reference can't be found + if(emojiToggle2 == null) { emojiToggle2 = emojiToggle0; } @@ -870,12 +912,16 @@ public class TaskFormActivity extends BaseActivity implements AdapterView.OnItem } //save this.task.setTags(taskTags); - this.task.save(); + if(saveToDb){ + this.task.save(); + } + //send back to other elements. TaskSaveEvent event = new TaskSaveEvent(); if (TaskFormActivity.this.task.getId() == null) { event.created = true; } + event.ignoreEvent = setIgnoreFlag; event.task = TaskFormActivity.this.task; EventBus.getDefault().post(event); diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/social/ChallengesListViewAdapter.java b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/social/ChallengesListViewAdapter.java index e517f61bf..8c12c231a 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/social/ChallengesListViewAdapter.java +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/social/ChallengesListViewAdapter.java @@ -1,18 +1,5 @@ package com.habitrpg.android.habitica.ui.adapter.social; -import com.github.underscore.$; -import com.habitrpg.android.habitica.R; -import com.habitrpg.android.habitica.events.commands.ShowChallengeDetailActivityCommand; -import com.habitrpg.android.habitica.events.commands.ShowChallengeDetailDialogCommand; -import com.habitrpg.android.habitica.ui.fragments.social.challenges.ChallengeFilterOptions; -import com.habitrpg.android.habitica.models.social.Challenge; -import com.habitrpg.android.habitica.models.user.HabitRPGUser; - -import net.pherth.android.emoji_library.EmojiParser; -import net.pherth.android.emoji_library.EmojiTextView; - -import org.greenrobot.eventbus.EventBus; - import android.content.Context; import android.support.annotation.Nullable; import android.support.v4.content.ContextCompat; @@ -23,19 +10,28 @@ import android.view.ViewGroup; import android.widget.LinearLayout; import android.widget.TextView; +import com.github.underscore.$; +import com.habitrpg.android.habitica.R; +import com.habitrpg.android.habitica.events.commands.ShowChallengeDetailActivityCommand; +import com.habitrpg.android.habitica.events.commands.ShowChallengeDetailDialogCommand; +import com.habitrpg.android.habitica.models.social.Challenge; +import com.habitrpg.android.habitica.models.user.HabitRPGUser; +import com.habitrpg.android.habitica.ui.fragments.social.challenges.ChallengeFilterOptions; + +import net.pherth.android.emoji_library.EmojiParser; +import net.pherth.android.emoji_library.EmojiTextView; + +import org.greenrobot.eventbus.EventBus; + import java.util.ArrayList; import java.util.List; -import java.util.Objects; import butterknife.BindView; import butterknife.ButterKnife; import static com.raizlabs.android.dbflow.config.FlowManager.getContext; - public class ChallengesListViewAdapter extends RecyclerView.Adapter { - - private List challenges = new ArrayList<>(); private List challengesSource = new ArrayList<>(); diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/social/challenges/ChallengeTasksRecyclerViewAdapter.java b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/social/challenges/ChallengeTasksRecyclerViewAdapter.java new file mode 100644 index 000000000..fdb7d21ff --- /dev/null +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/social/challenges/ChallengeTasksRecyclerViewAdapter.java @@ -0,0 +1,195 @@ +package com.habitrpg.android.habitica.ui.adapter.social.challenges; + +import android.content.Context; +import android.support.annotation.Nullable; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Button; +import android.widget.TextView; + +import com.github.underscore.$; +import com.habitrpg.android.habitica.R; +import com.habitrpg.android.habitica.components.AppComponent; +import com.habitrpg.android.habitica.helpers.TaskFilterHelper; +import com.habitrpg.android.habitica.models.tasks.Task; +import com.habitrpg.android.habitica.ui.adapter.tasks.SortableTasksRecyclerViewAdapter; +import com.habitrpg.android.habitica.ui.viewHolders.tasks.BaseTaskViewHolder; +import com.habitrpg.android.habitica.ui.viewHolders.tasks.DailyViewHolder; +import com.habitrpg.android.habitica.ui.viewHolders.tasks.HabitViewHolder; +import com.habitrpg.android.habitica.ui.viewHolders.tasks.RewardViewHolder; +import com.habitrpg.android.habitica.ui.viewHolders.tasks.TodoViewHolder; + +import java.util.List; + +import rx.Observable; +import rx.subjects.PublishSubject; + +public class ChallengeTasksRecyclerViewAdapter + extends SortableTasksRecyclerViewAdapter { + public static final String TASK_TYPE_ADD_ITEM = "ADD_ITEM"; + + private static final int TYPE_HEADER = 0; + private static final int TYPE_HABIT = 1; + private static final int TYPE_DAILY = 2; + private static final int TYPE_TODO = 3; + private static final int TYPE_REWARD = 4; + private static final int TYPE_ADD_ITEM = 5; + + private int dailyResetOffset = 0; + private PublishSubject addItemSubject = PublishSubject.create(); + private boolean openTaskDisabled; + private boolean taskActionsDisabled; + + public ChallengeTasksRecyclerViewAdapter(@Nullable TaskFilterHelper taskFilterHelper, int layoutResource, + Context newContext, String userID, @Nullable SortTasksCallback sortCallback, + boolean openTaskDisabled, boolean taskActionsDisabled) { + super("", taskFilterHelper, layoutResource, newContext, userID, sortCallback); + this.openTaskDisabled = openTaskDisabled; + this.taskActionsDisabled = taskActionsDisabled; + } + + public void setDailyResetOffset(int newResetOffset) { + dailyResetOffset = newResetOffset; + } + + @Override + protected void injectThis(AppComponent component) { + component.inject(this); + } + + @Override + public boolean loadFromDatabase() { + return false; + } + + @Override + public int getItemViewType(int position) { + Task task = this.filteredContent.get(position); + + if (task.type.equals(Task.TYPE_HABIT)) + return TYPE_HABIT; + + if (task.type.equals(Task.TYPE_DAILY)) + return TYPE_DAILY; + + if (task.type.equals(Task.TYPE_TODO)) + return TYPE_TODO; + + if (task.type.equals(Task.TYPE_REWARD)) + return TYPE_REWARD; + + if (addItemSubject.hasObservers() && task.type.equals(TASK_TYPE_ADD_ITEM)) + return TYPE_ADD_ITEM; + + return TYPE_HEADER; + } + + public Observable addItemObservable(){ + return addItemSubject; + } + + public int addTaskUnder(Task taskToAdd, Task taskAbove) { + int position = $.findIndex(this.content, t -> t.getId().equals(taskAbove.getId())); + + content.add(position + 1, taskToAdd); + filter(); + + return position; + } + + @Override + public BaseTaskViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + BaseTaskViewHolder viewHolder = null; + + switch (viewType) { + case TYPE_HABIT: + viewHolder = new HabitViewHolder(getContentView(parent, R.layout.habit_item_card)); + break; + case TYPE_DAILY: + viewHolder = new DailyViewHolder(getContentView(parent, R.layout.daily_item_card), dailyResetOffset); + break; + case TYPE_TODO: + viewHolder = new TodoViewHolder(getContentView(parent, R.layout.todo_item_card)); + break; + case TYPE_REWARD: + viewHolder = new RewardViewHolder(getContentView(parent, R.layout.reward_item_card)); + break; + case TYPE_ADD_ITEM: + viewHolder = new AddItemViewHolder(getContentView(parent, R.layout.challenge_add_task_item), addItemSubject); + break; + default: + viewHolder = new DividerViewHolder(getContentView(parent, R.layout.challenge_task_divider)); + break; + } + + viewHolder.setDisabled(openTaskDisabled, taskActionsDisabled); + return viewHolder; + } + + public List getTaskList(){ + return $.map(content, t -> t); + } + + + /** + * @param task + * @return true if task found&updated + */ + public boolean replaceTask(Task task) { + int i; + for (i = 0; i < this.content.size(); ++i) { + if (content.get(i).getId().equals(task.getId())) { + break; + } + } + if (i < content.size()) { + content.set(i, task); + + filter(); + return true; + } + + return false; + } + + public class AddItemViewHolder extends BaseTaskViewHolder { + + private Button addBtn; + private PublishSubject callback; + private Task newTask; + + public AddItemViewHolder(View itemView, PublishSubject callback) { + super(itemView, false); + this.callback = callback; + + addBtn = (Button) itemView.findViewById(R.id.btn_add_task); + addBtn.setClickable(true); + addBtn.setOnClickListener(view -> callback.onNext(newTask)); + context = itemView.getContext(); + } + + @Override + public void bindHolder(Task newTask, int position) { + this.newTask = newTask; + addBtn.setText(newTask.text); + } + } + + private class DividerViewHolder extends BaseTaskViewHolder { + + private TextView divider_name; + + public DividerViewHolder(View itemView) { + super(itemView, false); + + divider_name = (TextView) itemView.findViewById(R.id.divider_name); + + context = itemView.getContext(); + } + + @Override + public void bindHolder(Task newTask, int position) { + divider_name.setText(newTask.text); + } + } +} diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/customization/AvatarCustomizationFragment.java b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/customization/AvatarCustomizationFragment.java index ba56075f7..39ef029bc 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/customization/AvatarCustomizationFragment.java +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/customization/AvatarCustomizationFragment.java @@ -72,7 +72,7 @@ public class AvatarCustomizationFragment extends BaseMainFragment { this.updateActiveCustomization(); this.adapter.userSize = this.user.getPreferences().getSize(); this.adapter.hairColor = this.user.getPreferences().getHair().getColor(); - this.adapter.gemBalance = user.getBalance() * 4; + this.adapter.gemBalance = user.getGemCount(); return view; } @@ -134,7 +134,7 @@ public class AvatarCustomizationFragment extends BaseMainFragment { @Override public void updateUserData(HabitRPGUser user) { super.updateUserData(user); - this.adapter.gemBalance = user.getBalance() * 4; + this.adapter.gemBalance = user.getGemCount(); this.updateActiveCustomization(); if (adapter.getCustomizationList() != null) { List ownedCustomizations = new ArrayList<>(); diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/GuildsOverviewFragment.java b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/GuildsOverviewFragment.java index 556525dff..c6d81fadf 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/GuildsOverviewFragment.java +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/GuildsOverviewFragment.java @@ -2,6 +2,8 @@ package com.habitrpg.android.habitica.ui.fragments.social; import com.habitrpg.android.habitica.R; import com.habitrpg.android.habitica.components.AppComponent; +import com.habitrpg.android.habitica.data.ChallengeRepository; +import com.habitrpg.android.habitica.data.local.ChallengeLocalRepository; import com.habitrpg.android.habitica.ui.fragments.BaseMainFragment; import com.habitrpg.android.habitica.models.social.Group; @@ -18,6 +20,8 @@ import android.widget.TextView; import java.util.ArrayList; import java.util.List; +import javax.inject.Inject; + import butterknife.BindView; import butterknife.ButterKnife; @@ -32,6 +36,9 @@ public class GuildsOverviewFragment extends BaseMainFragment implements View.OnC @BindView(R.id.chat_refresh_layout) SwipeRefreshLayout swipeRefreshLayout; + @Inject + ChallengeRepository challengeRepository; + private List guilds; private ArrayList guildIDs; @@ -77,6 +84,8 @@ public class GuildsOverviewFragment extends BaseMainFragment implements View.OnC if (swipeRefreshLayout != null) { swipeRefreshLayout.setRefreshing(false); } + + challengeRepository.setUsersGroups(groups); }, throwable -> { }); } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/challenges/ChallengeListFragment.java b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/challenges/ChallengeListFragment.java index 59c927ab5..fe831a326 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/challenges/ChallengeListFragment.java +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/challenges/ChallengeListFragment.java @@ -1,17 +1,23 @@ package com.habitrpg.android.habitica.ui.fragments.social.challenges; +import android.content.Intent; import android.os.Bundle; import android.support.v4.widget.SwipeRefreshLayout; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; 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.LinearLayout; import com.habitrpg.android.habitica.R; import com.habitrpg.android.habitica.components.AppComponent; +import com.habitrpg.android.habitica.ui.activities.CreateChallengeActivity; +import com.habitrpg.android.habitica.ui.activities.PartyInviteActivity; import com.habitrpg.android.habitica.ui.adapter.social.ChallengesListViewAdapter; import com.habitrpg.android.habitica.ui.fragments.BaseMainFragment; import com.habitrpg.android.habitica.models.social.Challenge; @@ -50,7 +56,7 @@ public class ChallengeListFragment extends BaseMainFragment implements SwipeRefr private Action0 refreshCallback; private boolean withFilter; - public void setWithFilter(boolean withFilter){ + public void setWithFilter(boolean withFilter) { this.withFilter = withFilter; } @@ -121,12 +127,12 @@ public class ChallengeListFragment extends BaseMainFragment implements SwipeRefr swipeRefreshLayout.setOnRefreshListener(this); swipeRefreshEmptyLayout.setOnRefreshListener(this); - challengeFilterLayout.setVisibility(withFilter?View.VISIBLE:View.GONE); + challengeFilterLayout.setVisibility(withFilter ? View.VISIBLE : View.GONE); challengeFilterLayout.setClickable(true); challengeFilterLayout.setOnClickListener(view -> ChallengeFilterDialogHolder.showDialog(getActivity(), currentChallengesInView, lastFilterOptions, filterOptions -> { - challengeAdapter.setFilterByGroups(filterOptions); - this.lastFilterOptions = filterOptions; - })); + challengeAdapter.setFilterByGroups(filterOptions); + this.lastFilterOptions = filterOptions; + })); recyclerView.setLayoutManager(new LinearLayoutManager(this.activity)); recyclerView.setAdapter(challengeAdapter); @@ -215,4 +221,27 @@ public class ChallengeListFragment extends BaseMainFragment implements SwipeRefr public String customTitle() { return getString(R.string.sidebar_challenges); } + + @Override + public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { + inflater.inflate(R.menu.menu_list_challenges, menu); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + // Handle action bar item clicks here. The action bar will + // automatically handle clicks on the Home/Up button, so long + // as you specify a parent activity in AndroidManifest.xml. + int id = item.getItemId(); + + switch (id) { + case R.id.action_create_challenge: + Intent intent = new Intent(getActivity(), CreateChallengeActivity.class); + startActivity(intent); + + return true; + } + + return super.onOptionsItemSelected(item); + } } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/challenges/ChallengeTasksRecyclerViewFragment.java b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/challenges/ChallengeTasksRecyclerViewFragment.java index 0cc1fbb4e..b9034b656 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/challenges/ChallengeTasksRecyclerViewFragment.java +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/challenges/ChallengeTasksRecyclerViewFragment.java @@ -5,6 +5,7 @@ import com.habitrpg.android.habitica.helpers.TaskFilterHelper; import com.habitrpg.android.habitica.data.ApiClient; import com.habitrpg.android.habitica.R; import com.habitrpg.android.habitica.components.AppComponent; +import com.habitrpg.android.habitica.ui.adapter.social.challenges.ChallengeTasksRecyclerViewAdapter; import com.habitrpg.android.habitica.ui.adapter.tasks.BaseTasksRecyclerViewAdapter; import com.habitrpg.android.habitica.ui.adapter.tasks.SortableTasksRecyclerViewAdapter; import com.habitrpg.android.habitica.ui.fragments.BaseFragment; @@ -86,7 +87,10 @@ public class ChallengeTasksRecyclerViewFragment extends BaseFragment { } public void setInnerAdapter() { - this.recyclerAdapter = new ChallengeTasksRecyclerViewAdapter(null, 0, getContext(), userID, null); + ChallengeTasksRecyclerViewAdapter challengeTasksRecyclerViewAdapter = new ChallengeTasksRecyclerViewAdapter(null, 0, getContext(), userID, null, true, true); + this.recyclerAdapter = challengeTasksRecyclerViewAdapter; + + challengeTasksRecyclerViewAdapter.setDailyResetOffset(user.getPreferences().getDayStart()); if (tasksOnInitialize != null && tasksOnInitialize.size() != 0 && recyclerAdapter != null && recyclerAdapter.getItemCount() == 0) { recyclerAdapter.setTasks(tasksOnInitialize); @@ -137,96 +141,8 @@ public class ChallengeTasksRecyclerViewFragment extends BaseFragment { // region Challenge specific RecyclerViewAdapters - public class ChallengeTasksRecyclerViewAdapter extends SortableTasksRecyclerViewAdapter { - private static final int TYPE_HEADER = 0; - private static final int TYPE_HABIT = 1; - private static final int TYPE_DAILY = 2; - private static final int TYPE_TODO = 3; - private static final int TYPE_REWARD = 4; - private int dailyResetOffset = 0; - public ChallengeTasksRecyclerViewAdapter(@Nullable TaskFilterHelper taskFilterHelper, int layoutResource, Context newContext, String userID, @Nullable SortTasksCallback sortCallback) { - super("", taskFilterHelper, layoutResource, newContext, userID, sortCallback); - - if (user != null) { - dailyResetOffset = user.getPreferences().getDayStart(); - } - } - - @Override - protected void injectThis(AppComponent component) { - component.inject(this); - } - - @Override - public boolean loadFromDatabase() { - return false; - } - - @Override - public int getItemViewType(int position) { - Task task = this.filteredContent.get(position); - - if (task.type.equals(Task.TYPE_HABIT)) - return TYPE_HABIT; - - if (task.type.equals(Task.TYPE_DAILY)) - return TYPE_DAILY; - - if (task.type.equals(Task.TYPE_TODO)) - return TYPE_TODO; - - if (task.type.equals(Task.TYPE_REWARD)) - return TYPE_REWARD; - - return TYPE_HEADER; - } - - @Override - public BaseTaskViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { - BaseTaskViewHolder viewHolder; - - switch (viewType) { - case TYPE_HABIT: - viewHolder = new HabitViewHolder(getContentView(parent, R.layout.habit_item_card)); - break; - case TYPE_DAILY: - viewHolder = new DailyViewHolder(getContentView(parent, R.layout.daily_item_card), dailyResetOffset); - break; - case TYPE_TODO: - viewHolder = new TodoViewHolder(getContentView(parent, R.layout.todo_item_card)); - break; - case TYPE_REWARD: - viewHolder = new RewardViewHolder(getContentView(parent, R.layout.reward_item_card)); - break; - default: - viewHolder = new DividerViewHolder(getContentView(parent, R.layout.challenge_task_divider)); - break; - } - - //viewHolder.setDisabled(true); - return viewHolder; - } - } - - private class DividerViewHolder extends BaseTaskViewHolder { - - private TextView divider_name; - - public DividerViewHolder(View itemView) { - super(itemView, false); - - divider_name = (TextView) itemView.findViewById(R.id.divider_name); - - context = itemView.getContext(); - } - - @Override - public void bindHolder(Task newTask, int position) { - divider_name.setText(newTask.text); - } - } // endregion } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewHolders/tasks/BaseTaskViewHolder.java b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewHolders/tasks/BaseTaskViewHolder.java index 8b7995802..4e467bc5e 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewHolders/tasks/BaseTaskViewHolder.java +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewHolders/tasks/BaseTaskViewHolder.java @@ -63,7 +63,6 @@ public abstract class BaseTaskViewHolder extends RecyclerView.ViewHolder impleme @BindView(R.id.approvalRequiredTextField) TextView approvalRequiredTextView; - boolean disabled; public BaseTaskViewHolder(View itemView) { this(itemView, true); @@ -187,7 +186,7 @@ public abstract class BaseTaskViewHolder extends RecyclerView.ViewHolder impleme @Override public void onClick(View v) { - if (v != itemView || isDisabled()) { + if (v != itemView || this.openTaskDisabled) { return; } @@ -201,13 +200,11 @@ public abstract class BaseTaskViewHolder extends RecyclerView.ViewHolder impleme return true; } - public boolean isDisabled() { - return disabled; - } + protected boolean openTaskDisabled, taskActionsDisabled; - public void setDisabled(boolean disabled) { - this.disabled = disabled; + public void setDisabled(boolean openTaskDisabled, boolean taskActionsDisabled) { + this.openTaskDisabled = openTaskDisabled; + this.taskActionsDisabled = taskActionsDisabled; - itemView.setEnabled(!disabled); } } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewHolders/tasks/ChecklistedViewHolder.java b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewHolders/tasks/ChecklistedViewHolder.java index 38877ac49..233216c3f 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewHolders/tasks/ChecklistedViewHolder.java +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewHolders/tasks/ChecklistedViewHolder.java @@ -167,9 +167,9 @@ public abstract class ChecklistedViewHolder extends BaseTaskViewHolder implement } @Override - public void setDisabled(boolean disabled) { - super.setDisabled(disabled); + public void setDisabled(boolean openTaskDisabled, boolean taskActionsDisabled) { + super.setDisabled(openTaskDisabled, taskActionsDisabled); - this.checkbox.setEnabled(!disabled); + this.checkbox.setEnabled(!taskActionsDisabled); } } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewHolders/tasks/HabitViewHolder.java b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewHolders/tasks/HabitViewHolder.java index c3919cf82..e70d64863 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewHolders/tasks/HabitViewHolder.java +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewHolders/tasks/HabitViewHolder.java @@ -88,10 +88,10 @@ public class HabitViewHolder extends BaseTaskViewHolder { } @Override - public void setDisabled(boolean disabled) { - super.setDisabled(disabled); + public void setDisabled(boolean openTaskDisabled, boolean taskActionsDisabled) { + super.setDisabled(openTaskDisabled, taskActionsDisabled); - this.btnPlus.setEnabled(!disabled); - this.btnMinus.setEnabled(!disabled); + this.btnPlus.setEnabled(!taskActionsDisabled); + this.btnMinus.setEnabled(!taskActionsDisabled); } } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewHolders/tasks/RewardViewHolder.java b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewHolders/tasks/RewardViewHolder.java index c37b2d372..268f0f3ac 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewHolders/tasks/RewardViewHolder.java +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewHolders/tasks/RewardViewHolder.java @@ -89,10 +89,10 @@ public class RewardViewHolder extends BaseTaskViewHolder { } @Override - public void setDisabled(boolean disabled) { - super.setDisabled(disabled); + public void setDisabled(boolean openTaskDisabled, boolean taskActionsDisabled) { + super.setDisabled(openTaskDisabled, taskActionsDisabled); - this.rewardButton.setEnabled(!disabled); + this.rewardButton.setEnabled(!taskActionsDisabled); } } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/utils/ChallengeDeserializer.java b/Habitica/src/main/java/com/habitrpg/android/habitica/utils/ChallengeSerializer.java similarity index 73% rename from Habitica/src/main/java/com/habitrpg/android/habitica/utils/ChallengeDeserializer.java rename to Habitica/src/main/java/com/habitrpg/android/habitica/utils/ChallengeSerializer.java index ee5fe6818..ff61a97e1 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/utils/ChallengeDeserializer.java +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/utils/ChallengeSerializer.java @@ -6,13 +6,15 @@ import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.JsonParseException; +import com.google.gson.JsonSerializationContext; +import com.google.gson.JsonSerializer; import com.habitrpg.android.habitica.models.social.Challenge; import android.text.TextUtils; import java.lang.reflect.Type; -public class ChallengeDeserializer implements JsonDeserializer { +public class ChallengeSerializer implements JsonDeserializer, JsonSerializer { @Override public Challenge deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { @@ -43,7 +45,15 @@ public class ChallengeDeserializer implements JsonDeserializer { if (profile != null) { challenge.leaderName = profile.get("name").getAsString(); - challenge.leaderId = leaderObj.get("id").getAsString(); + + JsonElement id = leaderObj.get("id"); + if (id == null) { + id = leaderObj.get("_id"); + } + + if (id != null) { + challenge.leaderId = id.getAsString(); + } } } } @@ -85,4 +95,22 @@ public class ChallengeDeserializer implements JsonDeserializer { return ""; } + + @Override + public JsonElement serialize(Challenge src, Type typeOfSrc, JsonSerializationContext context) { + JsonObject object = new JsonObject(); + object.addProperty("id", src.id); + object.addProperty("name", src.name); + object.addProperty("shortName", src.shortName); + object.addProperty("description", src.description); + object.addProperty("memberCount", src.memberCount); + object.addProperty("prize", src.prize); + object.addProperty("official", src.official); + + object.addProperty("group", src.groupId); + object.add("tasksOrder", context.serialize(src.tasksOrder)); + + + return object; + } } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/widget/AvatarStatsWidgetProvider.java b/Habitica/src/main/java/com/habitrpg/android/habitica/widget/AvatarStatsWidgetProvider.java index 8815e0fbe..f75d94a75 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/widget/AvatarStatsWidgetProvider.java +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/widget/AvatarStatsWidgetProvider.java @@ -122,7 +122,7 @@ public class AvatarStatsWidgetProvider extends BaseWidgetProvider { int sp = (int) ((stats.getGp() - gp) * 100); remoteViews.setTextViewText(R.id.gold_tv, String.valueOf(gp)); remoteViews.setTextViewText(R.id.silver_tv, String.valueOf(sp)); - remoteViews.setTextViewText(R.id.gems_tv, String.valueOf((int) (user.getBalance() * 4))); + remoteViews.setTextViewText(R.id.gems_tv, String.valueOf(user.getGemCount())); remoteViews.setTextViewText(R.id.lvl_tv, context.getString(R.string.user_level, user.getStats().getLvl())); AvatarView avatarView = new AvatarView(context, true, true, true);