Merge pull request #744 from HabitRPG/create_challenges

Create challenges
This commit is contained in:
Phillip Thelen 2017-04-28 11:30:54 +02:00 committed by GitHub
commit 0be358d564
51 changed files with 1829 additions and 224 deletions

View file

@ -141,6 +141,17 @@
android:name="android.support.PARENT_ACTIVITY"
android:value=".ui.activities.MainActivity" />
</activity>
<activity
android:name=".ui.activities.CreateChallengeActivity"
android:theme="@style/AppTheme.ActionBar"
android:parentActivityName=".ui.activities.MainActivity"
android:screenOrientation="portrait"
android:launchMode="singleTask"
tools:ignore="UnusedAttribute">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value=".ui.activities.MainActivity" />
</activity>
<activity android:name="com.facebook.FacebookActivity"
android:configChanges=
"keyboard|keyboardHidden|screenLayout|screenSize|orientation"

View file

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:viewportHeight="24.0" android:viewportWidth="24.0" android:width="24dp">
<path android:fillColor="#FFFFFF" android:pathData="M19,6.41L17.59,5 12,10.59 6.41,5 5,6.41 10.59,12 5,17.59 6.41,19 12,13.41 17.59,19 19,17.59 13.41,12z"/>
</vector>

View file

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@drawable/challenge_gem_add_button_disabled" android:state_enabled="false"></item>
<item android:drawable="@drawable/challenge_gem_add_button_enabled" android:state_enabled="true"></item>
</selector>

View file

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item >
<bitmap
android:src="@drawable/plus"
android:tint="#e1e0e3" />
</item>
</layer-list>

View file

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item >
<bitmap
android:src="@drawable/plus"
android:tint="@color/brand_400" />
</item>
</layer-list>

View file

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@drawable/challenge_gem_remove_button_disabled" android:state_enabled="false"></item>
<item android:drawable="@drawable/challenge_gem_remove_button_enabled" android:state_enabled="true"></item>
</selector>

View file

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item >
<bitmap
android:src="@drawable/minus"
android:tint="#e1e0e3" />
</item>
</layer-list>

View file

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item >
<bitmap
android:src="@drawable/minus"
android:tint="@color/brand_400" />
</item>
</layer-list>

View file

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:viewportHeight="24.0" android:viewportWidth="24.0" android:width="24dp">
<path android:fillColor="#FFFFFF" android:pathData="M19,6.41L17.59,5 12,10.59 6.41,5 5,6.41 10.59,12 5,17.59 6.41,19 12,13.41 17.59,19 19,17.59 13.41,12z"/>
</vector>

View file

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:viewportHeight="24.0" android:viewportWidth="24.0" android:width="24dp">
<path android:fillColor="#000000" android:pathData="M11,17h2v-6h-2v6zM12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,20c-4.41,0 -8,-3.59 -8,-8s3.59,-8 8,-8 8,3.59 8,8 -3.59,8 -8,8zM11,9h2L13,7h-2v2z"/>
</vector>

View file

