Begin implementing new challenge design and layout

This commit is contained in:
Phillip Thelen 2018-07-23 18:03:58 +02:00
parent 5040574bf0
commit 5626b76956
54 changed files with 4538 additions and 1671 deletions

View file

@ -0,0 +1,10 @@
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="@color/white" />
<stroke
android:width="0.5dip"
android:color="#2f000000"/>
</shape>

View file

@ -0,0 +1,6 @@
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<solid android:color="@color/white" />
</shape>

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="@color/white"/>
<corners android:radius="20dip"/>
<padding android:left="0dip" android:top="0dip" android:right="0dip" android:bottom="0dip" />
</shape>

View file

@ -1,30 +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="@drawable/border_1f000000"
android:orientation="horizontal"
android:weightSum="3"
android:baselineAligned="false">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content">
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/border_1f000000_white"
android:orientation="horizontal"
android:weightSum="3"
android:baselineAligned="false"
android:minHeight="60dp">
<FrameLayout
android:id="@+id/lock_icon_background"
android:layout_width="48dp"
android:layout_height="match_parent"
android:layout_marginRight="@dimen/spacing_large"
android:layout_marginTop="0.5dp"
android:layout_marginBottom="0.5dp">
<ImageView
android:layout_width="16dp"
android:layout_height="16dp"
android:layout_marginBottom="18dp"
android:layout_marginEnd="15.5dp"
android:layout_marginLeft="14.5dp"
android:layout_marginRight="15.5dp"
android:layout_marginStart="14.5dp"
android:layout_marginTop="18dp"
android:src="@drawable/daily_checkbox" />
</LinearLayout>
android:id="@+id/lock_icon"
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_gravity="center"
android:scaleType="center"/>
</FrameLayout>
<LinearLayout

View file

@ -4,45 +4,26 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:background="@drawable/border_1f000000"
android:background="@drawable/border_1f000000_white"
android:weightSum="3"
android:baselineAligned="false">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content">
android:baselineAligned="false"
android:minHeight="60dp">
<FrameLayout
android:id="@+id/lock_icon_background_plus"
android:layout_width="48dp"
android:layout_height="match_parent"
android:background="@color/gray_700"
android:layout_marginRight="@dimen/spacing_large"
android:layout_marginTop="0.5dp"
android:layout_marginBottom="0.5dp">
<ImageView
android:id="@+id/plus_img_tinted"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="18dp"
android:layout_marginEnd="15.5dp"
android:layout_marginLeft="14.5dp"
android:layout_marginRight="15.5dp"
android:layout_marginStart="14.5dp"
android:layout_marginTop="18dp"
android:src="@drawable/plus"
android:tint="#4d000000"
android:visibility="gone"
tools:visibility="visible" />
<ImageView
android:id="@+id/plus_img"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="18dp"
android:layout_marginEnd="15.5dp"
android:layout_marginLeft="14.5dp"
android:layout_marginRight="15.5dp"
android:layout_marginStart="14.5dp"
android:layout_marginTop="18dp"
android:src="@drawable/plus"
android:tint="#1f000000"
android:visibility="gone"
tools:visibility="visible" />
</LinearLayout>
android:id="@+id/lock_icon_plus"
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_gravity="center"
android:scaleType="center"/>
</FrameLayout>
<LinearLayout
@ -63,40 +44,20 @@
</LinearLayout>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<FrameLayout
android:id="@+id/lock_icon_background_minus"
android:layout_width="48dp"
android:layout_height="match_parent"
android:background="@color/gray_700"
android:layout_marginLeft="@dimen/spacing_large"
android:layout_marginTop="0.5dp"
android:layout_marginBottom="0.5dp">
<ImageView
android:id="@+id/minus_img_tinted"
android:layout_width="16dp"
android:layout_height="16dp"
android:layout_marginBottom="18dp"
android:layout_marginEnd="15.5dp"
android:layout_marginLeft="14.5dp"
android:layout_marginRight="15.5dp"
android:layout_marginStart="14.5dp"
android:layout_marginTop="18dp"
android:src="@drawable/minus"
android:tint="#4d000000"
android:visibility="gone"
tools:visibility="visible" />
<ImageView
android:id="@+id/minus_img"
android:layout_width="16dp"
android:layout_height="16dp"
android:layout_marginBottom="18dp"
android:layout_marginEnd="15.5dp"
android:layout_marginLeft="14.5dp"
android:layout_marginRight="15.5dp"
android:layout_marginStart="14.5dp"
android:layout_marginTop="18dp"
android:src="@drawable/minus"
android:tint="#1f000000"
android:visibility="gone"
tools:visibility="visible" />
</LinearLayout>
android:id="@+id/lock_icon_minus"
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_gravity="center"
android:scaleType="center"/>
</FrameLayout>
</LinearLayout>

View file

@ -1,12 +1,13 @@
<?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:orientation="horizontal"
android:background="@drawable/border_1f000000"
android:weightSum="3"
android:baselineAligned="false">
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:background="@drawable/border_1f000000_white"
android:weightSum="3"
android:baselineAligned="false"
android:minHeight="60dp">
<ImageView
android:id="@+id/gold_icon"
android:layout_width="16dp"

View file

@ -4,20 +4,35 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:id="@+id/task_group_name"
style="@style/Body1"
android:layout_marginTop="13dp"
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
tools:text="2 Habits" />
android:gravity="center_vertical"
android:layout_marginLeft="@dimen/spacing_large"
android:layout_marginTop="24dp"
android:layout_marginBottom="@dimen/spacing_large"
>
<TextView
android:id="@+id/task_group_name"
style="@style/Body1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
tools:text="Habits"
android:textSize="15sp"/>
<TextView
android:id="@+id/task_count_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/spacing_medium"
style="@style/Pill.White"
android:textColor="@color/gray_400"
tools:text="2"/>
</LinearLayout>
<LinearLayout
android:id="@+id/tasks_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="11dp"
android:background="@drawable/border_1f000000"
android:orientation="vertical">

View file

@ -1,30 +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="@drawable/border_1f000000"
android:orientation="horizontal"
android:weightSum="3"
android:baselineAligned="false">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content">
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/border_1f000000_white"
android:orientation="horizontal"
android:weightSum="3"
android:baselineAligned="false"
android:minHeight="60dp">
<FrameLayout
android:id="@+id/lock_icon_background"
android:layout_width="48dp"
android:layout_height="match_parent"
android:layout_marginRight="@dimen/spacing_large"
android:layout_marginTop="0.5dp"
android:layout_marginBottom="0.5dp">
<ImageView
android:layout_width="16dp"
android:layout_height="16dp"
android:layout_marginBottom="18dp"
android:layout_marginEnd="15.5dp"
android:layout_marginLeft="14.5dp"
android:layout_marginRight="15.5dp"
android:layout_marginStart="14.5dp"
android:layout_marginTop="18dp"
android:src="@drawable/todo_checkbox" />
</LinearLayout>
android:id="@+id/lock_icon"
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_gravity="center"
android:scaleType="center"/>
</FrameLayout>
<LinearLayout

View file

@ -0,0 +1,210 @@
<?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:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
app:maxHeightMultiplier="0.7">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:scrollbarAlwaysDrawVerticalTrack="true"
android:scrollbars="vertical"
android:layout_marginTop="@dimen/spacing_large"
android:paddingBottom="?attr/actionBarSize">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingLeft="16dp"
android:paddingRight="16dp">
<net.pherth.android.emoji_library.EmojiTextView
android:id="@+id/challenge_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
style="@style/Title1"
android:textSize="18sp"
tools:text="Challenge name"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:baselineAligned="false"
android:layout_marginTop="@dimen/spacing_large"
android:layout_marginBottom="@dimen/spacing_large">
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="0.5"
android:background="@drawable/layout_rounded_bg"
android:padding="@dimen/spacing_medium"
android:orientation="vertical"
android:layout_marginRight="@dimen/spacing_medium">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal">
<ImageView
android:id="@+id/participant_count_icon"
android:layout_width="24dp"
android:layout_height="24dp" />
<TextView
android:id="@+id/participant_count"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:textSize="17sp"
android:textColor="@color/gray_100"
tools:text="5"
android:layout_marginLeft="@dimen/spacing_small"
/>
</LinearLayout>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/participants"
android:gravity="center"
android:textSize="12sp"
android:textColor="@color/gray_200"/>
</LinearLayout>
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="0.5"
android:background="@drawable/layout_rounded_bg"
android:padding="@dimen/spacing_medium"
android:orientation="vertical"
android:layout_marginLeft="@dimen/spacing_medium">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal">
<ImageView
android:id="@+id/gem_amount_icon"
android:layout_width="24dp"
android:layout_height="24dp" />
<TextView
android:id="@+id/gem_amount"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:textSize="17sp"
android:textColor="@color/gray_100"
tools:text="5"
android:layout_marginLeft="@dimen/spacing_small"
/>
</LinearLayout>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/challenge_prize"
android:gravity="center"
android:textSize="12sp"
android:textColor="@color/gray_200"/>
</LinearLayout>
</LinearLayout>
</LinearLayout>
<FrameLayout
android:id="@+id/join_button_wrapper"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingLeft="@dimen/spacing_large"
android:paddingRight="@dimen/spacing_large"
android:paddingTop="@dimen/spacing_medium"
android:paddingBottom="@dimen/spacing_medium"
android:background="@color/white">
<Button
android:id="@+id/join_button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/join"
style="@style/HabiticaButton.Green"/>
</FrameLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingLeft="@dimen/spacing_large"
android:paddingRight="@dimen/spacing_large"
android:paddingTop="@dimen/spacing_medium"
android:paddingBottom="@dimen/spacing_medium"
android:background="@color/white"
android:orientation="horizontal">
<com.habitrpg.android.habitica.ui.RoundedFrameLayout
android:layout_width="@dimen/avatar_chat_size"
android:layout_height="@dimen/avatar_chat_size"
android:clipChildren="true"
android:layout_marginRight="@dimen/spacing_medium"
android:layout_marginEnd="@dimen/spacing_medium"
android:background="@drawable/rounded_avatar_bg">
<com.habitrpg.android.habitica.ui.AvatarView
android:id="@+id/creator_avatarview"
android:layout_width="60dp"
android:layout_height="60dp"
android:layout_gravity="center"
app:showMount="false"
app:showPet="false" />
</com.habitrpg.android.habitica.ui.RoundedFrameLayout>
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_weight="1">
<com.habitrpg.android.habitica.ui.views.social.UsernameLabel
android:id="@+id/creator_label"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/created_challenge"
android:textColor="@color/gray_200"
android:textSize="12sp"/>
</LinearLayout>
<ImageButton
android:id="@+id/creator_message_button"
android:layout_width="40dp"
android:layout_height="match_parent"
style="@style/Base.Widget.AppCompat.Button.Borderless"/>
</LinearLayout>
<com.habitrpg.android.habitica.ui.views.CollapsibleSectionView
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:title="@string/challenge_categories"
android:background="@color/white">
<net.pherth.android.emoji_library.EmojiTextView
android:id="@+id/challenge_description"
style="@style/Body2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="11dp"
android:lineSpacingExtra="4sp"
tools:text="Theres always more to learn! Maybe its a specialized topic youre interested in, or maybe its the experiences of a notable contributor. Read about it and you may win 15 gems!" />
</com.habitrpg.android.habitica.ui.views.CollapsibleSectionView>
<LinearLayout
android:id="@+id/task_group_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="13dp"
android:orientation="vertical">
</LinearLayout>
<FrameLayout
android:id="@+id/leave_button_wrapper"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingLeft="@dimen/spacing_large"
android:paddingRight="@dimen/spacing_large"
android:paddingTop="@dimen/spacing_medium"
android:paddingBottom="@dimen/spacing_large">
<Button
android:id="@+id/leave_button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/leave"
style="@style/HabiticaButton.Red"/>
</FrameLayout>
</LinearLayout>
</android.support.v4.widget.NestedScrollView>

View file

@ -782,4 +782,7 @@
<string name="report">Report</string>
<string name="like">Like</string>
<string name="reply">Reply</string>
<string name="challenge_prize">Challenge Prize</string>
<string name="challenge_categories">Challenge Categories</string>
<string name="created_challenge">Created Challenge</string>
</resources>

View file

@ -224,6 +224,10 @@
<item name="android:paddingBottom">@dimen/pill_vertical_padding</item>
</style>
<style name="Pill.White">
<item name="android:background">@drawable/pill_bg_white</item>
</style>
<style name="Pill.Selected">
<item name="android:textColor">@color/white</item>
<item name="android:background">@drawable/pill_bg_purple</item>

Binary file not shown.

View file