@ -0,0 +1,220 @@
<?xml version="1.0" encoding="utf-8"?>
<android.support.v4.widget.NestedScrollView 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/activity_create_challenge"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:paddingBottom="@dimen/activity_vertical_margin"
tools:context="com.habitrpg.android.habitica.ui.activities.CreateChallengeActivity">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/brand_200"
android:orientation="vertical"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="22dp">
<android.support.design.widget.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColorHint="@color/brand_500"
app:hintTextAppearance="@style/TextAppearance.AppCompat"
android:id="@+id/create_challenge_title_input_layout">
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="fill_horizontal"
android:hint="@string/new_challenge_title"
android:id="@+id/create_challenge_title"
android:textColor="@color/white"
android:textColorHighlight="@color/brand_500"
android:textColorHint="@color/brand_500"
tools:text="Get in shape" />
</android.support.design.widget.TextInputLayout>
<android.support.design.widget.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColorHint="@color/brand_500"
android:id="@+id/create_challenge_description_input_layout"
app:hintTextAppearance="@style/TextAppearance.AppCompat">
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/description_optional"
android:inputType="textMultiLine"
android:id="@+id/create_challenge_description"
android:maxLines="5"
android:minLines="3"
android:textColor="@color/white"
android:textColorHighlight="@color/brand_500"
android:textColorHint="@color/brand_500" />
</android.support.design.widget.TextInputLayout>
</LinearLayout>
<View
android:layout_width="match_parent"
android:layout_height="2dp"
android:background="@color/brand_700" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin">
<TextView
style="@style/Subheader2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="23dp"
android:text="@string/gem_reward"
android:textColor="#cc000000" />
<TextView
style="@style/Caption3"
android:id="@+id/create_challenge_gem_error"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="23dp"
android:text="@string/gem_reward"
android:textColor="#f74e52"
android:visibility="gone"
tools:visibility="visible"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="28dp">
<Button
android:id="@+id/challenge_add_gem_btn"
style="@style/Body1_Button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="2"
android:drawableLeft="@drawable/challenge_gem_add_button"/>
<ImageView
android:layout_width="18dp"
android:layout_height="18dp"
android:layout_gravity="center"
android:src="@drawable/ic_header_gem" />
<EditText
style="@style/Subheader2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="center"
android:inputType="number"
android:textColor="#8a000000"
android:text="0"
android:id="@+id/create_challenge_prize"
android:textAlignment="center" />
<Button
android:id="@+id/challenge_remove_gem_btn"
style="@style/Body1_Button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="2"
android:drawableRight="@drawable/challenge_gem_remove_button"/>
</LinearLayout>
<TextView
style="@style/Subheader2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="26dp"
android:text="@string/location"
android:textColor="#cc000000" />
<Spinner
android:id="@+id/challenge_location_spinner"
style="@android:style/Widget.Material.Spinner.Underlined"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="#8a000000"
android:layout_marginTop="17dp" />
<TextView
style="@style/Subheader2"
android:layout_width="wrap_content"
android:layout_height="20dp"
android:layout_marginTop="39dp"
android:drawablePadding="12dp"
android:drawableEnd="@drawable/ic_info_outline_black_24dp"
android:drawableTint="@color/brand_300"
android:gravity="center"
android:text="Tag"
android:textColor="#cc000000" />
<android.support.design.widget.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/create_challenge_tag_input_layout"
app:hintTextAppearance="@style/TextAppearance.AppCompat">
<EditText
android:id="@+id/create_challenge_tag"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/identify_your_challenge_with_a_tag"
android:maxLines="1"
android:textColor="@color/white"
android:textColorHint="#61000000" />
</android.support.design.widget.TextInputLayout>
<TextView
style="@style/Subheader2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/tasks" />
<TextView
style="@style/Caption3"
android:id="@+id/create_challenge_task_error"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="23dp"
android:textColor="#f74e52"
android:text="@string/challenge_create_error_no_tasks"
android:visibility="gone"
tools:visibility="visible"/>
</LinearLayout>
<android.support.v7.widget.RecyclerView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:nestedScrollingEnabled="false"
android:id="@+id/create_challenge_task_list">
</android.support.v7.widget.RecyclerView>
</LinearLayout>
</android.support.v4.widget.NestedScrollView>

View file

@ -0,0 +1,28 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#FFF"
android:orientation="vertical"
android:gravity="center">
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="22dp"
android:layout_marginBottom="18dp"
android:layout_marginEnd="24dp"
android:layout_marginStart="24dp"
android:textAllCaps="false"
style="@style/Subheader2"
android:textColor="@color/brand_400"
android:background="#f9f9f9"
android:id="@+id/btn_add_task"
tools:text="Add habit">
</Button>
</LinearLayout>

View file

@ -1,4 +1,4 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
@ -42,6 +42,7 @@
android:id="@+id/challenges.refresh.layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="gone"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
@ -54,62 +55,65 @@
android:scrollbars="vertical"
android:paddingBottom="?attr/actionBarSize" />
</android.support.v4.widget.SwipeRefreshLayout>
<android.support.v4.widget.SwipeRefreshLayout
android:id="@+id/challenges_refresh_empty"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<android.support.v4.widget.NestedScrollView
<android.support.v4.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false">
<LinearLayout
android:id="@+id/challenges.list.empty"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false">
android:layout_height="wrap_content"
android:layout_marginLeft="48dp"
android:layout_marginRight="48dp"
android:layout_marginTop="65dp"
android:orientation="vertical">
<LinearLayout
android:id="@+id/challenges.list.empty"
<TextView
style="@style/Headline"
android:layout_width="match_parent"
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" />
<TextView
style="@style/Headline"
android:layout_width="match_parent"
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" />
<TextView
style="@style/Body2"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="22dp"
android:gravity="center"
android:text="@string/join_a_challenge"
android:textAlignment="center"
android:textColor="#66000000" />
<TextView
style="@style/Body2"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="22dp"
android:gravity="center"
android:text="@string/join_a_challenge"
android:textAlignment="center"
android:textColor="#66000000" />
<TextView
android:id="@+id/textView5"
style="@style/Body2"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="22dp"
android:gravity="center"
android:text="@string/check_the_public_challenge_tab"
android:textAlignment="center"
android:textColor="#66000000" />
<TextView
android:id="@+id/textView5"
style="@style/Body2"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="22dp"
android:gravity="center"
android:text="@string/check_the_public_challenge_tab"
android:textAlignment="center"
android:textColor="#66000000" />
</LinearLayout>
</LinearLayout>
</android.support.v4.widget.NestedScrollView>
</android.support.v4.widget.NestedScrollView>
</android.support.v4.widget.SwipeRefreshLayout>
</LinearLayout>
</FrameLayout>

View file

@ -5,9 +5,15 @@
<item
android:id="@+id/action_leave"
android:actionViewClass="android.widget.ImageButton"
android:icon="@drawable/leave_light"
android:title="@string/leave"
app:showAsAction="always"
android:actionViewClass="android.widget.ImageButton"/>
app:showAsAction="always" />
<group
android:id="@+id/challenge_edit_action_group">
<item
android:id="@+id/action_edit"
android:title="@string/edit_challenge" />
</group>
</menu>

View file

@ -0,0 +1,11 @@
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
tools:context="com.habitrpg.android.habitica.TaskActivity">
<item
android:id="@+id/action_save"
android:title="@string/save"
app:showAsAction="always"/>
</menu>

View file

@ -0,0 +1,10 @@
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
tools:context="com.habitrpg.android.habitica.TaskActivity">
<item
android:id="@+id/action_create_challenge"
android:title="@string/create_challenge"/>
</menu>

View file

@ -571,4 +571,18 @@
<string name="dailyDueDefaultViewDescription">With this option set, the Dailies tasks will default to due instead of all</string>
<string name="no_billing_gems">Your device does not have any of the supported payment methods. Please use the habitica website if you want to purchase gems.</string>
<string name="no_billing_subscriptions">Your device does not have any of the supported payment methods. Please use the habitica website if you want to purchase a subscription.</string>
<string name="save">Save</string>
<string name="location">Location</string>
<string name="gem_reward">Gem reward</string>
<string name="tasks">Tasks</string>
<string name="create_challenge">Create challenge</string>
<string name="edit_challenge">Edit Challenge</string>
<string name="challenge_create_error_tavern_one_gem">You need at least 1 gem to create a challenge in Tavern.</string>
<string name="challenge_create_error_enough_gems">You don\'t have enough gems to create a challenge.</string>
<string name="identify_your_challenge_with_a_tag">Identify your challenge with a tag ..</string>
<string name="challenge_create_error_tag">You need a tag to create this Challenge.</string>
<string name="challenge_create_error_no_tasks">You need to add at least one task to create this Challenge.</string>
<string name="challenge_create_error_title">You need a title to create this Challenge.</string>
<string name="description_optional">Description (optional)</string>
<string name="new_challenge_title">New challenge title</string>
</resources>

View file