@ -16,7 +16,6 @@ import com.habitrpg.android.habitica.receivers.NotificationPublisher;
import com.habitrpg.android.habitica.receivers.TaskAlarmBootReceiver;
import com.habitrpg.android.habitica.receivers.TaskReceiver;
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.FixCharacterValuesActivity;
@ -79,9 +78,8 @@ import com.habitrpg.android.habitica.ui.fragments.social.PublicGuildsFragment;
import com.habitrpg.android.habitica.ui.fragments.social.QuestDetailFragment;
import com.habitrpg.android.habitica.ui.fragments.social.TavernDetailFragment;
import com.habitrpg.android.habitica.ui.fragments.social.TavernFragment;
import com.habitrpg.android.habitica.ui.fragments.social.challenges.ChallengeDetailDialogHolder;
import com.habitrpg.android.habitica.ui.fragments.social.challenges.ChallengeDetailFragment;
import com.habitrpg.android.habitica.ui.fragments.social.challenges.ChallengeListFragment;
import com.habitrpg.android.habitica.ui.fragments.social.challenges.ChallengeTasksRecyclerViewFragment;
import com.habitrpg.android.habitica.ui.fragments.social.challenges.ChallengesOverviewFragment;
import com.habitrpg.android.habitica.ui.fragments.social.party.PartyDetailFragment;
import com.habitrpg.android.habitica.ui.fragments.social.party.PartyFragment;
@ -230,8 +228,6 @@ public interface AppComponent {
void inject(ChallengeListFragment challengeListFragment);
void inject(ChallengeDetailActivity challengeDetailActivity);
void inject(ApiClient apiClient);
void inject(TaskListWidgetProvider taskListWidgetProvider);
@ -250,8 +246,6 @@ public interface AppComponent {
void inject(ChallengeTasksRecyclerViewAdapter challengeTasksRecyclerViewAdapter);
void inject(ChallengeTasksRecyclerViewFragment challengeTasksRecyclerViewFragment);
void inject(TaskListFactory taskListFactory);
void inject(GemPurchaseActivity gemPurchaseActivity);
@ -272,8 +266,6 @@ public interface AppComponent {
void inject(CreateChallengeActivity createChallengeActivity);
void inject(ChallengeDetailDialogHolder challengeDetailDialogHolder);
void inject(TavernDetailFragment tavernDetailFragment);
void inject(PartyDetailFragment partyDetailFragment);
@ -299,4 +291,6 @@ public interface AppComponent {
void inject(WelcomeFragment welcomeFragment);
void inject(@NotNull NavigationDrawerFragment navigationDrawerFragment);
void inject(@NotNull ChallengeDetailFragment challengeDetailFragment);
}

View file

@ -1,41 +0,0 @@
package com.habitrpg.android.habitica.data;
import com.habitrpg.android.habitica.models.LeaveChallengeBody;
import com.habitrpg.android.habitica.models.social.Challenge;
import com.habitrpg.android.habitica.models.tasks.Task;
import com.habitrpg.android.habitica.models.tasks.TaskList;
import com.habitrpg.android.habitica.models.user.User;
import java.util.List;
import io.reactivex.Flowable;
import io.realm.RealmResults;
public interface ChallengeRepository extends BaseRepository {
Flowable<Challenge> getChallenge(String challengeId);
Flowable<TaskList> getChallengeTasks(String challengeId);
Flowable<Challenge> createChallenge(Challenge challenge, List<Task> taskList);
/**
*
* @param challenge the challenge that will be updated
* @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 with the updated challenge
*/
Flowable<Challenge> updateChallenge(Challenge challenge, List<Task> fullTaskList,
List<Task> addedTaskList, List<Task> updatedTaskList, List<String> removedTaskList);
Flowable<Void> deleteChallenge(String challengeId);
Flowable<RealmResults<Challenge>> getChallenges();
Flowable<RealmResults<Challenge>> getUserChallenges(String userId);
Flowable<List<Challenge>> retrieveChallenges(User user);
Flowable<Void> leaveChallenge(Challenge challenge, LeaveChallengeBody leaveChallengeBody);
Flowable<Challenge> joinChallenge(Challenge challenge);
}

View file

@ -0,0 +1,48 @@
package com.habitrpg.android.habitica.data
import com.habitrpg.android.habitica.models.LeaveChallengeBody
import com.habitrpg.android.habitica.models.social.Challenge
import com.habitrpg.android.habitica.models.social.ChallengeMembership
import com.habitrpg.android.habitica.models.social.GroupMembership
import com.habitrpg.android.habitica.models.tasks.Task
import com.habitrpg.android.habitica.models.tasks.TaskList
import com.habitrpg.android.habitica.models.user.User
import io.reactivex.Flowable
import io.realm.RealmResults
interface ChallengeRepository : BaseRepository {
fun retrieveChallenges(user: User): Flowable<List<Challenge>>
fun getChallenges(): Flowable<RealmResults<Challenge>>
fun getChallenge(challengeId: String): Flowable<Challenge>
fun getChallengeTasks(challengeId: String): Flowable<RealmResults<Task>>
fun retrieveChallenge(challengeID: String): Flowable<Challenge>
fun retrieveChallengeTasks(challengeID: String): Flowable<TaskList>
fun createChallenge(challenge: Challenge, taskList: List<Task>): Flowable<Challenge>
/**
*
* @param challenge the challenge that will be updated
* @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 with the updated challenge
*/
fun updateChallenge(challenge: Challenge, fullTaskList: List<Task>,
addedTaskList: List<Task>, updatedTaskList: List<Task>, removedTaskList: List<String>): Flowable<Challenge>
fun deleteChallenge(challengeId: String): Flowable<Void>
fun getUserChallenges(userId: String): Flowable<RealmResults<Challenge>>
fun leaveChallenge(challenge: Challenge, leaveChallengeBody: LeaveChallengeBody): Flowable<Void>
fun joinChallenge(challenge: Challenge): Flowable<Challenge>
fun getChallengepMembership(id: String): Flowable<ChallengeMembership>
fun getChallengeMemberships(): Flowable<RealmResults<ChallengeMembership>>
fun isChallengeMember(challengeID: String): Flowable<Boolean>
}

View file

@ -7,6 +7,7 @@ import com.habitrpg.android.habitica.models.responses.PostChatMessageResult
import com.habitrpg.android.habitica.models.social.Challenge
import com.habitrpg.android.habitica.models.social.ChatMessage
import com.habitrpg.android.habitica.models.social.Group
import com.habitrpg.android.habitica.models.social.GroupMembership
import com.habitrpg.android.habitica.models.user.User
import io.reactivex.Flowable
import io.reactivex.Single
@ -72,4 +73,7 @@ interface SocialRepository : BaseRepository {
fun forceStartQuest(party: Group): Flowable<Quest>
fun getMemberAchievements(userId: String?): Flowable<AchievementResult>
fun getGroupMembership(id: String): Flowable<GroupMembership>
fun getGroupMemberships(): Flowable<RealmResults<GroupMembership>>
}

View file

@ -6,25 +6,51 @@ import com.habitrpg.android.habitica.data.ChallengeRepository
import com.habitrpg.android.habitica.data.local.ChallengeLocalRepository
import com.habitrpg.android.habitica.models.LeaveChallengeBody
import com.habitrpg.android.habitica.models.social.Challenge
import com.habitrpg.android.habitica.models.social.ChallengeMembership
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 com.habitrpg.android.habitica.models.user.User
import io.reactivex.Flowable
import io.realm.RealmResults
import java.util.*
class ChallengeRepositoryImpl(localRepository: ChallengeLocalRepository, apiClient: ApiClient) : BaseRepositoryImpl<ChallengeLocalRepository>(localRepository, apiClient), ChallengeRepository {
class ChallengeRepositoryImpl(localRepository: ChallengeLocalRepository, apiClient: ApiClient, private val userId: String) : BaseRepositoryImpl<ChallengeLocalRepository>(localRepository, apiClient), ChallengeRepository {
override fun isChallengeMember(challengeID: String): Flowable<Boolean> {
return localRepository.isChallengeMember(userId, challengeID)
}
override fun getChallengepMembership(id: String): Flowable<ChallengeMembership> {
return localRepository.getChallengeMembership(userId, id)
}
override fun getChallengeMemberships(): Flowable<RealmResults<ChallengeMembership>> {
return localRepository.getChallengeMemberships(userId)
}
override fun getChallenge(challengeId: String): Flowable<Challenge> {
return apiClient.getChallenge(challengeId)
return localRepository.getChallenge(challengeId)
}
override fun getChallengeTasks(challengeId: String): Flowable<TaskList> {
return apiClient.getChallengeTasks(challengeId)
override fun getChallengeTasks(challengeId: String): Flowable<RealmResults<Task>> {
return localRepository.getTasks(challengeId)
}
override fun retrieveChallenge(challengeID: String): Flowable<Challenge> {
return apiClient.getChallenge(challengeID).doOnNext {
localRepository.save(it)
}
}
override fun retrieveChallengeTasks(challengeID: String): Flowable<TaskList> {
return apiClient.getChallengeTasks(challengeID).doOnNext {
val taskList = it.tasks.values.toList()
taskList.forEach {
it.userId = challengeID
}
localRepository.save(taskList)
}
}
private fun getTaskOrders(taskList: List<Task>): TasksOrder {
val stringListMap = U.groupBy(taskList) { t -> t.type }
@ -46,7 +72,7 @@ class ChallengeRepositoryImpl(localRepository: ChallengeLocalRepository, apiClie
}
private fun addChallengeTasks(challenge: Challenge, addedTaskList: List<Task>): Flowable<Challenge> {
return apiClient.createChallengeTasks(challenge.id, addedTaskList).map { challenge }
return apiClient.createChallengeTasks(challenge.id ?: "", addedTaskList).map { challenge }
}
override fun createChallenge(challenge: Challenge, taskList: List<Task>): Flowable<Challenge> {
@ -95,17 +121,13 @@ class ChallengeRepositoryImpl(localRepository: ChallengeLocalRepository, apiClie
.doOnNext { localRepository.saveChallenges(it) }
}
override fun leaveChallenge(challenge: Challenge?, leaveChallengeBody: LeaveChallengeBody): Flowable<Void> {
return if (challenge == null) {
Flowable.empty()
} else apiClient.leaveChallenge(challenge.id, leaveChallengeBody)
.doOnNext { localRepository.setParticipating(challenge, false) }
override fun leaveChallenge(challenge: Challenge, leaveChallengeBody: LeaveChallengeBody): Flowable<Void> {
return apiClient.leaveChallenge(challenge.id ?: "", leaveChallengeBody)
.doOnNext { localRepository.setParticipating(userId, challenge.id ?: "", false) }
}
override fun joinChallenge(challenge: Challenge?): Flowable<Challenge> {
return if (challenge == null) {
Flowable.empty()
} else apiClient.joinChallenge(challenge.id)
.doOnNext { localRepository.setParticipating(challenge, true) }
override fun joinChallenge(challenge: Challenge): Flowable<Challenge> {
return apiClient.joinChallenge(challenge.id ?: "")
.doOnNext { localRepository.setParticipating(userId, challenge.id ?: "", false) }
}
}

View file

@ -12,6 +12,7 @@ import com.habitrpg.android.habitica.models.responses.PostChatMessageResult
import com.habitrpg.android.habitica.models.social.Challenge
import com.habitrpg.android.habitica.models.social.ChatMessage
import com.habitrpg.android.habitica.models.social.Group
import com.habitrpg.android.habitica.models.social.GroupMembership
import com.habitrpg.android.habitica.models.user.User
import io.reactivex.Flowable
import io.reactivex.Single
@ -22,6 +23,13 @@ import java.util.*
class SocialRepositoryImpl(localRepository: SocialLocalRepository, apiClient: ApiClient, private val userId: String) : BaseRepositoryImpl<SocialLocalRepository>(localRepository, apiClient), SocialRepository {
override fun getGroupMembership(id: String): Flowable<GroupMembership> {
return localRepository.getGroupMembership(userId, id)
}
override fun getGroupMemberships(): Flowable<RealmResults<GroupMembership>> {
return localRepository.getGroupMemberships(userId)
}
override fun retrieveGroupChat(groupId: String): Single<List<ChatMessage>> {
return apiClient.listGroupChat(groupId)
@ -83,14 +91,7 @@ class SocialRepositoryImpl(localRepository: SocialLocalRepository, apiClient: Ap
}
override fun retrieveGroup(id: String): Flowable<Group> {
var observable = apiClient.getGroup(id)
if ("party" != id && localRepository.doesGroupExist(id)) {
observable = observable.withLatestFrom(localRepository.getGroup(id), BiFunction<Group, Group, Group> { newGroup, oldGroup ->
newGroup.isMember = oldGroup.isMember
newGroup
})
}
return Flowable.zip(observable.doOnNext { localRepository.save(it) }, retrieveGroupChat(id)
return Flowable.zip(apiClient.getGroup(id).doOnNext { localRepository.save(it) }, retrieveGroupChat(id)
.map {
it.forEach {
it.groupId = id
@ -98,7 +99,7 @@ class SocialRepositoryImpl(localRepository: SocialLocalRepository, apiClient: Ap
return@map it
}
.doOnSuccess { localRepository.save(it) }.toFlowable(),
BiFunction<Group, List<ChatMessage>, Group> { group, chat ->
BiFunction<Group, List<ChatMessage>, Group> { group, _ ->
group
}
)
@ -117,7 +118,7 @@ class SocialRepositoryImpl(localRepository: SocialLocalRepository, apiClient: Ap
}
return apiClient.leaveGroup(id)
.flatMapMaybe { localRepository.getGroup(id).firstElement() }
.doOnNext { group -> localRepository.executeTransaction { group.isMember = false } }
.doOnNext { localRepository.executeTransaction { localRepository.updateMembership(userId, id, false) } }
}
override fun joinGroup(id: String?): Flowable<Group> {
@ -126,7 +127,7 @@ class SocialRepositoryImpl(localRepository: SocialLocalRepository, apiClient: Ap
}
return apiClient.joinGroup(id)
.doOnNext { group ->
group?.isMember = true
localRepository.updateMembership(userId, id, true)
localRepository.save(group)
}
}
@ -148,7 +149,10 @@ class SocialRepositoryImpl(localRepository: SocialLocalRepository, apiClient: Ap
return apiClient.listGroups(type)
.doOnNext { groups ->
if ("guilds" == type) {
groups.forEach { guild -> guild.isMember = true }
val memberships = groups.map {
return@map GroupMembership(userId, it.id)
}
localRepository.save(memberships)
}
localRepository.save(groups)
}
@ -195,7 +199,7 @@ class SocialRepositoryImpl(localRepository: SocialLocalRepository, apiClient: Ap
}
}
override fun getUserGroups(): Flowable<RealmResults<Group>> = localRepository.getUserGroups()
override fun getUserGroups(): Flowable<RealmResults<Group>> = localRepository.getUserGroups(userId)
override fun acceptQuest(user: User?, partyId: String): Flowable<Void> {
return apiClient.acceptQuest(partyId)

View file

@ -2,6 +2,8 @@ package com.habitrpg.android.habitica.data.local
import com.habitrpg.android.habitica.models.social.Challenge
import com.habitrpg.android.habitica.models.social.ChallengeMembership
import com.habitrpg.android.habitica.models.social.GroupMembership
import com.habitrpg.android.habitica.models.tasks.Task
import io.reactivex.Flowable
@ -11,11 +13,15 @@ interface ChallengeLocalRepository : BaseLocalRepository {
val challenges: Flowable<RealmResults<Challenge>>
fun getChallenge(id: String): Flowable<Challenge>
fun getTasks(challenge: Challenge): Flowable<List<Task>>
fun getTasks(challengeID: String): Flowable<RealmResults<Task>>
fun getUserChallenges(userId: String): Flowable<RealmResults<Challenge>>
fun setParticipating(challenge: Challenge, isParticipating: Boolean)
fun setParticipating(userID: String, challengeID: String, isParticipating: Boolean)
fun saveChallenges(challenges: List<Challenge>)
fun getChallengeMembership(userId: String, id: String): Flowable<ChallengeMembership>
fun getChallengeMemberships(userId: String): Flowable<RealmResults<ChallengeMembership>>
fun isChallengeMember(userID: String, challengeID: String): Flowable<Boolean>
}

View file

@ -3,14 +3,16 @@ package com.habitrpg.android.habitica.data.local
import com.habitrpg.android.habitica.models.members.Member
import com.habitrpg.android.habitica.models.social.ChatMessage
import com.habitrpg.android.habitica.models.social.Group
import com.habitrpg.android.habitica.models.social.GroupMembership
import com.habitrpg.android.habitica.models.user.User
import io.reactivex.Flowable
import io.realm.Realm
import io.realm.RealmResults
interface SocialLocalRepository : BaseLocalRepository {
fun getPublicGuilds(): Flowable<RealmResults<Group>>
fun getUserGroups(): Flowable<RealmResults<Group>>
fun getUserGroups(userID: String): Flowable<RealmResults<Group>>
fun getGroups(type: String): Flowable<RealmResults<Group>>
fun getGroup(id: String): Flowable<Group>
@ -34,4 +36,7 @@ interface SocialLocalRepository : BaseLocalRepository {
fun saveChatMessages(groupId: String?, chatMessages: List<ChatMessage>)
fun doesGroupExist(id: String): Boolean
fun updateMembership(userId: String, id: String, isMember: Boolean)
fun getGroupMembership(userId: String, id: String): Flowable<GroupMembership>
fun getGroupMemberships(userId: String): Flowable<RealmResults<GroupMembership>>
}

View file

@ -2,18 +2,38 @@ 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.ChallengeMembership
import com.habitrpg.android.habitica.models.tasks.Task
import java.util.ArrayList
import io.reactivex.Flowable
import io.realm.OrderedRealmCollectionSnapshot
import io.realm.Realm
import io.realm.RealmResults
import io.realm.Sort
import java.util.*
class RealmChallengeLocalRepository(realm: Realm) : RealmBaseLocalRepository(realm), ChallengeLocalRepository {
override fun isChallengeMember(userID: String, challengeID: String): Flowable<Boolean> = realm.where(ChallengeMembership::class.java)
.equalTo("userID", userID)
.equalTo("challengeID", challengeID)
.findAll()
.asFlowable()
.filter { it.isLoaded }
.map { it.count() > 0 }
override fun getChallengeMembership(userId: String, id: String): Flowable<ChallengeMembership> = realm.where(ChallengeMembership::class.java)
.equalTo("userID", userId)
.equalTo("challengeID", id)
.findAll()
.asFlowable()
.filter { it.isLoaded }
.map { it.first() }
override fun getChallengeMemberships(userId: String): Flowable<RealmResults<ChallengeMembership>> = realm.where(ChallengeMembership::class.java)
.equalTo("userID", userId)
.findAll()
.asFlowable()
.filter { it.isLoaded }
override fun getChallenge(id: String): Flowable<Challenge> {
return realm.where(Challenge::class.java)
.equalTo("id", id)
@ -23,8 +43,12 @@ class RealmChallengeLocalRepository(realm: Realm) : RealmBaseLocalRepository(rea
.map { it.first() }
}
override fun getTasks(challenge: Challenge): Flowable<List<Task>> {
return Flowable.empty()
override fun getTasks(challengeID: String): Flowable<RealmResults<Task>> {
return realm.where(Task::class.java)
.equalTo("userId", challengeID)
.findAll()
.asFlowable()
.filter { realmObject -> realmObject.isLoaded }
}
override val challenges: Flowable<RealmResults<Challenge>>
@ -36,24 +60,45 @@ class RealmChallengeLocalRepository(realm: Realm) : RealmBaseLocalRepository(rea
.filter { it.isLoaded }
override fun getUserChallenges(userId: String): Flowable<RealmResults<Challenge>> {
return realm.where(Challenge::class.java)
.isNotNull("name")
.equalTo("isParticipating", true)
.sort("official", Sort.DESCENDING, "createdAt", Sort.DESCENDING)
.findAllAsync()
return realm.where(ChallengeMembership::class.java)
.equalTo("userID", userId)
.findAll()
.asFlowable()
.filter { it.isLoaded }
.flatMap {
val ids = it.map {
return@map it.challengeID
}.toTypedArray()
realm.where(Challenge::class.java)
.isNotNull("name")
.`in`("id", ids)
.sort("official", Sort.DESCENDING, "createdAt", Sort.DESCENDING)
.findAll()
.asFlowable()
.filter { it.isLoaded }
}
}
override fun setParticipating(challenge: Challenge, isParticipating: Boolean) {
realm.executeTransaction { challenge.isParticipating = isParticipating }
override fun setParticipating(userID: String, challengeID: String, isParticipating: Boolean) {
if (isParticipating) {
realm.executeTransaction {
realm.insertOrUpdate(ChallengeMembership(userID, challengeID))
}
} else {
val membership = realm.where(ChallengeMembership::class.java).equalTo("userID", userID).equalTo("challengeID", challengeID).findFirst()
if (membership != null) {
realm.executeTransaction {
membership.deleteFromRealm()
}
}
}
}
override fun saveChallenges(onlineChallenges: List<Challenge>) {
override fun saveChallenges(challenges: List<Challenge>) {
val localChallenges = realm.where(Challenge::class.java).findAll().createSnapshot()
val challengesToDelete = ArrayList<Challenge>()
for (localTask in localChallenges) {
if (!onlineChallenges.contains(localTask)) {
if (!challenges.contains(localTask)) {
challengesToDelete.add(localTask)
}
}
@ -62,6 +107,6 @@ class RealmChallengeLocalRepository(realm: Realm) : RealmBaseLocalRepository(rea
localTask.deleteFromRealm()
}
}
realm.executeTransaction { realm1 -> realm1.insertOrUpdate(onlineChallenges) }
realm.executeTransaction { realm1 -> realm1.insertOrUpdate(challenges) }
}
}

View file

@ -5,6 +5,7 @@ import com.habitrpg.android.habitica.models.members.Member
import com.habitrpg.android.habitica.models.social.ChatMessage
import com.habitrpg.android.habitica.models.social.ChatMessageLike
import com.habitrpg.android.habitica.models.social.Group
import com.habitrpg.android.habitica.models.social.GroupMembership
import com.habitrpg.android.habitica.models.user.User
import io.reactivex.Flowable
import io.realm.Realm
@ -15,6 +16,35 @@ import java.util.*
class RealmSocialLocalRepository(realm: Realm) : RealmBaseLocalRepository(realm), SocialLocalRepository {
override fun getGroupMembership(userId: String, id: String): Flowable<GroupMembership> = realm.where(GroupMembership::class.java)
.equalTo("userID", userId)
.equalTo("groupID", id)
.findAll()
.asFlowable()
.filter { it.isLoaded }
.map { it.first() }
override fun getGroupMemberships(userId: String): Flowable<RealmResults<GroupMembership>> = realm.where(GroupMembership::class.java)
.equalTo("userID", userId)
.findAll()
.asFlowable()
.filter { it.isLoaded }
override fun updateMembership(userId: String, id: String, isMember: Boolean) {
if (isMember) {
realm.executeTransaction {
realm.insertOrUpdate(GroupMembership(userId, id))
}
} else {
val membership = realm.where(GroupMembership::class.java).equalTo("userID", userId).equalTo("groupID", id).findFirst()
if (membership != null) {
realm.executeTransaction {
membership.deleteFromRealm()
}
}
}
}
override fun getPublicGuilds(): Flowable<RealmResults<Group>> = realm.where(Group::class.java)
.equalTo("type", "guild")
.equalTo("privacy", "public")
@ -23,13 +53,24 @@ class RealmSocialLocalRepository(realm: Realm) : RealmBaseLocalRepository(realm)
.asFlowable()
.filter { it.isLoaded }
override fun getUserGroups(): Flowable<RealmResults<Group>> = realm.where(Group::class.java)
.equalTo("type", "guild")
.equalTo("isMember", true)
.sort("memberCount", Sort.DESCENDING)
override fun getUserGroups(userID: String): Flowable<RealmResults<Group>> = realm.where(GroupMembership::class.java)
.equalTo("userID", userID)
.findAll()
.asFlowable()
.filter { it.isLoaded }
.asFlowable()
.filter { it.isLoaded }
.flatMap {
realm.where(Group::class.java)
.equalTo("type", "guild")
.`in`("id", it.map {
return@map it.groupID
}.toTypedArray())
.sort("memberCount", Sort.DESCENDING)
.findAll()
.asFlowable()
.filter { it.isLoaded }
}
override fun getGroups(type: String): Flowable<RealmResults<Group>> {
return realm.where(Group::class.java)

View file

@ -4,7 +4,7 @@ import com.habitrpg.android.habitica.data.local.UserLocalRepository
import com.habitrpg.android.habitica.models.Skill
import com.habitrpg.android.habitica.models.Tag
import com.habitrpg.android.habitica.models.TutorialStep
import com.habitrpg.android.habitica.models.social.Challenge
import com.habitrpg.android.habitica.models.social.ChallengeMembership
import com.habitrpg.android.habitica.models.social.ChatMessage
import com.habitrpg.android.habitica.models.user.User
import io.reactivex.Flowable
@ -42,7 +42,7 @@ class RealmUserLocalRepository(realm: Realm) : RealmBaseLocalRepository(realm),
removeOldTags(user.id, user.tags)
}
if (user.challenges != null) {
removeOldChallenges(user.challenges)
removeOldChallenges(user.id, user.challenges)
}
}
@ -62,12 +62,12 @@ class RealmUserLocalRepository(realm: Realm) : RealmBaseLocalRepository(realm),
}
}
private fun removeOldChallenges(onlineChallenges: List<Challenge>) {
val challenges = realm.where(Challenge::class.java).equalTo("isParticipating", true).findAll().createSnapshot()
val challengesToDelete = challenges.filterNot { onlineChallenges.contains(it) }
private fun removeOldChallenges(userID: String, onlineChallenges: List<ChallengeMembership>) {
val memberships = realm.where(ChallengeMembership::class.java).equalTo("userID", userID).findAll().createSnapshot()
val membershipsToDelete = memberships.filterNot { onlineChallenges.contains(it) }
realm.executeTransaction {
for (challenge in challengesToDelete) {
challenge.isParticipating = false
membershipsToDelete.forEach {
it.deleteFromRealm()
}
}
}

View file

@ -1,9 +0,0 @@
package com.habitrpg.android.habitica.events.commands;
public class ShowChallengeDetailActivityCommand {
public String challengeId;
public ShowChallengeDetailActivityCommand(String challengeId) {
this.challengeId = challengeId;
}
}

View file

@ -1,11 +0,0 @@
package com.habitrpg.android.habitica.events.commands;
public class ShowChallengeDetailDialogCommand {
public String challengeId;
public ShowChallengeDetailDialogCommand(String challengeId) {
this.challengeId = challengeId;
}
}

View file

@ -1,5 +1,7 @@
package com.habitrpg.android.habitica.models.members;
import android.support.annotation.Nullable;
import com.google.gson.annotations.SerializedName;
import com.habitrpg.android.habitica.models.Avatar;
import com.habitrpg.android.habitica.models.social.UserParty;
@ -19,10 +21,15 @@ public class Member extends RealmObject implements Avatar {
@SerializedName("_id")
private String id;
private Stats stats;
@Nullable
private Inbox inbox;
@Nullable
private MemberPreferences preferences;
@Nullable
private Profile profile;
@Nullable
private UserParty party;
@Nullable
private ContributorInfo contributor;
private Outfit costume;
@ -73,6 +80,7 @@ public class Member extends RealmObject implements Avatar {
}
}
@Nullable
public Stats getStats() {
return stats;
}
@ -84,6 +92,7 @@ public class Member extends RealmObject implements Avatar {
}
}
@Nullable
public Inbox getInbox() {
return inbox;
}
@ -95,6 +104,7 @@ public class Member extends RealmObject implements Avatar {
}
}
@Nullable
public Profile getProfile() {
return profile;
}
@ -106,6 +116,7 @@ public class Member extends RealmObject implements Avatar {
}
}
@Nullable
public ContributorInfo getContributor() {
return contributor;
}
@ -117,6 +128,7 @@ public class Member extends RealmObject implements Avatar {
}
}
@Nullable
public UserParty getParty() {
return party;
}

View file

@ -1,76 +0,0 @@
package com.habitrpg.android.habitica.models.social;
import com.habitrpg.android.habitica.models.tasks.ChecklistItem;
import com.habitrpg.android.habitica.models.tasks.TasksOrder;
import com.habitrpg.android.habitica.models.user.User;
import java.util.Date;
import java.util.HashMap;
import io.realm.RealmObject;
import io.realm.annotations.Ignore;
import io.realm.annotations.PrimaryKey;
public class Challenge extends RealmObject {
public static final String TASK_ORDER_HABITS = "habits";
public static final String TASK_ORDER_TODOS = "todos";
public static final String TASK_ORDER_DAILYS = "dailys";
public static final String TASK_ORDER_REWARDS = "rewards";
@PrimaryKey
public String id;
public String name;
public String shortName;
public String description;
public String leaderName;
public String leaderId;
public String groupName;
public String groupId;
public int prize;
public boolean official;
public int memberCount;
public String todoList;
public String habitList;
public String dailyList;
public String rewardList;
public Date createdAt;
public Date updatedAt;
public Group group;
public User leader;
@Ignore
public TasksOrder tasksOrder;
public boolean isParticipating;
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;
}
@Override
public boolean equals(Object obj) {
if (obj.getClass().equals(Challenge.class) && this.id != null) {
return this.id.equals(((Challenge)obj).id);
}
return super.equals(obj);
}
}

View file

@ -0,0 +1,73 @@
package com.habitrpg.android.habitica.models.social
import com.habitrpg.android.habitica.extensions.notNull
import com.habitrpg.android.habitica.models.tasks.TasksOrder
import com.habitrpg.android.habitica.models.user.User
import io.realm.RealmObject
import io.realm.annotations.Ignore
import io.realm.annotations.PrimaryKey
import java.util.*
open class Challenge : RealmObject() {
@PrimaryKey
var id: String? = null
var name: String? = null
var shortName: String? = null
var description: String? = null
var leaderName: String? = null
var leaderId: String? = null
var groupName: String? = null
var groupId: String? = null
var prize: Int = 0
var official: Boolean = false
var memberCount: Int = 0
var todoList: String? = null
var habitList: String? = null
var dailyList: String? = null
var rewardList: String? = null
var createdAt: Date? = null
var updatedAt: Date? = null
var group: Group? = null
var leader: User? = null
@Ignore
var tasksOrder: TasksOrder? = null
fun getTasksOrder(): HashMap<String, Array<String>> {
val map = HashMap<String, Array<String>>()
if (dailyList?.isNotEmpty() == true) {
dailyList?.split(",".toRegex())?.dropLastWhile { it.isEmpty() }?.toTypedArray().notNull { map[TASK_ORDER_DAILYS] }
}
if (habitList?.isNotEmpty() == true) {
habitList?.split(",".toRegex())?.dropLastWhile { it.isEmpty() }?.toTypedArray().notNull { map[TASK_ORDER_HABITS] }
}
if (rewardList?.isNotEmpty() == true) {
rewardList?.split(",".toRegex())?.dropLastWhile { it.isEmpty() }?.toTypedArray().notNull { map[TASK_ORDER_REWARDS] }
}
if (todoList?.isNotEmpty() == true) {
todoList?.split(",".toRegex())?.dropLastWhile { it.isEmpty() }?.toTypedArray().notNull { map[TASK_ORDER_TODOS] }
}
return map
}
override fun equals(other: Any?): Boolean {
return if (other?.javaClass == Challenge::class.java && this.id != null) {
this.id == (other as Challenge).id
} else super.equals(other)
}
companion object {
const val TASK_ORDER_HABITS = "habits"
const val TASK_ORDER_TODOS = "todos"
const val TASK_ORDER_DAILYS = "dailys"
const val TASK_ORDER_REWARDS = "rewards"
}
}

View file

@ -0,0 +1,40 @@
package com.habitrpg.android.habitica.models.social
import io.realm.RealmObject
import io.realm.annotations.PrimaryKey
open class ChallengeMembership : RealmObject {
@PrimaryKey
var combinedID: String = ""
var userID: String = ""
set(value) {
field = value
combinedID = userID + challengeID
}
var challengeID: String = ""
set(value) {
field = value
combinedID = userID + challengeID
}
constructor(userID: String, challengeID: String) : super() {
this.userID = userID
this.challengeID = challengeID
}
constructor() : super()
override fun equals(other: Any?): Boolean {
return if (other?.javaClass == ChallengeMembership::class.java) {
this.combinedID == (other as ChallengeMembership).combinedID
} else super.equals(other)
}
override fun hashCode(): Int {
var result = combinedID.hashCode()
result = 31 * result + userID.hashCode()
result = 31 * result + challengeID.hashCode()
return result
}
}

View file

@ -26,8 +26,6 @@ open class Group : RealmObject() {
var memberCount: Int = 0
var isMember: Boolean = false
var type: String? = null
var logo: String? = null

View file

@ -0,0 +1,27 @@
package com.habitrpg.android.habitica.models.social
import io.realm.RealmObject
import io.realm.annotations.PrimaryKey
open class GroupMembership : RealmObject {
@PrimaryKey
var combinedID: String = ""
var userID: String = ""
set(value) {
field = value
combinedID = userID + groupID
}
var groupID: String = ""
set(value) {
field = value
combinedID = userID + groupID
}
constructor(userID: String, groupID: String) : super() {
this.userID = userID
this.groupID = groupID
}
constructor() : super()
}

View file

@ -85,6 +85,19 @@ open class Task : RealmObject, Parcelable {
val completedChecklistCount: Int
get() = checklist?.count { it.completed } ?: 0
val extraLightTaskColor: Int
get() {
return when {
this.value < -20 -> return R.color.maroon_500
this.value < -10 -> return R.color.red_500
this.value < -1 -> return R.color.orange_500
this.value < 1 -> return R.color.yellow_500
this.value < 5 -> return R.color.green_500
this.value < 10 -> return R.color.teal_500
else -> R.color.blue_500
}
}
val lightTaskColor: Int
get() {
return when {

View file

@ -6,6 +6,7 @@ import com.habitrpg.android.habitica.models.PushDevice;
import com.habitrpg.android.habitica.models.Tag;
import com.habitrpg.android.habitica.models.invitations.Invitations;
import com.habitrpg.android.habitica.models.social.Challenge;
import com.habitrpg.android.habitica.models.social.ChallengeMembership;
import com.habitrpg.android.habitica.models.social.UserParty;
import com.habitrpg.android.habitica.models.tasks.TaskList;
import com.habitrpg.android.habitica.models.tasks.TasksOrder;
@ -51,7 +52,7 @@ public class User extends RealmObject implements Avatar {
@Ignore
private TasksOrder tasksOrder;
private RealmList<Challenge> challenges;
private RealmList<ChallengeMembership> challenges;
private Date lastCron;
private Boolean needsCron;
@ -310,11 +311,11 @@ public class User extends RealmObject implements Avatar {
this.tags = tags;
}
public RealmList<Challenge> getChallenges() {
public RealmList<ChallengeMembership> getChallenges() {
return challenges;
}
public void setChallenges(RealmList<Challenge> challenges) {
public void setChallenges(RealmList<ChallengeMembership> challenges) {
this.challenges = challenges;
}

View file

@ -91,8 +91,8 @@ public class RepositoryModule {
}
@Provides
ChallengeRepository providesChallengeRepository(ChallengeLocalRepository localRepository, ApiClient apiClient) {
return new ChallengeRepositoryImpl(localRepository, apiClient);
ChallengeRepository providesChallengeRepository(ChallengeLocalRepository localRepository, ApiClient apiClient, @Named(AppModule.NAMED_USER_ID) String userId) {
return new ChallengeRepositoryImpl(localRepository, apiClient, userId);
}
@Provides

View file

@ -1,397 +0,0 @@
package com.habitrpg.android.habitica.ui.activities
import android.app.AlertDialog
import android.content.Intent
import android.graphics.drawable.BitmapDrawable
import android.os.Bundle
import android.support.design.widget.TabLayout
import android.support.v7.widget.RecyclerView
import android.support.v7.widget.Toolbar
import android.view.Menu
import android.view.MenuItem
import android.view.View
import android.widget.Button
import android.widget.FrameLayout
import android.widget.TextView
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.UserRepository
import com.habitrpg.android.habitica.events.HabitScoreEvent
import com.habitrpg.android.habitica.events.TaskUpdatedEvent
import com.habitrpg.android.habitica.events.commands.BuyRewardCommand
import com.habitrpg.android.habitica.events.commands.ChecklistCheckedCommand
import com.habitrpg.android.habitica.events.commands.TaskCheckedCommand
import com.habitrpg.android.habitica.extensions.notNull
import com.habitrpg.android.habitica.helpers.RxErrorHandler
import com.habitrpg.android.habitica.interactors.*
import com.habitrpg.android.habitica.models.LeaveChallengeBody
import com.habitrpg.android.habitica.models.responses.TaskScoringResult
import com.habitrpg.android.habitica.models.social.Challenge
import com.habitrpg.android.habitica.models.tasks.Task
import com.habitrpg.android.habitica.models.user.User
import com.habitrpg.android.habitica.modules.AppModule
import com.habitrpg.android.habitica.ui.fragments.social.challenges.ChallengeDetailDialogHolder
import com.habitrpg.android.habitica.ui.fragments.social.challenges.ChallengeTasksRecyclerViewFragment
import com.habitrpg.android.habitica.ui.helpers.MarkdownParser
import com.habitrpg.android.habitica.ui.helpers.bindView
import com.habitrpg.android.habitica.ui.views.HabiticaIconsHelper
import com.habitrpg.android.habitica.ui.views.HabiticaSnackbar
import com.habitrpg.android.habitica.utils.Action1
import io.reactivex.functions.Consumer
import net.pherth.android.emoji_library.EmojiParser
import net.pherth.android.emoji_library.EmojiTextView
import org.greenrobot.eventbus.EventBus
import org.greenrobot.eventbus.Subscribe
import java.util.*
import javax.inject.Inject
import javax.inject.Named
class ChallengeDetailActivity : BaseActivity() {
@Inject
internal lateinit var challengeRepository: ChallengeRepository
@field:[Inject Named(AppModule.NAMED_USER_ID)]
internal lateinit var userId: String
@Inject
internal lateinit var userRepository: UserRepository
private val floatingMenuWrapper: FrameLayout by bindView(R.id.floating_menu_wrapper)
private val detailTabs: TabLayout by bindView(R.id.detail_tabs)
private val toolbar: Toolbar by bindView(R.id.toolbar)
// region UseCases
@Inject
internal lateinit var habitScoreUseCase: HabitScoreUseCase
@Inject
internal lateinit var dailyCheckUseCase: DailyCheckUseCase
@Inject
internal lateinit var todoCheckUseCase: TodoCheckUseCase
@Inject
internal lateinit var buyRewardUseCase: BuyRewardUseCase
@Inject
internal lateinit var checklistCheckUseCase: ChecklistCheckUseCase
@Inject
internal lateinit var displayItemDropUseCase: DisplayItemDropUseCase
@Inject
internal lateinit var notifyUserUseCase: NotifyUserUseCase
// endregion
private var challenge: Challenge? = null
private var user: User? = null
override fun getLayoutResId(): Int {
return R.layout.activity_challenge_detail
}
override fun onCreateOptionsMenu(menu: Menu): Boolean {
val inflater = menuInflater
inflater.inflate(R.menu.menu_challenge_details, menu)
if (challenge?.leaderId != userId) {
menu.setGroupVisible(R.id.challenge_edit_action_group, false)
}
return true
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setupToolbar(toolbar)
supportActionBar?.setTitle(R.string.challenge_details)
detailTabs.visibility = View.GONE
val extras = intent.extras
val challengeId = extras.getString(CHALLENGE_ID)
val fullList = ArrayList<Task>()
userRepository.getUser(userId).firstElement().subscribe(Consumer { user ->
this@ChallengeDetailActivity.user = user
createTaskRecyclerFragment(fullList)
}, RxErrorHandler.handleEmptyError())
if (challengeId != null) {
challengeRepository.getChallengeTasks(challengeId)
.firstElement()
.subscribe(Consumer { taskList ->
val resultList = ArrayList<Task>()
val todos = ArrayList<Task>()
val habits = ArrayList<Task>()
val dailies = ArrayList<Task>()
val rewards = ArrayList<Task>()
for (entry in taskList.tasks.entries) {
when (entry.value.type) {
Task.TYPE_TODO -> todos.add(entry.value)
Task.TYPE_HABIT ->
habits.add(entry.value)
Task.TYPE_DAILY ->
dailies.add(entry.value)
Task.TYPE_REWARD ->
rewards.add(entry.value)
}
}
if (!habits.isEmpty()) {
val dividerTask = Task()
dividerTask.id = "divhabits"
dividerTask.type = "divider"
dividerTask.text = "Challenge Habits"
resultList.add(dividerTask)
resultList.addAll(habits)
}
if (!dailies.isEmpty()) {
val dividerTask = Task()
dividerTask.id = "divdailies"
dividerTask.type = "divider"
dividerTask.text = "Challenge Dailies"
resultList.add(dividerTask)
resultList.addAll(dailies)
}
if (!todos.isEmpty()) {
val dividerTask = Task()
dividerTask.id = "divtodos"
dividerTask.type = "divider"
dividerTask.text = "Challenge To-Dos"
resultList.add(dividerTask)
resultList.addAll(todos)
}
if (!rewards.isEmpty()) {
val dividerTask = Task()
dividerTask.id = "divrewards"
dividerTask.type = "divider"
dividerTask.text = "Challenge Rewards"
resultList.add(dividerTask)
resultList.addAll(rewards)
}
fullList.addAll(resultList)
}, RxErrorHandler.handleEmptyError())
}
if (challengeId != null) {
challengeRepository.getChallenge(challengeId).subscribe(Consumer { challenge ->
this@ChallengeDetailActivity.challenge = challenge
val challengeViewHolder = ChallengeViewHolder(findViewById(R.id.challenge_header))
challengeViewHolder.bind(challenge)
}, RxErrorHandler.handleEmptyError())
}
}
private fun createTaskRecyclerFragment(fullList: List<Task>) {
val fragment = ChallengeTasksRecyclerViewFragment.newInstance(user, fullList)
if (supportFragmentManager.fragments == null) {
supportFragmentManager.beginTransaction().add(R.id.fragment_container, fragment).commitAllowingStateLoss()
} else {
val transaction = supportFragmentManager.beginTransaction()
transaction.setCustomAnimations(android.R.anim.fade_in, android.R.anim.fade_out, android.R.anim.fade_in, android.R.anim.fade_out)
transaction.replace(R.id.fragment_container, fragment).addToBackStack(null).commitAllowingStateLoss()
}
}
override fun onDestroy() {
challengeRepository.close()
userRepository.close()
super.onDestroy()
}
override fun injectActivity(component: AppComponent?) {
component?.inject(this)
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
R.id.action_edit -> {
openChallengeEditActivity()
return true
}
R.id.action_leave -> {
showChallengeLeaveDialog()
return true
}
}
return super.onOptionsItemSelected(item)
}
private fun openChallengeEditActivity() {
val intent = Intent(this, CreateChallengeActivity::class.java)
intent.putExtra(CreateChallengeActivity.CHALLENGE_ID_KEY, challenge?.id)
startActivity(intent)
}
private fun showChallengeLeaveDialog() {
AlertDialog.Builder(this)
.setTitle(this.getString(R.string.challenge_leave_title))
.setMessage(this.getString(R.string.challenge_leave_text, challenge?.name ?: ""))
.setPositiveButton(this.getString(R.string.yes)) { dialog, _ ->
dialog.dismiss()
showRemoveTasksDialog(object: Action1<String> {
override fun call(t: String) {
challengeRepository.leaveChallenge(challenge, LeaveChallengeBody(t))
.subscribe(Consumer { finish() }, RxErrorHandler.handleEmptyError())
}
})
}
.setNegativeButton(this.getString(R.string.no)) { dialog, _ -> dialog.dismiss() }.show()
}
// refactor as an UseCase later - see ChallengeDetailDialogHolder
private fun showRemoveTasksDialog(callback: Action1<String>) {
AlertDialog.Builder(this)
.setTitle(this.getString(R.string.challenge_remove_tasks_title))
.setMessage(this.getString(R.string.challenge_remove_tasks_text))
.setPositiveButton(this.getString(R.string.remove_tasks)) { dialog, _ ->
callback.call("remove-all")
dialog.dismiss()
}
.setNegativeButton(this.getString(R.string.keep_tasks)) { dialog, _ ->
callback.call("keep-all")
dialog.dismiss()
}.show()
}
override fun onSupportNavigateUp(): Boolean {
finish()
return true
}
override fun onBackPressed() {
finish()
}
internal inner class ChallengeViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
private val challengeName: EmojiTextView by bindView(itemView, R.id.challenge_name)
private val challengeDescription: EmojiTextView by bindView(itemView, R.id.challenge_description)
private val memberCountTextView: TextView by bindView(itemView, R.id.challenge_member_count)
private val gemPrizeTextView: TextView by bindView(itemView, R.id.gem_amount)
private val showMoreButton: Button by bindView(itemView, R.id.btn_show_more)
private var challenge: Challenge? = null
init {
showMoreButton.setOnClickListener { onShowMore() }
val gemDrawable = BitmapDrawable(itemView.resources, HabiticaIconsHelper.imageOfGem())
gemPrizeTextView.setCompoundDrawablesWithIntrinsicBounds(gemDrawable, null, null, null)
}
fun bind(challenge: Challenge) {
this.challenge = challenge
challengeName.text = EmojiParser.parseEmojis(challenge.name)
challengeDescription.text = MarkdownParser.parseMarkdown(challenge.description)
memberCountTextView.text = challenge.memberCount.toString()
if (challenge.prize == 0) {
gemPrizeTextView.visibility = View.GONE
} else {
gemPrizeTextView.visibility = View.VISIBLE
gemPrizeTextView.text = challenge.prize.toString()
}
}
fun onShowMore() {
challenge.notNull {
ChallengeDetailDialogHolder.showDialog(this@ChallengeDetailActivity,
this@ChallengeDetailActivity.challengeRepository,
it,
object: Action1<Challenge> {
override fun call(t: Challenge) {
onBackPressed()
}
})
}
}
}
@Subscribe
fun onEvent(event: TaskCheckedCommand) {
when (event.Task.type) {
Task.TYPE_DAILY -> {
dailyCheckUseCase.observable(DailyCheckUseCase.RequestValues(user, event.Task, !event.Task.completed))
.subscribe(Consumer { this.onTaskDataReceived(it) }, RxErrorHandler.handleEmptyError())
}
Task.TYPE_TODO -> {
todoCheckUseCase.observable(TodoCheckUseCase.RequestValues(user, event.Task, !event.Task.completed))
.subscribe(Consumer { this.onTaskDataReceived(it) }, RxErrorHandler.handleEmptyError())
}
}
}
@Subscribe
fun onEvent(event: ChecklistCheckedCommand) {
checklistCheckUseCase.observable(ChecklistCheckUseCase.RequestValues(event.task.id, event.item.id))
.subscribe(Consumer { EventBus.getDefault().post(TaskUpdatedEvent(event.task)) }, RxErrorHandler.handleEmptyError())
}
@Subscribe
fun onEvent(event: HabitScoreEvent) {
habitScoreUseCase.observable(HabitScoreUseCase.RequestValues(user, event.habit, event.Up))
.subscribe(Consumer { this.onTaskDataReceived(it) }, RxErrorHandler.handleEmptyError())
}
@Subscribe
fun onEvent(event: BuyRewardCommand) {
if ((user?.stats?.gp ?: 0.toDouble()) < event.Reward.value) {
HabiticaSnackbar.showSnackbar(floatingMenuWrapper, getString(R.string.no_gold), HabiticaSnackbar.SnackbarDisplayType.FAILURE)
return
}
if (event.Reward.specialTag == null || event.Reward.specialTag != "item") {
buyRewardUseCase.observable(BuyRewardUseCase.RequestValues(user, event.Reward))
.subscribe(Consumer { HabiticaSnackbar.showSnackbar(floatingMenuWrapper, getString(R.string.notification_purchase_reward), HabiticaSnackbar.SnackbarDisplayType.NORMAL) }, RxErrorHandler.handleEmptyError())
}
}
private fun onTaskDataReceived(data: TaskScoringResult) {
if (user != null) {
notifyUserUseCase.observable(NotifyUserUseCase.RequestValues(this, floatingMenuWrapper,
user, data.experienceDelta, data.healthDelta, data.goldDelta, data.manaDelta, data.questDamage, data.hasLeveledUp))
}
displayItemDropUseCase.observable(DisplayItemDropUseCase.RequestValues(data, this, floatingMenuWrapper))
.subscribe(Consumer { }, RxErrorHandler.handleEmptyError())
}
companion object {
var CHALLENGE_ID = "CHALLENGE_ID"
}
}

View file

@ -360,7 +360,7 @@ class CreateChallengeActivity : BaseActivity() {
}
private fun fillControlsByChallenge() {
challengeRepository.getChallenge(challengeId).subscribe(Consumer { challenge ->
/*challengeRepository.getChallenge(challengeId).subscribe(Consumer { challenge ->
createChallengeTitle.setText(challenge.name)
createChallengeDescription.setText(challenge.description)
@ -377,13 +377,7 @@ class CreateChallengeActivity : BaseActivity() {
}
checkPrizeAndMinimumForTavern()
challengeRepository.getChallengeTasks(challengeId).doOnComplete { editMode = true }.subscribe(Consumer { tasks ->
for (task in tasks.tasks.values) {
addOrUpdateTaskInList(task)
}
}, RxErrorHandler.handleEmptyError())
}, RxErrorHandler.handleEmptyError())
}, RxErrorHandler.handleEmptyError())*/
}
private fun openNewTaskActivity(type: String?, task: Task?) {

View file

@ -156,6 +156,10 @@ class FullProfileActivity : BaseActivity() {
private fun updateView(user: Member) {
val profile = user.profile
if (profile == null) {
return
}
userName = profile.name
title = profile.name

View file

@ -8,14 +8,16 @@ import android.widget.ImageView
import android.widget.LinearLayout
import android.widget.TextView
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.extensions.inflate
import com.habitrpg.android.habitica.extensions.notNull
import com.habitrpg.android.habitica.models.social.Challenge
import com.habitrpg.android.habitica.ui.fragments.social.challenges.ChallengeFilterOptions
import com.habitrpg.android.habitica.ui.helpers.bindView
import com.habitrpg.android.habitica.ui.views.HabiticaIconsHelper
import com.playseeds.android.sdk.inappmessaging.Log
import io.reactivex.BackpressureStrategy
import io.reactivex.Flowable
import io.reactivex.subjects.PublishSubject
import io.realm.OrderedRealmCollection
import io.realm.RealmRecyclerViewAdapter
import net.pherth.android.emoji_library.EmojiParser
@ -25,13 +27,22 @@ import org.greenrobot.eventbus.EventBus
class ChallengesListViewAdapter(data: OrderedRealmCollection<Challenge>?, autoUpdate: Boolean, private val viewUserChallengesOnly: Boolean, private val userId: String) : RealmRecyclerViewAdapter<Challenge, ChallengesListViewAdapter.ChallengeViewHolder>(data, autoUpdate) {
private var unfilteredData: OrderedRealmCollection<Challenge>? = null
private val openChallengeFragmentEvents = PublishSubject.create<String>()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ChallengeViewHolder {
return ChallengeViewHolder(parent.inflate(R.layout.challenge_item), viewUserChallengesOnly)
}
override fun onBindViewHolder(holder: ChallengeViewHolder, position: Int) {
data.notNull {
holder.bind(it[position])
data?.get(position).notNull { challenge ->
holder.bind(challenge)
holder.itemView.setOnClickListener {
if (challenge.isManaged) {
challenge.id.notNull {
openChallengeFragmentEvents.onNext(it)
}
}
}
}
}
@ -70,7 +81,11 @@ class ChallengesListViewAdapter(data: OrderedRealmCollection<Challenge>?, autoUp
}
}
class ChallengeViewHolder internal constructor(itemView: View, private val viewUserChallengesOnly: Boolean) : RecyclerView.ViewHolder(itemView), View.OnClickListener {
fun getOpenDetailFragmentFlowable(): Flowable<String> {
return openChallengeFragmentEvents.toFlowable(BackpressureStrategy.DROP)
}
class ChallengeViewHolder internal constructor(itemView: View, private val viewUserChallengesOnly: Boolean) : RecyclerView.ViewHolder(itemView) {
private val challengeName: EmojiTextView by bindView(R.id.challenge_name)
private val challengeDescription: TextView by bindView(R.id.challenge_group_name)
@ -87,8 +102,6 @@ class ChallengesListViewAdapter(data: OrderedRealmCollection<Challenge>?, autoUp
private var challenge: Challenge? = null
init {
itemView.setOnClickListener(this)
gemIconView.setImageBitmap(HabiticaIconsHelper.imageOfGem())
if (!viewUserChallengesOnly) {
@ -99,7 +112,8 @@ class ChallengesListViewAdapter(data: OrderedRealmCollection<Challenge>?, autoUp
fun bind(challenge: Challenge) {
this.challenge = challenge
challengeName.text = EmojiParser.parseEmojis(challenge.name.trim { it <= ' ' })
Log.e(challenge.id + challenge.name)
challengeName.text = EmojiParser.parseEmojis(challenge.name?.trim { it <= ' ' })
challengeDescription.text = challenge.groupName
officialChallengeLayout.visibility = if (challenge.official) View.VISIBLE else View.GONE
@ -109,7 +123,7 @@ class ChallengesListViewAdapter(data: OrderedRealmCollection<Challenge>?, autoUp
challengeParticipatingTextView.visibility = View.GONE
arrowImage.visibility = View.VISIBLE
} else {
challengeParticipatingTextView.visibility = if (challenge.isParticipating) View.VISIBLE else View.GONE
//challengeParticipatingTextView.visibility = if (challenge.isParticipating) View.VISIBLE else View.GONE
leaderName.text = itemView.context.getString(R.string.byLeader, challenge.leaderName)
participantCount.text = challenge.memberCount.toString()
@ -119,15 +133,5 @@ class ChallengesListViewAdapter(data: OrderedRealmCollection<Challenge>?, autoUp
gemPrizeTextView.text = challenge.prize.toString()
}
override fun onClick(view: View) {
if (challenge != null && challenge!!.isManaged && challenge!!.isManaged) {
if (viewUserChallengesOnly) {
EventBus.getDefault().post(ShowChallengeDetailActivityCommand(challenge!!.id))
} else {
EventBus.getDefault().post(ShowChallengeDetailDialogCommand(challenge!!.id))
}
}
}
}
}

View file

@ -79,7 +79,7 @@ class PartyMemberRecyclerViewAdapter(data: OrderedRealmCollection<Member>?, auto
else -> R.color.task_gray
}
ViewHelper.SetBackgroundTint(classBackground, ContextCompat.getColor(itemView.context, colorResourceID))
userName.text = user.profile.name
userName.text = user.profile?.name
itemView.isClickable = true
itemView.setOnClickListener { userClickedEvents.onNext(user.id) }

View file

@ -82,9 +82,12 @@ class StatsFragment: BaseMainFragment() {
leftSparklesView.setImageBitmap(HabiticaIconsHelper.imageOfAttributeSparklesLeft())
rightSparklesView.setImageBitmap(HabiticaIconsHelper.imageOfAttributeSparklesRight())
distributeEvenlyHelpButton.setImageBitmap(HabiticaIconsHelper.imageOfInfoIcon())
distributeClassHelpButton.setImageBitmap(HabiticaIconsHelper.imageOfInfoIcon())
distributeTaskHelpButton.setImageBitmap(HabiticaIconsHelper.imageOfInfoIcon())
context.notNull {
val color = ContextCompat.getColor(it, R.color.brand_200)
distributeEvenlyHelpButton.setImageBitmap(HabiticaIconsHelper.imageOfInfoIcon(color))
distributeClassHelpButton.setImageBitmap(HabiticaIconsHelper.imageOfInfoIcon(color))
distributeTaskHelpButton.setImageBitmap(HabiticaIconsHelper.imageOfInfoIcon(color))
}
compositeSubscription.add(userRepository.getUser(userId).subscribe(Consumer {
user = it

View file

@ -1,307 +0,0 @@
package com.habitrpg.android.habitica.ui.fragments.social.challenges
import android.app.Activity
import android.app.AlertDialog
import android.content.Intent
import android.os.Bundle
import android.view.View
import android.widget.Button
import android.widget.ImageView
import android.widget.LinearLayout
import android.widget.TextView
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.data.ChallengeRepository
import com.habitrpg.android.habitica.extensions.notNull
import com.habitrpg.android.habitica.helpers.RxErrorHandler
import com.habitrpg.android.habitica.models.LeaveChallengeBody
import com.habitrpg.android.habitica.models.social.Challenge
import com.habitrpg.android.habitica.models.tasks.Task
import com.habitrpg.android.habitica.ui.activities.ChallengeDetailActivity
import com.habitrpg.android.habitica.ui.activities.FullProfileActivity
import com.habitrpg.android.habitica.ui.helpers.MarkdownParser
import com.habitrpg.android.habitica.ui.helpers.bindView
import com.habitrpg.android.habitica.ui.views.HabiticaIconsHelper
import com.habitrpg.android.habitica.utils.Action1
import io.reactivex.functions.Consumer
import net.pherth.android.emoji_library.EmojiParser
import net.pherth.android.emoji_library.EmojiTextView
import java.util.*
class ChallengeDetailDialogHolder private constructor(view: View, private val context: Activity) {
private val notJoinedHeader: LinearLayout? by bindView(view, R.id.challenge_not_joined_header)
private val joinedHeader: LinearLayout? by bindView(view, R.id.challenge_joined_header)
private val joinButton: Button? by bindView(view, R.id.challenge_join_btn)
private val leaveButton: Button? by bindView(view, R.id.challenge_leave_btn)
private val challengeName: EmojiTextView? by bindView(view, R.id.challenge_name)
private val challengeDescription: EmojiTextView? by bindView(view, R.id.challenge_description)
private val challengeLeader: TextView? by bindView(view, R.id.challenge_leader)
private val gemAmountView: TextView? by bindView(view, R.id.gem_amount)
private val memberCountView: TextView? by bindView(view, R.id.challenge_member_count)
private val taskGrouplayout: LinearLayout? by bindView(view, R.id.task_group_layout)
private val openChallengeButton: Button? by bindView(view, R.id.challenge_go_to_btn)
private var dialog: AlertDialog? = null
lateinit var challengeRepository: ChallengeRepository
private var challenge: Challenge? = null
private var challengeLeftAction: Action1<Challenge>? = null
init {
joinButton?.setOnClickListener { joinChallenge() }
leaveButton?.setOnClickListener { leaveChallenge() }
challengeLeader?.setOnClickListener { openLeaderProfile() }
openChallengeButton?.setOnClickListener { openChallengeActivity() }
}
fun bind(dialog: AlertDialog, challengeRepository: ChallengeRepository, challenge: Challenge,
challengeLeftAction: Action1<Challenge>) {
this.dialog = dialog
this.challengeRepository = challengeRepository
this.challenge = challenge
this.challengeLeftAction = challengeLeftAction
changeViewsByChallenge(challenge)
}
private fun changeViewsByChallenge(challenge: Challenge) {
setJoined(challenge.isParticipating)
challengeName?.text = EmojiParser.parseEmojis(challenge.name)
challengeDescription?.text = MarkdownParser.parseMarkdown(challenge.description)
challengeLeader?.text = challenge.leaderName
gemAmountView?.text = challenge.prize.toString()
memberCountView?.text = challenge.memberCount.toString()
challengeRepository.getChallengeTasks(challenge.id)
.subscribe(Consumer { taskList ->
val todos = ArrayList<Task>()
val habits = ArrayList<Task>()
val dailies = ArrayList<Task>()
val rewards = ArrayList<Task>()
for (entry in taskList.tasks.entries) {
when (entry.value.type) {
Task.TYPE_TODO -> todos.add(entry.value)
Task.TYPE_HABIT ->
habits.add(entry.value)
Task.TYPE_DAILY ->
dailies.add(entry.value)
Task.TYPE_REWARD ->
rewards.add(entry.value)
}
}
if (habits.size > 0) {
addHabits(habits)
}
if (dailies.size > 0) {
addDailys(dailies)
}
if (todos.size > 0) {
addTodos(todos)
}
if (rewards.size > 0) {
addRewards(rewards)
}
}, RxErrorHandler.handleEmptyError())
}
private fun addHabits(habits: ArrayList<Task>) {
val taskGroup = context.layoutInflater.inflate(R.layout.dialog_challenge_detail_task_group, taskGrouplayout, false) as LinearLayout
val groupName = taskGroup.findViewById<View>(R.id.task_group_name) as TextView
val tasksLayout = taskGroup.findViewById<View>(R.id.tasks_layout) as LinearLayout
groupName.text = habits.size.toString() + " " + getLabelByTypeAndCount(Challenge.TASK_ORDER_HABITS, habits.size)
val size = habits.size
for (i in 0 until size) {
val task = habits[i]
val entry = context.layoutInflater.inflate(R.layout.dialog_challenge_detail_habit, tasksLayout, false)
val habitTitle = entry.findViewById<View>(R.id.habit_title) as TextView
val plusImg = entry.findViewById<View>(if (task.up == true) R.id.plus_img_tinted else R.id.plus_img) as ImageView
val minusImg = entry.findViewById<View>(if (task.down == true) R.id.minus_img_tinted else R.id.minus_img) as ImageView
plusImg.visibility = View.VISIBLE
minusImg.visibility = View.VISIBLE
habitTitle.text = EmojiParser.parseEmojis(task.text)
tasksLayout.addView(entry)
}
taskGrouplayout?.addView(taskGroup)
}
private fun addDailys(dailies: ArrayList<Task>) {
val taskGroup = context.layoutInflater.inflate(R.layout.dialog_challenge_detail_task_group, taskGrouplayout, false) as LinearLayout
val groupName = taskGroup.findViewById<View>(R.id.task_group_name) as TextView
val tasksLayout = taskGroup.findViewById<View>(R.id.tasks_layout) as LinearLayout
val size = dailies.size
groupName.text = dailies.size.toString() + " " + getLabelByTypeAndCount(Challenge.TASK_ORDER_DAILYS, size)
for (i in 0 until size) {
val task = dailies[i]
val entry = context.layoutInflater.inflate(R.layout.dialog_challenge_detail_daily, tasksLayout, false)
val title = entry.findViewById<View>(R.id.daily_title) as TextView
title.text = EmojiParser.parseEmojis(task.text)
if (task.checklist != null && task.checklist?.isEmpty() == false) {
val checklistIndicatorWrapper = entry.findViewById<View>(R.id.checklistIndicatorWrapper)
checklistIndicatorWrapper.visibility = View.VISIBLE
val checkListAllTextView = entry.findViewById<View>(R.id.checkListAllTextView) as TextView
checkListAllTextView.text = task.checklist?.size.toString()
}
tasksLayout.addView(entry)
}
taskGrouplayout?.addView(taskGroup)
}
private fun addTodos(todos: ArrayList<Task>) {
val taskGroup = context.layoutInflater.inflate(R.layout.dialog_challenge_detail_task_group, taskGrouplayout, false) as LinearLayout
val groupName = taskGroup.findViewById<View>(R.id.task_group_name) as TextView
val tasksLayout = taskGroup.findViewById<View>(R.id.tasks_layout) as LinearLayout
val size = todos.size
groupName.text = todos.size.toString() + " " + getLabelByTypeAndCount(Challenge.TASK_ORDER_TODOS, size)
for (i in 0 until size) {
val task = todos[i]
val entry = context.layoutInflater.inflate(R.layout.dialog_challenge_detail_todo, tasksLayout, false)
val title = entry.findViewById<View>(R.id.todo_title) as TextView
title.text = EmojiParser.parseEmojis(task.text)
if (task.checklist != null && task.checklist?.isEmpty() == false) {
val checklistIndicatorWrapper = entry.findViewById<View>(R.id.checklistIndicatorWrapper)
checklistIndicatorWrapper.visibility = View.VISIBLE
val checkListAllTextView = entry.findViewById<View>(R.id.checkListAllTextView) as TextView
checkListAllTextView.text = task.checklist?.size.toString()
}
tasksLayout.addView(entry)
}
taskGrouplayout?.addView(taskGroup)
}
private fun addRewards(rewards: ArrayList<Task>) {
val taskGroup = context.layoutInflater.inflate(R.layout.dialog_challenge_detail_task_group, taskGrouplayout, false) as LinearLayout
val groupName = taskGroup.findViewById<View>(R.id.task_group_name) as TextView
val tasksLayout = taskGroup.findViewById<View>(R.id.tasks_layout) as LinearLayout
val size = rewards.size
groupName.text = rewards.size.toString() + " " + getLabelByTypeAndCount(Challenge.TASK_ORDER_REWARDS, size)
for (i in 0 until size) {
val task = rewards[i]
val entry = context.layoutInflater.inflate(R.layout.dialog_challenge_detail_reward, tasksLayout, false)
(entry.findViewById<View>(R.id.gold_icon) as ImageView).setImageBitmap(HabiticaIconsHelper.imageOfGold())
val title = entry.findViewById<View>(R.id.reward_title) as TextView
title.text = EmojiParser.parseEmojis(task.text)
tasksLayout.addView(entry)
}
taskGrouplayout?.addView(taskGroup)
}
private fun getLabelByTypeAndCount(type: String, count: Int): String {
return when (type) {
Challenge.TASK_ORDER_DAILYS -> context.getString(if (count == 1) R.string.daily else R.string.dailies)
Challenge.TASK_ORDER_HABITS -> context.getString(if (count == 1) R.string.habit else R.string.habits)
Challenge.TASK_ORDER_REWARDS -> context.getString(if (count == 1) R.string.reward else R.string.rewards)
else -> context.getString(if (count == 1) R.string.todo else R.string.todos)
}
}
private fun setJoined(joined: Boolean) {
joinedHeader?.visibility = if (joined) View.VISIBLE else View.GONE
leaveButton?.visibility = if (joined) View.VISIBLE else View.GONE
notJoinedHeader?.visibility = if (joined) View.GONE else View.VISIBLE
joinButton?.visibility = if (joined) View.GONE else View.VISIBLE
}
private fun openLeaderProfile() {
FullProfileActivity.open(context, challenge?.leaderId ?: "")
}
private fun openChallengeActivity() {
val bundle = Bundle()
bundle.putString(ChallengeDetailActivity.CHALLENGE_ID, challenge?.id)
val intent = Intent(context, ChallengeDetailActivity::class.java)
intent.putExtras(bundle)
context.startActivity(intent)
this.dialog?.dismiss()
}
private fun joinChallenge() {
this.challengeRepository.joinChallenge(challenge).subscribe(Consumer<Challenge> { this.changeViewsByChallenge(it) }, RxErrorHandler.handleEmptyError())
}
private fun leaveChallenge() {
AlertDialog.Builder(context)
.setTitle(context.getString(R.string.challenge_leave_title))
.setMessage(context.getString(R.string.challenge_leave_text, challenge?.name))
.setPositiveButton(context.getString(R.string.yes)) { dialog, _ ->
showRemoveTasksDialog(object : Action1<String> {
override fun call(t: String) {
challengeRepository.leaveChallenge(challenge, LeaveChallengeBody(t))
.subscribe(Consumer {
challenge.notNull {
challengeLeftAction?.call(it)
}
dialog.dismiss()
}, RxErrorHandler.handleEmptyError())
}
})
}
.setNegativeButton(context.getString(R.string.no)) { dialog, _ -> dialog.dismiss() }.show()
}
// refactor as an UseCase later - see ChallengeDetailActivity
private fun showRemoveTasksDialog(callback: Action1<String>) {
AlertDialog.Builder(context)
.setTitle(context.getString(R.string.challenge_remove_tasks_title))
.setMessage(context.getString(R.string.challenge_remove_tasks_text))
.setPositiveButton(context.getString(R.string.remove_tasks)) { dialog, _ ->
callback.call("remove-all")
dialog.dismiss()
}
.setNegativeButton(context.getString(R.string.keep_tasks)) { dialog, _ ->
callback.call("keep-all")
dialog.dismiss()
}.show()
}
companion object {
fun showDialog(activity: Activity?, challengeRepository: ChallengeRepository, challenge: Challenge, challengeLeftAction: Action1<Challenge>) {
if (activity == null) {
return
}
val dialogLayout = activity.layoutInflater.inflate(R.layout.dialog_challenge_detail, null)
val challengeDetailDialogHolder = ChallengeDetailDialogHolder(dialogLayout, activity)
val builder = AlertDialog.Builder(activity)
.setView(dialogLayout)
challengeDetailDialogHolder.bind(builder.show(), challengeRepository, challenge, challengeLeftAction)
}
}
}

View file

@ -0,0 +1,299 @@
package com.habitrpg.android.habitica.ui.fragments.social.challenges
import android.graphics.Color
import android.os.Bundle
import android.support.v4.content.ContextCompat
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Button
import android.widget.ImageView
import android.widget.LinearLayout
import android.widget.TextView
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.extensions.inflate
import com.habitrpg.android.habitica.extensions.notNull
import com.habitrpg.android.habitica.helpers.RxErrorHandler
import com.habitrpg.android.habitica.models.social.Challenge
import com.habitrpg.android.habitica.models.tasks.Task
import com.habitrpg.android.habitica.ui.AvatarView
import com.habitrpg.android.habitica.ui.fragments.BaseMainFragment
import com.habitrpg.android.habitica.ui.helpers.MarkdownParser
import com.habitrpg.android.habitica.ui.helpers.bindView
import com.habitrpg.android.habitica.ui.views.HabiticaIconsHelper
import com.habitrpg.android.habitica.ui.views.social.UsernameLabel
import io.reactivex.functions.Action
import io.reactivex.functions.Consumer
import net.pherth.android.emoji_library.EmojiParser
import net.pherth.android.emoji_library.EmojiTextView
import java.util.*
import javax.inject.Inject
import android.graphics.PorterDuff
import android.graphics.PorterDuffColorFilter
import com.habitrpg.android.habitica.data.SocialRepository
import com.habitrpg.android.habitica.models.members.Member
class ChallengeDetailFragment: BaseMainFragment() {
@Inject
lateinit var challengeRepository: ChallengeRepository
@Inject
lateinit var socialRepository: SocialRepository
private val joinButtonWrapper: ViewGroup? by bindView(R.id.join_button_wrapper)
private val joinButton: Button? by bindView(R.id.join_button)
private val leaveButonWrapper: ViewGroup? by bindView(R.id.leave_button_wrapper)
private val leaveButton: Button? by bindView(R.id.leave_button)
private val challengeName: EmojiTextView? by bindView(R.id.challenge_name)
private val challengeDescription: EmojiTextView? by bindView(R.id.challenge_description)
private val challengeLeaderAvatarView: AvatarView? by bindView(R.id.creator_avatarview)
private val challengeLeaderLabel: UsernameLabel? by bindView(R.id.creator_label)
private val gemAmountView: TextView? by bindView(R.id.gem_amount)
private val gemAmountIconView: ImageView? by bindView(R.id.gem_amount_icon)
private val memberCountView: TextView? by bindView(R.id.participant_count)
private val memberCountIconView: ImageView? by bindView(R.id.participant_count_icon)
private val taskGrouplayout: LinearLayout? by bindView(R.id.task_group_layout)
var challengeID: String? = null
override fun injectFragment(component: AppComponent) {
component.inject(this)
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
super.onCreateView(inflater, container, savedInstanceState)
return container?.inflate(R.layout.fragment_challenge_detail)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
gemAmountIconView?.setImageBitmap(HabiticaIconsHelper.imageOfGem_36())
memberCountIconView?.setImageBitmap(HabiticaIconsHelper.imageOfParticipantIcon())
challengeID.notNull {
compositeSubscription.add(challengeRepository.getChallenge(it)
.doOnNext {
set(it)
}
.map {
return@map (it.leaderId ?: "")
}
.flatMap { creatorID ->
return@flatMap socialRepository.getMember(creatorID)
}
.subscribe(Consumer { set(it)}, RxErrorHandler.handleEmptyError()))
compositeSubscription.add(challengeRepository.getChallengeTasks(it).subscribe(Consumer { taskList ->
taskGrouplayout?.removeAllViewsInLayout()
val todos = ArrayList<Task>()
val habits = ArrayList<Task>()
val dailies = ArrayList<Task>()
val rewards = ArrayList<Task>()
for (entry in taskList) {
when (entry.type) {
Task.TYPE_TODO -> todos.add(entry)
Task.TYPE_HABIT -> habits.add(entry)
Task.TYPE_DAILY -> dailies.add(entry)
Task.TYPE_REWARD -> rewards.add(entry)
}
}
if (habits.size > 0) {
addHabits(habits)
}
if (dailies.size > 0) {
addDailys(dailies)
}
if (todos.size > 0) {
addTodos(todos)
}
if (rewards.size > 0) {
addRewards(rewards)
}
}, RxErrorHandler.handleEmptyError()))
compositeSubscription.add(challengeRepository.isChallengeMember(it).subscribe(Consumer { isMember ->
setJoined(isMember)
}, RxErrorHandler.handleEmptyError()))
}
refresh()
}
private fun refresh() {
challengeID.notNull {id ->
challengeRepository.retrieveChallenge(id)
.flatMap { challengeRepository.retrieveChallengeTasks(id) }
.subscribe(Consumer { }, RxErrorHandler.handleEmptyError(), Action { })
}
}
private fun set(challenge: Challenge) {
challengeName?.text = EmojiParser.parseEmojis(challenge.name)
challengeDescription?.text = MarkdownParser.parseMarkdown(challenge.description)
challengeLeaderLabel?.username = challenge.leaderName
gemAmountView?.text = challenge.prize.toString()
memberCountView?.text = challenge.memberCount.toString()
}
private fun set(creator: Member) {
challengeLeaderAvatarView?.setAvatar(creator)
challengeLeaderLabel?.tier = creator.contributor?.level ?: 0
challengeLeaderLabel?.username = creator.displayName
}
private fun setJoined(joined: Boolean) {
joinButton?.visibility = if (!joined) View.VISIBLE else View.GONE
joinButtonWrapper?.visibility = if (!joined) View.VISIBLE else View.GONE
leaveButton?.visibility = if (joined) View.VISIBLE else View.GONE
leaveButonWrapper?.visibility = if (joined) View.VISIBLE else View.GONE
}
private fun addHabits(habits: ArrayList<Task>) {
val taskGroup = taskGrouplayout?.inflate(R.layout.dialog_challenge_detail_task_group)
val groupName = taskGroup?.findViewById(R.id.task_group_name) as TextView?
val tasksLayout = taskGroup?.findViewById(R.id.tasks_layout) as LinearLayout?
groupName?.text = getLabelByTypeAndCount(Challenge.TASK_ORDER_HABITS, habits.size)
taskGroup?.findViewById<TextView>(R.id.task_count_view)?.text = habits.size.toString()
val size = habits.size
for (i in 0 until size) {
val task = habits[i]
val entry = tasksLayout?.inflate(R.layout.dialog_challenge_detail_habit)
val habitTitle = entry?.findViewById(R.id.habit_title) as TextView?
entry?.findViewById<ImageView>(R.id.lock_icon_plus)?.setImageBitmap(HabiticaIconsHelper.imageOfLocked(Color.parseColor("#DFDEDF")))
entry?.findViewById<ImageView>(R.id.lock_icon_minus)?.setImageBitmap(HabiticaIconsHelper.imageOfLocked(Color.parseColor("#DFDEDF")))
context.notNull {
if (task.up == true) {
entry?.findViewById<ImageView>(R.id.lock_icon_plus)?.setImageBitmap(HabiticaIconsHelper.imageOfLocked(Color.parseColor("#B3FFFFFF")))
entry?.findViewById<View>(R.id.lock_icon_background_plus)?.setBackgroundColor(ContextCompat.getColor(it, task.mediumTaskColor))
val drawable = ContextCompat.getDrawable(it, R.drawable.circle_white)
drawable?.colorFilter = PorterDuffColorFilter(ContextCompat.getColor(it, task.darkTaskColor), PorterDuff.Mode.MULTIPLY)
entry?.findViewById<View>(R.id.lock_icon_plus)?.setBackgroundDrawable(drawable)
}
if (task.down == true) {
entry?.findViewById<ImageView>(R.id.lock_icon_minus)?.setImageBitmap(HabiticaIconsHelper.imageOfLocked(Color.parseColor("#B3FFFFFF")))
entry?.findViewById<View>(R.id.lock_icon_background_minus)?.setBackgroundColor(ContextCompat.getColor(it, task.mediumTaskColor))
val drawable = ContextCompat.getDrawable(it, R.drawable.circle_white)
drawable?.colorFilter = PorterDuffColorFilter(ContextCompat.getColor(it, task.darkTaskColor), PorterDuff.Mode.MULTIPLY)
entry?.findViewById<View>(R.id.lock_icon_plus)?.setBackgroundDrawable(drawable)
}
}
habitTitle?.text = EmojiParser.parseEmojis(task.text)
tasksLayout?.addView(entry)
}
taskGrouplayout?.addView(taskGroup)
}
private fun addDailys(dailies: ArrayList<Task>) {
val taskGroup = taskGrouplayout?.inflate(R.layout.dialog_challenge_detail_task_group)
val groupName = taskGroup?.findViewById(R.id.task_group_name) as TextView?
val tasksLayout = taskGroup?.findViewById(R.id.tasks_layout) as LinearLayout?
val size = dailies.size
groupName?.text = getLabelByTypeAndCount(Challenge.TASK_ORDER_DAILYS, size)
taskGroup?.findViewById<TextView>(R.id.task_count_view)?.text = dailies.size.toString()
for (i in 0 until size) {
val task = dailies[i]
val entry = tasksLayout?.inflate(R.layout.dialog_challenge_detail_daily)
val title = entry?.findViewById(R.id.daily_title) as TextView?
title?.text = EmojiParser.parseEmojis(task.text)
entry?.findViewById<ImageView>(R.id.lock_icon)?.setImageBitmap(HabiticaIconsHelper.imageOfLocked(Color.parseColor("#949494")))
context.notNull {
entry?.findViewById<View>(R.id.lock_icon_background)?.setBackgroundColor(ContextCompat.getColor(it, task.mediumTaskColor))
val drawable = ContextCompat.getDrawable(it, R.drawable.circle_white)
drawable?.colorFilter = PorterDuffColorFilter(ContextCompat.getColor(it, task.extraLightTaskColor), PorterDuff.Mode.MULTIPLY)
entry?.findViewById<View>(R.id.lock_icon)?.setBackgroundDrawable(drawable)
}
if (task.checklist != null && task.checklist?.isEmpty() == false) {
val checklistIndicatorWrapper = entry?.findViewById<View>(R.id.checklistIndicatorWrapper)
checklistIndicatorWrapper?.visibility = View.VISIBLE
val checkListAllTextView = entry?.findViewById<View>(R.id.checkListAllTextView) as TextView?
checkListAllTextView?.text = task.checklist?.size.toString()
}
tasksLayout?.addView(entry)
}
taskGrouplayout?.addView(taskGroup)
}
private fun addTodos(todos: ArrayList<Task>) {
val taskGroup = taskGrouplayout?.inflate(R.layout.dialog_challenge_detail_task_group)
val groupName = taskGroup?.findViewById(R.id.task_group_name) as TextView?
val tasksLayout = taskGroup?.findViewById(R.id.tasks_layout) as LinearLayout?
val size = todos.size
groupName?.text = getLabelByTypeAndCount(Challenge.TASK_ORDER_TODOS, size)
taskGroup?.findViewById<TextView>(R.id.task_count_view)?.text = todos.size.toString()
for (i in 0 until size) {
val task = todos[i]
val entry = tasksLayout?.inflate(R.layout.dialog_challenge_detail_todo)
val title = entry?.findViewById(R.id.todo_title) as TextView?
title?.text = EmojiParser.parseEmojis(task.text)
entry?.findViewById<ImageView>(R.id.lock_icon)?.setImageBitmap(HabiticaIconsHelper.imageOfLocked(Color.parseColor("#949494")))
context.notNull {
entry?.findViewById<View>(R.id.lock_icon_background)?.setBackgroundColor(ContextCompat.getColor(it, task.mediumTaskColor))
val drawable = ContextCompat.getDrawable(it, R.drawable.circle_white)
drawable?.colorFilter = PorterDuffColorFilter(ContextCompat.getColor(it, task.extraLightTaskColor), PorterDuff.Mode.MULTIPLY)
entry?.findViewById<View>(R.id.lock_icon)?.setBackgroundDrawable(drawable)
}
if (task.checklist != null && task.checklist?.isEmpty() == false) {
val checklistIndicatorWrapper = entry?.findViewById<View>(R.id.checklistIndicatorWrapper)
checklistIndicatorWrapper?.visibility = View.VISIBLE
val checkListAllTextView = entry?.findViewById<View>(R.id.checkListAllTextView) as TextView?
checkListAllTextView?.text = task.checklist?.size.toString()
}
tasksLayout?.addView(entry)
}
taskGrouplayout?.addView(taskGroup)
}
private fun addRewards(rewards: ArrayList<Task>) {
val taskGroup = taskGrouplayout?.inflate(R.layout.dialog_challenge_detail_task_group)
val groupName = taskGroup?.findViewById(R.id.task_group_name) as TextView?
val tasksLayout = taskGroup?.findViewById(R.id.tasks_layout) as LinearLayout?
val size = rewards.size
groupName?.text = getLabelByTypeAndCount(Challenge.TASK_ORDER_REWARDS, size)
taskGroup?.findViewById<TextView>(R.id.task_count_view)?.text = rewards.size.toString()
for (i in 0 until size) {
val task = rewards[i]
val entry = tasksLayout?.inflate(R.layout.dialog_challenge_detail_reward)
(entry?.findViewById<View>(R.id.gold_icon) as ImageView?)?.setImageBitmap(HabiticaIconsHelper.imageOfGold())
val title = entry?.findViewById<View>(R.id.reward_title) as TextView?
title?.text = EmojiParser.parseEmojis(task.text)
tasksLayout?.addView(entry)
}
taskGrouplayout?.addView(taskGroup)
}
private fun getLabelByTypeAndCount(type: String, count: Int): String {
return when (type) {
Challenge.TASK_ORDER_DAILYS -> context?.getString(if (count == 1) R.string.daily else R.string.dailies)
Challenge.TASK_ORDER_HABITS -> context?.getString(if (count == 1) R.string.habit else R.string.habits)
Challenge.TASK_ORDER_REWARDS -> context?.getString(if (count == 1) R.string.reward else R.string.rewards)
else -> context?.getString(if (count == 1) R.string.todo else R.string.todos)
} ?: ""
}
}

View file

@ -71,10 +71,10 @@ internal class ChallengeFilterDialogHolder private constructor(view: View, priva
continue
}
val group = Group()
group.id = challenge.groupId
group.id = challenge.groupId ?: ""
group.name = challenge.groupName
groupMap[challenge.groupName] = group
groupMap[challenge.groupName ?: ""] = group
}
}
@ -92,11 +92,11 @@ internal class ChallengeFilterDialogHolder private constructor(view: View, priva
}
fun allClicked() {
private fun allClicked() {
this.adapter?.selectAll()
}
fun noneClicked() {
private fun noneClicked() {
this.adapter?.deSelectAll()
}

View file

@ -12,6 +12,7 @@ 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.extensions.inflate
import com.habitrpg.android.habitica.extensions.notNull
import com.habitrpg.android.habitica.helpers.RxErrorHandler
import com.habitrpg.android.habitica.models.social.Challenge
import com.habitrpg.android.habitica.modules.AppModule
@ -69,6 +70,8 @@ class ChallengeListFragment : BaseMainFragment(), SwipeRefreshLayout.OnRefreshLi
resetViews()
challengeAdapter = ChallengesListViewAdapter(null, true, viewUserChallengesOnly, userId)
challengeAdapter?.getOpenDetailFragmentFlowable()?.subscribe(Consumer { openDetailFragment(it) }, RxErrorHandler.handleEmptyError())
.notNull { compositeSubscription.add(it) }
swipeRefreshLayout?.setOnRefreshListener(this)
@ -81,9 +84,16 @@ class ChallengeListFragment : BaseMainFragment(), SwipeRefreshLayout.OnRefreshLi
recyclerView?.setEmptyView(emptyView)
recyclerView?.itemAnimator = SafeDefaultItemAnimator()
challengeAdapter?.updateUnfilteredData(challenges)
loadLocalChallenges()
}
private fun openDetailFragment(challengeID: String) {
val detailFragment = ChallengeDetailFragment()
detailFragment.challengeID = challengeID
this.activity?.displayFragment(detailFragment)
}
override fun injectFragment(component: AppComponent) {
component.inject(this)
}
@ -93,32 +103,32 @@ class ChallengeListFragment : BaseMainFragment(), SwipeRefreshLayout.OnRefreshLi
}
private fun setRefreshing(state: Boolean) {
if (swipeRefreshLayout != null && swipeRefreshLayout!!.visibility == View.VISIBLE) {
swipeRefreshLayout!!.isRefreshing = state
}
swipeRefreshLayout?.isRefreshing = state
}
private fun loadLocalChallenges() {
val observable: Flowable<RealmResults<Challenge>> = if (viewUserChallengesOnly && user != null) {
challengeRepository.getUserChallenges(user?.id)
challengeRepository.getUserChallenges(user?.id ?: "")
} else {
challengeRepository.challenges
challengeRepository.getChallenges()
}
observable.firstElement().subscribe(Consumer { challenges ->
compositeSubscription.add(observable.firstElement().subscribe(Consumer { challenges ->
if (challenges.size == 0) {
fetchOnlineChallenges()
}
this.challenges = challenges
challengeAdapter!!.updateUnfilteredData(challenges)
}, RxErrorHandler.handleEmptyError())
challengeAdapter?.updateUnfilteredData(challenges)
}, RxErrorHandler.handleEmptyError()))
}
private fun fetchOnlineChallenges() {
setRefreshing(true)
challengeRepository.retrieveChallenges(user).doOnComplete {
setRefreshing(false)
} .subscribe(Consumer { }, RxErrorHandler.handleEmptyError())
user.notNull {
challengeRepository.retrieveChallenges(it).doOnComplete {
setRefreshing(false)
} .subscribe(Consumer { }, RxErrorHandler.handleEmptyError())
}
}
override fun customTitle(): String {
@ -141,13 +151,15 @@ class ChallengeListFragment : BaseMainFragment(), SwipeRefreshLayout.OnRefreshLi
}
private fun showFilterDialog() {
ChallengeFilterDialogHolder.showDialog(getActivity()!!,
challenges ?: emptyList(),
filterOptions, object : Action1<ChallengeFilterOptions> {
override fun call(t: ChallengeFilterOptions) {
changeFilter(t)
}
})
activity.notNull {
ChallengeFilterDialogHolder.showDialog(it,
challenges ?: emptyList(),
filterOptions, object : Action1<ChallengeFilterOptions> {
override fun call(t: ChallengeFilterOptions) {
changeFilter(t)
}
})
}
}
private fun changeFilter(challengeFilterOptions: ChallengeFilterOptions) {
@ -159,7 +171,7 @@ class ChallengeListFragment : BaseMainFragment(), SwipeRefreshLayout.OnRefreshLi
// 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.
val id = item!!.itemId
val id = item?.itemId
when (id) {
R.id.action_create_challenge -> {

View file

@ -1,108 +0,0 @@
package com.habitrpg.android.habitica.ui.fragments.social.challenges;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import com.habitrpg.android.habitica.R;
import com.habitrpg.android.habitica.components.AppComponent;
import com.habitrpg.android.habitica.models.tasks.Task;
import com.habitrpg.android.habitica.models.user.User;
import com.habitrpg.android.habitica.modules.AppModule;
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.fragments.BaseFragment;
import com.habitrpg.android.habitica.ui.helpers.SafeDefaultItemAnimator;
import java.util.List;
import javax.inject.Inject;
import javax.inject.Named;
public class ChallengeTasksRecyclerViewFragment extends BaseFragment {
public RecyclerView recyclerView;
public BaseTasksRecyclerViewAdapter recyclerAdapter;
@Inject
@Named(AppModule.NAMED_USER_ID)
String userID;
List<Task> tasksOnInitialize;
LinearLayoutManager layoutManager = null;
private User user;
private View view;
public static ChallengeTasksRecyclerViewFragment newInstance(User user, List<Task> tasks) {
ChallengeTasksRecyclerViewFragment fragment = new ChallengeTasksRecyclerViewFragment();
fragment.setRetainInstance(true);
fragment.user = user;
fragment.tasksOnInitialize = tasks;
if (tasks.size() != 0 && fragment.recyclerAdapter != null) {
fragment.recyclerAdapter.setTasks(tasks);
}
return fragment;
}
public void setInnerAdapter() {
this.recyclerAdapter = new ChallengeTasksRecyclerViewAdapter(null, 0, getContext(), userID, null, true, true);
if (user != null && user.getPreferences() != null) {
this.recyclerAdapter.setDailyResetOffset(user.getPreferences().getDayStart());
}
if (tasksOnInitialize != null && tasksOnInitialize.size() != 0 && recyclerAdapter != null && recyclerAdapter.getItemCount() == 0) {
recyclerAdapter.setTasks(tasksOnInitialize);
}
}
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
view = inflater.inflate(R.layout.fragment_recyclerview, container, false);
recyclerView = (RecyclerView) view.findViewById(R.id.recyclerView);
android.support.v4.app.FragmentActivity context = getActivity();
layoutManager = (LinearLayoutManager) recyclerView.getLayoutManager();
if (layoutManager == null) {
layoutManager = new LinearLayoutManager(context);
recyclerView.setLayoutManager(layoutManager);
}
if (recyclerView.getAdapter() == null) {
this.setInnerAdapter();
}
recyclerView.setItemAnimator(new SafeDefaultItemAnimator());
return view;
}
@Override
public void injectFragment(AppComponent component) {
component.inject(this);
}
@Override
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
recyclerView.setAdapter(recyclerAdapter);
}
@Override
public String getDisplayedClassName() {
return "ChallengeTasks" + super.getDisplayedClassName();
}
// region Challenge specific RecyclerViewAdapters
// endregion
}

View file

@ -1,6 +1,5 @@
package com.habitrpg.android.habitica.ui.fragments.social.challenges
import android.content.Intent
import android.os.Bundle
import android.support.v4.app.Fragment
import android.support.v4.app.FragmentStatePagerAdapter
@ -12,17 +11,9 @@ import android.view.ViewGroup
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.commands.ShowChallengeDetailActivityCommand
import com.habitrpg.android.habitica.events.commands.ShowChallengeDetailDialogCommand
import com.habitrpg.android.habitica.helpers.RxErrorHandler
import com.habitrpg.android.habitica.models.social.Challenge
import com.habitrpg.android.habitica.ui.activities.ChallengeDetailActivity
import com.habitrpg.android.habitica.ui.fragments.BaseMainFragment
import com.habitrpg.android.habitica.ui.helpers.bindView
import com.habitrpg.android.habitica.utils.Action1
import io.reactivex.functions.Consumer
import org.greenrobot.eventbus.Subscribe
import com.habitrpg.android.habitica.ui.helpers.resetViews
import javax.inject.Inject
@ -33,30 +24,27 @@ class ChallengesOverviewFragment : BaseMainFragment() {
private val viewPager: ViewPager? by bindView(R.id.viewPager)
var statePagerAdapter: FragmentStatePagerAdapter? = null
private var userChallengesFragment: ChallengeListFragment? = null
private var availableChallengesFragment: ChallengeListFragment? = null
private var userChallengesFragment: ChallengeListFragment? = ChallengeListFragment()
private var availableChallengesFragment: ChallengeListFragment? = ChallengeListFragment()
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
this.usesTabLayout = true
super.onCreateView(inflater, container, savedInstanceState)
val v = inflater.inflate(R.layout.fragment_viewpager, container, false)
return v
return inflater.inflate(R.layout.fragment_viewpager, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
setViewPagerAdapter()
resetViews()
userChallengesFragment = ChallengeListFragment()
userChallengesFragment?.user = this.user
userChallengesFragment?.setViewUserChallengesOnly(true)
availableChallengesFragment = ChallengeListFragment()
availableChallengesFragment?.user = this.user
availableChallengesFragment?.setViewUserChallengesOnly(false)
setViewPagerAdapter()
}
override fun onDestroy() {
@ -98,28 +86,7 @@ class ChallengesOverviewFragment : BaseMainFragment() {
}
viewPager?.adapter = statePagerAdapter
tabLayout?.setupWithViewPager(viewPager)
}
@Subscribe
fun onEvent(cmd: ShowChallengeDetailDialogCommand) {
challengeRepository.getChallenge(cmd.challengeId).firstElement().subscribe(Consumer { challenge ->
ChallengeDetailDialogHolder.showDialog(activity, challengeRepository, challenge,
object: Action1<Challenge> {
override fun call(t: Challenge) {
}
})
}, RxErrorHandler.handleEmptyError())
}
@Subscribe
fun onEvent(cmd: ShowChallengeDetailActivityCommand) {
val bundle = Bundle()
bundle.putString(ChallengeDetailActivity.CHALLENGE_ID, cmd.challengeId)
val intent = Intent(activity, ChallengeDetailActivity::class.java)
intent.putExtras(bundle)
activity?.startActivity(intent)
statePagerAdapter?.notifyDataSetChanged()
}
override fun customTitle(): String {

View file

@ -19,7 +19,7 @@ class CollapsibleSectionView(context: Context?, attrs: AttributeSet?) : LinearLa
private val titleView: LinearLayout by bindView(R.id.title_view)
private val titleLabel: TextView by bindView(R.id.titleTextView)
private val caretView: ImageView by bindView(R.id.caretView)
public val infoIconView: ImageView by bindView(R.id.infoIconView)
val infoIconView: ImageView by bindView(R.id.infoIconView)
private var preferences: SharedPreferences? = null
private val padding = context?.resources?.getDimension(R.dimen.spacing_large)?.toInt() ?: 0
@ -31,7 +31,7 @@ class CollapsibleSectionView(context: Context?, attrs: AttributeSet?) : LinearLa
titleLabel.text = value
}
var isCollapsed = false
private var isCollapsed = false
set(value) {
field = value
if (value) {
@ -128,7 +128,7 @@ class CollapsibleSectionView(context: Context?, attrs: AttributeSet?) : LinearLa
}
if (attributes?.getBoolean(R.styleable.CollapsibleSectionView_hasAdditionalInfo, false) == true) {
infoIconView.setImageBitmap(HabiticaIconsHelper.imageOfInfoIcon())
infoIconView.setImageBitmap(HabiticaIconsHelper.imageOfInfoIcon(ContextCompat.getColor(context, R.color.brand_200)))
} else {
infoIconView.visibility = View.GONE
}

View file

@ -3,8 +3,11 @@ package com.habitrpg.android.habitica.ui.views;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.PointF;
import android.graphics.RectF;
import android.support.annotation.ColorInt;
import android.support.annotation.ColorRes;
/**
* Created by phillip on 05.09.17.
@ -262,7 +265,7 @@ public class HabiticaIconsHelper {
imageOfItemIndicatorNumber = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(imageOfItemIndicatorNumber);
canvas.scale(displayDensity, displayDensity);
HabiticaIcons.drawItemIndicator(canvas, false, false);
HabiticaIcons.drawItemIndicator(canvas, Color.parseColor("#C3C0C7"), false, false);
return imageOfItemIndicatorNumber;
}
@ -276,8 +279,7 @@ public class HabiticaIconsHelper {
imageOfItemIndicatorLocked = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(imageOfItemIndicatorLocked);
canvas.scale(displayDensity, displayDensity);
HabiticaIcons.drawItemIndicator(canvas, true, false);
HabiticaIcons.drawItemIndicator(canvas, Color.parseColor("#C3C0C7"), true, false);
return imageOfItemIndicatorLocked;
}
@ -290,7 +292,7 @@ public class HabiticaIconsHelper {
imageOfItemIndicatorLimited = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(imageOfItemIndicatorLimited);
canvas.scale(displayDensity, displayDensity);
HabiticaIcons.drawItemIndicator(canvas, false, true);
HabiticaIcons.drawItemIndicator(canvas, Color.parseColor("#C3C0C7"), false, true);
return imageOfItemIndicatorLimited;
}
@ -474,7 +476,7 @@ public class HabiticaIconsHelper {
}
private static Bitmap imageOfInfoIcon = null;
public static Bitmap imageOfInfoIcon() {
public static Bitmap imageOfInfoIcon(@ColorInt int iconColor) {
if (imageOfInfoIcon != null)
return imageOfInfoIcon;
@ -482,7 +484,7 @@ public class HabiticaIconsHelper {
imageOfInfoIcon = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(imageOfInfoIcon);
canvas.scale(displayDensity, displayDensity);
HabiticaIcons.drawInfoIcon(canvas);
HabiticaIcons.drawInfoIcon(canvas, iconColor);
return imageOfInfoIcon;
}
@ -576,4 +578,27 @@ public class HabiticaIconsHelper {
return imageOfRage;
}
public static Bitmap imageOfLocked(@ColorInt int lockColor) {
Bitmap imageOfLocked = Bitmap.createBitmap(scaleSize(15), scaleSize(17), Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(imageOfLocked);
canvas.scale(displayDensity, displayDensity);
HabiticaIcons.drawLocked(canvas, lockColor);
return imageOfLocked;
}
private static Bitmap imageOfParticipantsIcon = null;
public static Bitmap imageOfParticipantIcon() {
if (imageOfParticipantsIcon != null)
return imageOfParticipantsIcon;
int size = scaleSize(20);
imageOfParticipantsIcon = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(imageOfParticipantsIcon);
canvas.scale(displayDensity, displayDensity);
HabiticaIcons.drawParticipantsIcon(canvas);
return imageOfParticipantsIcon;
}
}

View file

@ -1,125 +0,0 @@
package com.habitrpg.android.habitica.utils;
import android.text.TextUtils;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
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 java.lang.reflect.Type;
import java.util.Date;
public class ChallengeDeserializer implements JsonDeserializer<Challenge>, JsonSerializer<Challenge> {
@Override
public Challenge deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
JsonObject jsonObject = json.getAsJsonObject();
Challenge challenge = new Challenge();
challenge.id = jsonObject.get("id").getAsString();
challenge.name = jsonObject.get("name").getAsString();
if (jsonObject.has("sortName")) {
challenge.shortName = jsonObject.get("shortName").getAsString();
}
challenge.description = jsonObject.get("description").getAsString();
challenge.memberCount = jsonObject.get("memberCount").getAsInt();
JsonElement prizeElement = jsonObject.get("prize");
if (!prizeElement.isJsonNull()) {
challenge.prize = prizeElement.getAsInt();
}
challenge.official = jsonObject.get("official").getAsBoolean();
JsonElement leaderElement = jsonObject.get("leader");
if (leaderElement != null && !leaderElement.isJsonNull()) {
JsonObject leaderObj = leaderElement.getAsJsonObject();
if (leaderObj != null) {
JsonObject profile = leaderObj.get("profile").getAsJsonObject();
if (profile != null) {
challenge.leaderName = profile.get("name").getAsString();
JsonElement id = leaderObj.get("id");
if (id == null) {
id = leaderObj.get("_id");
}
if (id != null) {
challenge.leaderId = id.getAsString();
}
}
}
}
if (jsonObject.has("createdAt")) {
challenge.createdAt = context.deserialize(jsonObject.get("createdAt"), Date.class);
}
if (jsonObject.has("updatedAt")) {
challenge.updatedAt = context.deserialize(jsonObject.get("updatedAt"), Date.class);
}
JsonElement groupElement = jsonObject.get("group");
if (groupElement != null && !groupElement.isJsonNull()) {
JsonObject groupObj = groupElement.getAsJsonObject();
if (groupObj != null) {
challenge.groupName = groupObj.get("name").getAsString();
challenge.groupId = groupObj.get("_id").getAsString();
}
}
JsonElement tasksOrderElement = jsonObject.get("tasksOrder");
if (tasksOrderElement != null && !tasksOrderElement.isJsonNull()) {
JsonObject tasksOrderObj = tasksOrderElement.getAsJsonObject();
challenge.todoList = getTaskArrayAsString(context, tasksOrderObj, Challenge.TASK_ORDER_TODOS);
challenge.dailyList = getTaskArrayAsString(context, tasksOrderObj, Challenge.TASK_ORDER_DAILYS);
challenge.habitList = getTaskArrayAsString(context, tasksOrderObj, Challenge.TASK_ORDER_HABITS);
challenge.rewardList = getTaskArrayAsString(context, tasksOrderObj, Challenge.TASK_ORDER_REWARDS);
}
return challenge;
}
private String getTaskArrayAsString(JsonDeserializationContext context, JsonObject tasksOrderObj, String taskType) {
if (tasksOrderObj.has(taskType)) {
JsonElement jsonElement = tasksOrderObj.get(taskType);
String[] taskArray = context.deserialize(jsonElement, String[].class);
return TextUtils.join(",", taskArray);
}
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

@ -0,0 +1,124 @@
package com.habitrpg.android.habitica.utils
import android.text.TextUtils
import com.google.gson.JsonDeserializationContext
import com.google.gson.JsonDeserializer
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 java.lang.reflect.Type
import java.util.Date
class ChallengeDeserializer : JsonDeserializer<Challenge>, JsonSerializer<Challenge> {
@Throws(JsonParseException::class)
override fun deserialize(json: JsonElement, typeOfT: Type, context: JsonDeserializationContext): Challenge {
val jsonObject = json.asJsonObject
val challenge = Challenge()
challenge.id = jsonObject.get("id").asString
challenge.name = jsonObject.get("name").asString
if (jsonObject.has("sortName")) {
challenge.shortName = jsonObject.get("shortName").asString
}
challenge.description = jsonObject.get("description").asString
challenge.memberCount = jsonObject.get("memberCount").asInt
val prizeElement = jsonObject.get("prize")
if (!prizeElement.isJsonNull) {
challenge.prize = prizeElement.asInt
}
challenge.official = jsonObject.get("official").asBoolean
val leaderElement = jsonObject.get("leader")
if (leaderElement != null && !leaderElement.isJsonNull) {
val leaderObj = leaderElement.asJsonObject
if (leaderObj != null) {
val profile = leaderObj.get("profile").asJsonObject
if (profile != null) {
challenge.leaderName = profile.get("name").asString
var id: JsonElement? = leaderObj.get("id")
if (id == null) {
id = leaderObj.get("_id")
}
if (id != null) {
challenge.leaderId = id.asString
}
}
}
}
if (jsonObject.has("createdAt")) {
challenge.createdAt = context.deserialize(jsonObject.get("createdAt"), Date::class.java)
}
if (jsonObject.has("updatedAt")) {
challenge.updatedAt = context.deserialize(jsonObject.get("updatedAt"), Date::class.java)
}
val groupElement = jsonObject.get("group")
if (groupElement != null && !groupElement.isJsonNull) {
val groupObj = groupElement.asJsonObject
if (groupObj != null) {
challenge.groupName = groupObj.get("name").asString
challenge.groupId = groupObj.get("_id").asString
}
}
val tasksOrderElement = jsonObject.get("tasksOrder")
if (tasksOrderElement != null && !tasksOrderElement.isJsonNull) {
val tasksOrderObj = tasksOrderElement.asJsonObject
challenge.todoList = getTaskArrayAsString(context, tasksOrderObj, Challenge.TASK_ORDER_TODOS)
challenge.dailyList = getTaskArrayAsString(context, tasksOrderObj, Challenge.TASK_ORDER_DAILYS)
challenge.habitList = getTaskArrayAsString(context, tasksOrderObj, Challenge.TASK_ORDER_HABITS)
challenge.rewardList = getTaskArrayAsString(context, tasksOrderObj, Challenge.TASK_ORDER_REWARDS)
}
return challenge
}
private fun getTaskArrayAsString(context: JsonDeserializationContext, tasksOrderObj: JsonObject, taskType: String): String {
if (tasksOrderObj.has(taskType)) {
val jsonElement = tasksOrderObj.get(taskType)
val taskArray = context.deserialize<Array<String>>(jsonElement, Array<String>::class.java)
return TextUtils.join(",", taskArray)
}
return ""
}
override fun serialize(src: Challenge, typeOfSrc: Type, context: JsonSerializationContext): JsonElement {
val obj = JsonObject()
obj.addProperty("id", src.id)
obj.addProperty("name", src.name)
obj.addProperty("shortName", src.shortName)
obj.addProperty("description", src.description)
obj.addProperty("memberCount", src.memberCount)
obj.addProperty("prize", src.prize)
obj.addProperty("official", src.official)
obj.addProperty("group", src.groupId)
obj.add("tasksOrder", context.serialize(src.tasksOrder))
return obj
}
}

View file

@ -1,57 +0,0 @@
package com.habitrpg.android.habitica.utils;
import android.support.annotation.Nullable;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonParseException;
import com.habitrpg.android.habitica.models.social.Challenge;
import java.lang.reflect.Type;
import java.util.List;
import io.realm.Realm;
import io.realm.RealmList;
public class ChallengeListDeserializer implements JsonDeserializer<List<Challenge>> {
@Override
public List<Challenge> deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
RealmList<Challenge> challenges = new RealmList<>();
Realm realm = Realm.getDefaultInstance();
List<Challenge> existingChallenges = realm.copyFromRealm(realm.where(Challenge.class).findAll());
realm.close();
for (JsonElement element : json.getAsJsonArray()) {
Challenge challenge;
if (element.isJsonObject()) {
challenge = context.deserialize(element, Challenge.class);
Challenge existingChallenge = findExistingChallenge(existingChallenges, challenge.id);
if (existingChallenge != null) {
challenge.isParticipating = existingChallenge.isParticipating;
}
} else {
challenge = findExistingChallenge(existingChallenges, element.getAsString());
if (challenge == null) {
challenge = new Challenge();
challenge.id = element.getAsString();
}
challenge.isParticipating = true;
}
challenges.add(challenge);
}
return challenges;
}
@Nullable
private Challenge findExistingChallenge(List<Challenge> existingChallenges, String id) {
for (Challenge challenge : existingChallenges) {
if (id.equals(challenge.id)) {
return challenge;
}
}
return null;
}
}

View file

@ -0,0 +1,37 @@
package com.habitrpg.android.habitica.utils
import com.google.gson.JsonDeserializationContext
import com.google.gson.JsonDeserializer
import com.google.gson.JsonElement
import com.google.gson.JsonParseException
import com.habitrpg.android.habitica.models.social.Challenge
import java.lang.reflect.Type
import io.realm.Realm
import io.realm.RealmList
class ChallengeListDeserializer : JsonDeserializer<List<Challenge>> {
@Throws(JsonParseException::class)
override fun deserialize(json: JsonElement, typeOfT: Type, context: JsonDeserializationContext): List<Challenge> {
val challenges = RealmList<Challenge>()
val realm = Realm.getDefaultInstance()
realm.close()
for (element in json.asJsonArray) {
var challenge: Challenge?
if (element.isJsonObject) {
challenge = context.deserialize<Challenge>(element, Challenge::class.java)
} else {
challenge = Challenge()
challenge.id = element.asString
}
if (challenge != null) {
challenges.add(challenge)
}
}
return challenges
}
}

View file

@ -9,7 +9,7 @@ import com.habitrpg.android.habitica.models.PushDevice
import com.habitrpg.android.habitica.models.Tag
import com.habitrpg.android.habitica.models.inventory.Quest
import com.habitrpg.android.habitica.models.invitations.Invitations
import com.habitrpg.android.habitica.models.social.Challenge
import com.habitrpg.android.habitica.models.social.ChallengeMembership
import com.habitrpg.android.habitica.models.social.UserParty
import com.habitrpg.android.habitica.models.tasks.TasksOrder
import com.habitrpg.android.habitica.models.user.*
@ -84,7 +84,10 @@ class UserDeserializer : JsonDeserializer<User> {
user.tasksOrder = context.deserialize(obj.get("tasksOrder"), TasksOrder::class.java)
}
if (obj.has("challenges")) {
user.challenges = context.deserialize(obj.get("challenges"), object : TypeToken<RealmList<Challenge>>() {}.type)
user.challenges = RealmList()
obj.getAsJsonArray("challenges").forEach {
user.challenges.add(ChallengeMembership(user.id, it.asString))
}
}
if (obj.has("purchased")) {
user.purchased = context.deserialize(obj.get("purchased"), Purchases::class.java)