@ -287,6 +287,22 @@ public interface ApiService {
@POST("challenges/{challengeId}/leave")
Observable<HabitResponse<Void>> leaveChallenge(@Path("challengeId") String challengeId, @Body LeaveChallengeBody body);
@POST("challenges")
Observable<HabitResponse<Challenge>> createChallenge(@Body Challenge challenge);
@POST("tasks/challenge/{challengeId}")
Observable<HabitResponse<Task>> createChallengeTask(@Path("challengeId") String challengeId, @Body Task task);
@POST("tasks/challenge/{challengeId}")
Observable<HabitResponse<List<Task>>> createChallengeTasks(@Path("challengeId") String challengeId, @Body List<Task> tasks);
@PUT("challenges/{challengeId}")
Observable<HabitResponse<Challenge>> updateChallenge(@Path("challengeId") String challengeId, @Body Challenge challenge);
@DELETE("challenges/{challengeId}")
Observable<HabitResponse<Void>> deleteChallenge(@Path("challengeId") String challengeId);
//DEBUG: These calls only work on a local development server
@POST("debug/add-ten-gems")

View file

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

View file

@ -200,6 +200,13 @@ public interface ApiClient {
Observable<Void> leaveChallenge(String challengeId, LeaveChallengeBody body);
Observable<Challenge> createChallenge(Challenge challenge);
Observable<Task> createChallengeTask(String challengeId, Task task);
Observable<List<Task>> createChallengeTasks(String challengeId, List<Task> tasks);
Observable<Challenge> updateChallenge(Challenge challenge);
Observable<Void> deleteChallenge(String challengeId);
//DEBUG: These calls only work on a local development server
Observable<Void> debugAddTenGems();

View file

@ -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<Challenge> getChallenge(String challengeId);
Observable<TaskList> getChallengeTasks(String challengeId);
Observable<Challenge> createChallenge(Challenge challenge, List<Task> 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<Challenge> updateChallenge(Challenge challenge, List<Task> fullTaskList,
List<Task> addedTaskList, List<Task> updatedTaskList, List<String> removedTaskList);
Observable<Void> deleteChallenge(String challengeId);
void setUsersGroups(List<Group> groups);
Observable<List<Group>> getLocalGroups();
}

View file

@ -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<Throwable>, 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<Throwable>, ApiClient {
return apiService.leaveChallenge(challengeId, body).compose(configureApiCallObserver());
}
@Override
public Observable<Challenge> createChallenge(Challenge challenge) {
return apiService.createChallenge(challenge).compose(configureApiCallObserver());
}
@Override
public Observable<Task> createChallengeTask(String challengeId, Task task) {
return apiService.createChallengeTask(challengeId, task).compose(configureApiCallObserver());
}
@Override
public Observable<List<Task>> createChallengeTasks(String challengeId, List<Task> tasks) {
return apiService.createChallengeTasks(challengeId, tasks).compose(configureApiCallObserver());
}
@Override
public Observable<Challenge> updateChallenge(Challenge challenge) {
return apiService.updateChallenge(challenge.id, challenge).compose(configureApiCallObserver());
}
@Override
public Observable<Void> deleteChallenge(String challengeId) {
return apiService.deleteChallenge(challengeId).compose(configureApiCallObserver());
}
@Override
public Observable<Void> debugAddTenGems() {
return apiService.debugAddTenGems().compose(configureApiCallObserver());

View file

@ -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<ChallengeLocalRepository> implements ChallengeRepository {
public ChallengeRepositoryImpl(ChallengeLocalRepository localRepository, ApiClient apiClient) {
super(localRepository, apiClient);
}
@Override
public Observable<Challenge> getChallenge(String challengeId) {
return apiClient.getChallenge(challengeId);
}
@Override
public Observable<TaskList> getChallengeTasks(String challengeId) {
return apiClient.getChallengeTasks(challengeId);
}
private TasksOrder getTaskOrders(List<Task> taskList) {
Map<String, List<Task>> stringListMap = $.groupBy(taskList, t -> t.getType());
TasksOrder tasksOrder = new TasksOrder();
for (Map.Entry<String, List<Task>> entry : stringListMap.entrySet()) {
List<String> 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<Task> addedTaskList) {
if (addedTaskList.size() == 1) {
return apiClient.createChallengeTask(challengeId, addedTaskList.get(0));
} else {
return apiClient.createChallengeTasks(challengeId, addedTaskList);
}
}
@Override
public Observable<Challenge> createChallenge(Challenge challenge, List<Task> 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<Challenge> updateChallenge(Challenge challenge, List<Task> fullTaskList,
List<Task> addedTaskList, List<Task> updatedTaskList, List<String> removedTaskList) {
ArrayList<Observable> 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<Void> deleteChallenge(String challengeId) {
return apiClient.deleteChallenge(challengeId);
}
@Override
public void setUsersGroups(List<Group> groups) {
localRepository.setUsersGroups(groups);
}
@Override
public Observable<List<Group>> getLocalGroups() {
return localRepository.getGroups();
}
}

View file

@ -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<Challenge> getChallenge(String id);
Observable<List<Task>> getTasks(Challenge challenge);
void setUsersGroups(List<Group> group);
Observable<List<Group>> getGroups();
}

View file

@ -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<Challenge> getChallenge(String id) {
return Observable.defer(() -> Observable.just(new Select().from(Challenge.class).where(Condition.column("id").is(id)).querySingle()));
}
@Override
public Observable<List<Task>> getTasks(Challenge challenge) {
return null;
}
@Override
public void setUsersGroups(List<Group> groups) {
new Delete().from(Group.class).query();
for (Group group : groups) {
group.isMember = true;
group.async().save();
}
}
@Override
public Observable<List<Group>> getGroups() {
return Observable.defer(() -> Observable.just(new Select().from(Group.class).where(Condition.column("isMember").is(true)).queryList()));
}
@Override
public void close() {
}
}

View file

@ -8,5 +8,5 @@ import com.habitrpg.android.habitica.models.tasks.Task;
public class TaskSaveEvent {
public Task task;
public boolean created;
public boolean ignoreEvent;
}

View file

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

View file

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

View file

@ -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<String, String[]> getTasksOrder() {
HashMap<String, String[]> 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;
}

View file

@ -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<ChatMessage> chat;
public List<HabitRPGUser> members;
@Column
public int challengeCount;
@Column
public String leaderMessage;
// TODO Challenges
@Override
public boolean equals(Object o) {
if (this == o) {

View file

@ -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<Days> CREATOR = new Parcelable.Creator<Days>() {
@Override
public Days createFromParcel(Parcel source) {
return new Days(source);
}
@Override
public Days[] newArray(int size) {
return new Days[size];
}
};
}

View file

@ -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<TaskTag>();
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<ChecklistItem>();
in.readList(this.checklist, ChecklistItem.class.getClassLoader());
this.reminders = new ArrayList<RemindersItem>();
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<Task> CREATOR = new Parcelable.Creator<Task>() {
@Override
public Task createFromParcel(Parcel source) {
return new Task(source);
}
@Override
public Task[] newArray(int size) {
return new Task[size];
}
};
}

View file

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

View file

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

View file

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

View file

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

View file

@ -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<String, Task> addedTasks = new HashMap<>();
private HashMap<String, Task> updatedTasks = new HashMap<>();
private HashMap<String, Task> 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<Challenge> 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<String> 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<Task> 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<Challenge> createChallenge() {
Challenge c = getChallengeData();
List<Task> taskList = challengeTasks.getTaskList();
taskList.remove(addHabit);
taskList.remove(addDaily);
taskList.remove(addTodo);
taskList.remove(addReward);
return challengeRepository.createChallenge(c, taskList);
}
private Observable<Challenge> updateChallenge() {
Challenge c = getChallengeData();
List<Task> 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<Group> {
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;
}
}
}

View file

@ -850,6 +850,10 @@ public class MainActivity extends BaseActivity implements Action1<Throwable>, 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<Throwable>, 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 -> {});

View file

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

View file

@ -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<ChallengesListViewAdapter.ChallengeViewHolder> {
private List<Challenge> challenges = new ArrayList<>();
private List<Challenge> challengesSource = new ArrayList<>();

View file

@ -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<BaseTaskViewHolder> {
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<Task> 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<Task> 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<Task> 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<Task> callback;
private Task newTask;
public AddItemViewHolder(View itemView, PublishSubject<Task> 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);
}
}
}

View file

@ -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<String> ownedCustomizations = new ArrayList<>();

View file

@ -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<Group> guilds;
private ArrayList<String> guildIDs;
@ -77,6 +84,8 @@ public class GuildsOverviewFragment extends BaseMainFragment implements View.OnC
if (swipeRefreshLayout != null) {
swipeRefreshLayout.setRefreshing(false);
}
challengeRepository.setUsersGroups(groups);
}, throwable -> {
});
}

View file

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

View file

@ -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<BaseTaskViewHolder> {
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
}

View file

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

View file

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

View file

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

View file

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

View file

@ -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<Challenge> {
public class ChallengeSerializer implements JsonDeserializer<Challenge>, JsonSerializer<Challenge> {
@Override
public Challenge deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
@ -43,7 +45,15 @@ public class ChallengeDeserializer implements JsonDeserializer<Challenge> {
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<Challenge> {
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;
}
}

View file

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