Implement party creation

This commit is contained in:
Phillip Thelen 2019-03-05 13:14:42 +01:00
parent 81a9de01b9
commit 2edf68b474
21 changed files with 336 additions and 230 deletions

View file

@ -122,7 +122,7 @@
</activity> </activity>
<activity <activity
android:name=".ui.activities.GroupFormActivity" android:name=".ui.activities.GroupFormActivity"
android:theme="@style/AppThemeWithActionBarBlackText" android:theme="@style/AppTheme.NoActionBar.Transparent"
android:parentActivityName=".ui.activities.MainActivity" android:parentActivityName=".ui.activities.MainActivity"
android:screenOrientation="portrait" android:screenOrientation="portrait"
tools:ignore="UnusedAttribute"> tools:ignore="UnusedAttribute">

View file

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:top="18dp">
<shape android:shape="rectangle">
<solid android:color="@color/gray_500" />
<corners android:radius="4dp" android:topLeftRadius="8dp" android:topRightRadius="8dp" />
</shape>
</item>
<item android:bottom="2dp" android:top="18dp">
<shape android:shape="rectangle">
<solid android:color="@color/gray_700" />
<corners android:topLeftRadius="4dp" android:topRightRadius="4dp" android:bottomLeftRadius="2dp" android:bottomRightRadius="2dp" />
</shape>
</item>
</layer-list>

View file

@ -2,62 +2,130 @@
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android" <ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent"
xmlns:tools="http://schemas.android.com/tools"
android:background="@color/gray_700">
<LinearLayout <LinearLayout
android:orientation="vertical" android:orientation="vertical"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content"> android:layout_height="wrap_content"
android:paddingTop="30dp"
android:background="@color/white">
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ImageButton
android:id="@+id/cancel_button"
android:layout_width="50dp"
android:layout_height="wrap_content"
android:src="@drawable/ic_close_purple_300_24dp"
style="@style/Base.Widget.AppCompat.Button.Borderless"
android:contentDescription="@string/cancel" />
<Space
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"/>
<Button
android:id="@+id/save_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/create"
style="@style/HabiticaButton"
android:textColor="@color/brand_400"
android:paddingEnd="@dimen/spacing_large"
android:paddingStart="@dimen/spacing_large"
android:minWidth="20dp"/>
</LinearLayout>
<com.google.android.material.textfield.TextInputLayout <com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
app:hintTextAppearance="@style/TextAppearance.AppCompat"> app:hintTextAppearance="@style/TextAppearance.AppCompat"
<EditText android:layout_marginTop="@dimen/spacing_small"
android:layout_marginLeft="@dimen/spacing_large"
android:layout_marginRight="@dimen/spacing_large"
android:background="@drawable/edittext"
android:textColorHint="@color/gray_50">
<MultiAutoCompleteTextView
android:id="@+id/group_name_edittext" android:id="@+id/group_name_edittext"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="center_horizontal" android:layout_gravity="center_horizontal"
android:hint="@string/name" android:hint="@string/name"
android:textColor="@android:color/black" android:textColor="@android:color/black"
android:inputType="textCapSentences" /> android:inputType="textCapSentences"
android:background="@color/transparent"
android:paddingStart="@dimen/spacing_large"
android:paddingEnd="@dimen/spacing_large"
android:paddingBottom="@dimen/spacing_large"
android:paddingTop="20dp"
android:textColorHint="@color/gray_50"
/>
</com.google.android.material.textfield.TextInputLayout> </com.google.android.material.textfield.TextInputLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="72dp"
android:orientation="horizontal" >
<ImageButton
android:id="@+id/emoji.toggle.btn"
android:layout_width="48dp"
android:layout_height="48dp"
android:src="@drawable/ic_emoticon_grey600_24dp"
android:background="@drawable/md_transparent"
android:contentDescription="Toogle Emoji"
android:focusable="true"
android:focusableInTouchMode="true" />
<com.google.android.material.textfield.TextInputLayout <com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
app:hintTextAppearance="@style/TextAppearance.AppCompat"> app:hintTextAppearance="@style/TextAppearance.AppCompat"
android:layout_marginTop="@dimen/spacing_large"
android:layout_marginLeft="@dimen/spacing_large"
android:layout_marginRight="@dimen/spacing_large"
android:background="@drawable/edittext"
android:textColorHint="@color/gray_50">
<net.pherth.android.emoji_library.EmojiEditText <MultiAutoCompleteTextView
android:id="@+id/group_description_edittext" android:id="@+id/group_description_edittext"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="center_horizontal" android:layout_gravity="center_horizontal"
android:hint="@string/description" android:hint="@string/description"
android:textColor="@android:color/black" android:textColor="@android:color/black"
android:inputType="textCapSentences|textMultiLine" /> android:inputType="textCapSentences|textMultiLine"
android:background="@color/transparent"
android:paddingStart="@dimen/spacing_large"
android:paddingEnd="@dimen/spacing_large"
android:paddingBottom="@dimen/spacing_large"
android:paddingTop="20dp"
android:textColorHint="@color/gray_50"
android:minLines="3"
android:gravity="top" />
</com.google.android.material.textfield.TextInputLayout> </com.google.android.material.textfield.TextInputLayout>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/spacing_large"
android:layout_marginLeft="@dimen/spacing_large"
android:layout_marginRight="@dimen/spacing_large">
<TextView
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="wrap_content"
android:text="@string/only_leader_create_challenge"/>
<Switch
android:id="@+id/leader_create_challenge_switch"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
/>
</LinearLayout>
<View
android:id="@+id/privacy_separator"
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="#1e000000"
android:layout_marginTop="@dimen/spacing_medium"
android:layout_marginBottom="@dimen/spacing_medium"
tools:visibility="gone" />
<LinearLayout <LinearLayout
android:orientation="horizontal" android:orientation="horizontal"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:id="@+id/privacyWrapper"> android:id="@+id/privacyWrapper"
android:layout_marginLeft="@dimen/spacing_large"
android:layout_marginRight="@dimen/spacing_large"
tools:visibility="gone">
<TextView <TextView
android:layout_width="wrap_content" android:layout_width="wrap_content"
@ -70,5 +138,10 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:id="@+id/privacySpinner" /> android:id="@+id/privacySpinner" />
</LinearLayout> </LinearLayout>
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="#1e000000"
android:layout_marginTop="@dimen/spacing_medium" />
</LinearLayout> </LinearLayout>
</ScrollView> </ScrollView>

View file

@ -111,7 +111,7 @@
style="@style/HabiticaButton.Gray.600" style="@style/HabiticaButton.Gray.600"
android:background="@drawable/layout_rounded_bg_gray_700" android:background="@drawable/layout_rounded_bg_gray_700"
android:textSize="16sp" android:textSize="16sp"
android:text="@string/create_party_website" android:text="@string/create_party"
android:layout_marginTop="@dimen/spacing_large"/> android:layout_marginTop="@dimen/spacing_large"/>
</LinearLayout> </LinearLayout>
<View <View

View file

@ -10,4 +10,9 @@
android:orderInCategory="1" android:orderInCategory="1"
app:showAsAction="never" app:showAsAction="never"
android:title="@string/action_edit" /> android:title="@string/action_edit" />
<item
android:id="@+id/menu.guild.leave"
android:orderInCategory="1"
app:showAsAction="never"
android:title="@string/leave" />
</menu> </menu>

View file

@ -256,4 +256,8 @@
android:name="userID" android:name="userID"
app:argType="string" /> app:argType="string" />
</activity> </activity>
<activity
android:id="@+id/groupFormActivity"
android:name="com.habitrpg.android.habitica.ui.activities.GroupFormActivity"
android:label="GroupFormActivity" />
</navigation> </navigation>

View file

@ -860,5 +860,8 @@
<string name="discover">Discover</string> <string name="discover">Discover</string>
<string name="damage_paused">Damage paused</string> <string name="damage_paused">Damage paused</string>
<string name="preference_push_important_announcements">Important Announcements</string> <string name="preference_push_important_announcements">Important Announcements</string>
<string name="create">Create</string>
<string name="only_leader_create_challenge">Only leader can create Challenges</string>
<string name="create_party">Create Party</string>
</resources> </resources>

View file

@ -201,6 +201,9 @@ public interface ApiService {
@GET("groups/{gid}") @GET("groups/{gid}")
Flowable<HabitResponse<Group>> getGroup(@Path("gid") String groupId); Flowable<HabitResponse<Group>> getGroup(@Path("gid") String groupId);
@POST("groups")
Flowable<HabitResponse<Group>> createGroup(@Body Group item);
@PUT("groups/{id}") @PUT("groups/{id}")
Flowable<HabitResponse<Void>> updateGroup(@Path("id") String id, @Body Group item); Flowable<HabitResponse<Void>> updateGroup(@Path("id") String id, @Body Group item);

View file

@ -129,6 +129,7 @@ interface ApiClient {
fun getGroup(groupId: String): Flowable<Group> fun getGroup(groupId: String): Flowable<Group>
fun createGroup(group: Group): Flowable<Group>
fun updateGroup(id: String, item: Group): Flowable<Void> fun updateGroup(id: String, item: Group): Flowable<Void>
fun listGroupChat(groupId: String): Flowable<List<ChatMessage>> fun listGroupChat(groupId: String): Flowable<List<ChatMessage>>

View file

@ -36,7 +36,8 @@ interface SocialRepository : BaseRepository {
fun joinGroup(id: String?): Flowable<Group> fun joinGroup(id: String?): Flowable<Group>
fun updateGroup(group: Group?, name: String?, description: String?, leader: String?, privacy: String?): Flowable<Void> fun createGroup(name: String?, description: String?, leader: String?, type: String?, privacy: String?, leaderCreateChallenge: Boolean?): Flowable<Group>
fun updateGroup(group: Group?, name: String?, description: String?, leader: String?, leaderCreateChallenge: Boolean?): Flowable<Void>
fun retrieveGroups(type: String): Flowable<List<Group>> fun retrieveGroups(type: String): Flowable<List<Group>>
fun getGroups(type: String): Flowable<RealmResults<Group>> fun getGroups(type: String): Flowable<RealmResults<Group>>

View file

@ -63,6 +63,7 @@ class ApiClientImpl//private OnHabitsAPIResult mResultListener;
//private HostConfig mConfig; //private HostConfig mConfig;
(private val gsonConverter: GsonConverterFactory, override val hostConfig: HostConfig, private val crashlyticsProxy: CrashlyticsProxy, private val popupNotificationsManager: PopupNotificationsManager, private val context: Context) : Consumer<Throwable>, ApiClient { (private val gsonConverter: GsonConverterFactory, override val hostConfig: HostConfig, private val crashlyticsProxy: CrashlyticsProxy, private val popupNotificationsManager: PopupNotificationsManager, private val context: Context) : Consumer<Throwable>, ApiClient {
private lateinit var retrofitAdapter: Retrofit private lateinit var retrofitAdapter: Retrofit
// I think we don't need the ApiClientImpl anymore we could just use ApiService // I think we don't need the ApiClientImpl anymore we could just use ApiService
@ -476,6 +477,10 @@ class ApiClientImpl//private OnHabitsAPIResult mResultListener;
return apiService.getGroup(groupId).compose(configureApiCallObserver()) return apiService.getGroup(groupId).compose(configureApiCallObserver())
} }
override fun createGroup(group: Group): Flowable<Group> {
return apiService.createGroup(group).compose(configureApiCallObserver())
}
override fun updateGroup(id: String, item: Group): Flowable<Void> { override fun updateGroup(id: String, item: Group): Flowable<Void> {
return apiService.updateGroup(id, item).compose(configureApiCallObserver()) return apiService.updateGroup(id, item).compose(configureApiCallObserver())
} }

View file

@ -19,6 +19,7 @@ import io.realm.RealmResults
class SocialRepositoryImpl(localRepository: SocialLocalRepository, apiClient: ApiClient, userID: String) : BaseRepositoryImpl<SocialLocalRepository>(localRepository, apiClient, userID), SocialRepository { class SocialRepositoryImpl(localRepository: SocialLocalRepository, apiClient: ApiClient, userID: String) : BaseRepositoryImpl<SocialLocalRepository>(localRepository, apiClient, userID), SocialRepository {
override fun getGroupMembership(id: String): Flowable<GroupMembership> { override fun getGroupMembership(id: String): Flowable<GroupMembership> {
return localRepository.getGroupMembership(userID, id) return localRepository.getGroupMembership(userID, id)
} }
@ -131,7 +132,19 @@ class SocialRepositoryImpl(localRepository: SocialLocalRepository, apiClient: Ap
} }
} }
override fun updateGroup(group: Group?, name: String?, description: String?, leader: String?, privacy: String?): Flowable<Void> { override fun createGroup(name: String?, description: String?, leader: String?, type: String?, privacy: String?, leaderCreateChallenge: Boolean?): Flowable<Group> {
val group = Group()
group.name = name
group.description = description
group.type = type
group.leaderID = leader
group.privacy = privacy
return apiClient.createGroup(group).doOnNext {
localRepository.save(it)
}
}
override fun updateGroup(group: Group?, name: String?, description: String?, leader: String?, leaderCreateChallenge: Boolean?): Flowable<Void> {
if (group == null) { if (group == null) {
return Flowable.empty() return Flowable.empty()
} }
@ -139,7 +152,7 @@ class SocialRepositoryImpl(localRepository: SocialLocalRepository, apiClient: Ap
copiedGroup.name = name copiedGroup.name = name
copiedGroup.description = description copiedGroup.description = description
copiedGroup.leaderID = leader copiedGroup.leaderID = leader
copiedGroup.privacy = privacy copiedGroup.leaderOnlyChallenges = leaderCreateChallenge ?: false
localRepository.save(copiedGroup) localRepository.save(copiedGroup)
return apiClient.updateGroup(copiedGroup.id, copiedGroup) return apiClient.updateGroup(copiedGroup.id, copiedGroup)
} }

View file

@ -13,34 +13,22 @@ open class Group : RealmObject() {
@SerializedName("_id") @SerializedName("_id")
@PrimaryKey @PrimaryKey
var id: String = "" var id: String = ""
var balance: Double = 0.toDouble() var balance: Double = 0.toDouble()
var description: String? = null var description: String? = null
var summary: String? = null var summary: String? = null
var leaderID: String? = null var leaderID: String? = null
var leaderName: String? = null var leaderName: String? = null
var name: String? = null var name: String? = null
var memberCount: Int = 0 var memberCount: Int = 0
var type: String? = null var type: String? = null
var logo: String? = null var logo: String? = null
var quest: Quest? = null var quest: Quest? = null
var privacy: String? = null var privacy: String? = null
var members: RealmList<User>? = null var members: RealmList<User>? = null
var challengeCount: Int = 0 var challengeCount: Int = 0
var leaderMessage: String? = null var leaderMessage: String? = null
var leaderOnlyChallenges: Boolean = false
var leaderOnlyGetGems: Boolean = false
override fun equals(other: Any?): Boolean { override fun equals(other: Any?): Boolean {
if (this === other) { if (this === other) {

View file

@ -1,45 +1,35 @@
package com.habitrpg.android.habitica.ui.activities package com.habitrpg.android.habitica.ui.activities
import android.app.Activity import android.app.Activity
import android.content.Context
import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
import androidx.core.content.ContextCompat
import android.view.KeyEvent
import android.view.Menu
import android.view.MenuItem
import android.view.View import android.view.View
import android.view.inputmethod.InputMethodManager import android.widget.*
import android.widget.EditText
import android.widget.ImageButton
import android.widget.LinearLayout
import android.widget.Spinner
import com.habitrpg.android.habitica.R import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.components.AppComponent import com.habitrpg.android.habitica.components.AppComponent
import com.habitrpg.android.habitica.ui.helpers.KeyboardUtil import com.habitrpg.android.habitica.extensions.notNull
import com.habitrpg.android.habitica.ui.helpers.MarkdownParser import com.habitrpg.android.habitica.ui.helpers.*
import com.habitrpg.android.habitica.ui.helpers.bindView
import net.pherth.android.emoji_library.EmojiEditText
import net.pherth.android.emoji_library.EmojiPopup
class GroupFormActivity : BaseActivity() { class GroupFormActivity : BaseActivity() {
private var groupID: String? = null private var groupID: String? = null
private var groupType: String? = null
private var groupName: String? = null private var groupName: String? = null
private var groupDescription: String? = null private var groupDescription: String? = null
private var groupPrivacy: String? = null private var groupPrivacy: String? = null
private var groupLeader: String? = null private var groupLeader: String? = null
private var leaderCreateChallenge = false
private val groupNameEditText: EditText by bindView(R.id.group_name_edittext) private val cancelButton: ImageButton by bindView(R.id.cancel_button)
private val groupDescriptionEditText: EmojiEditText by bindView(R.id.group_description_edittext) private val saveButton: Button by bindView(R.id.save_button)
internal val emojiButton: ImageButton by bindView(R.id.emoji_toggle_btn) private val groupNameEditText: MultiAutoCompleteTextView by bindView(R.id.group_name_edittext)
internal val privacyWrapper: LinearLayout by bindView(R.id.privacyWrapper) private val groupDescriptionEditText: MultiAutoCompleteTextView by bindView(R.id.group_description_edittext)
private val leaderCreateChallengeSwitch: Switch by bindView(R.id.leader_create_challenge_switch)
private val privacyWrapper: LinearLayout by bindView(R.id.privacyWrapper)
internal val privacySpinner: Spinner by bindView(R.id.privacySpinner) internal val privacySpinner: Spinner by bindView(R.id.privacySpinner)
private val privacySeparator: View by bindView(R.id.privacy_separator)
private val popup: EmojiPopup by lazy { private var autocompleteAdapter: AutocompleteAdapter? = null
EmojiPopup(emojiButton.rootView, this, ContextCompat.getColor(this, R.color.brand))
}
override fun getLayoutResId(): Int { override fun getLayoutResId(): Int {
return R.layout.activity_group_form return R.layout.activity_group_form
@ -48,61 +38,34 @@ class GroupFormActivity : BaseActivity() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
val intent = intent intent.extras.notNull {bundle ->
val bundle = intent.extras groupID = bundle.getString("groupID")
this.groupID = bundle!!.getString("groupID") groupType = bundle.getString("groupType")
this.groupName = bundle.getString("name") groupName = bundle.getString("name")
this.groupDescription = bundle.getString("description") groupDescription = bundle.getString("description")
this.groupPrivacy = bundle.getString("privacy") groupPrivacy = bundle.getString("privacy")
this.groupLeader = bundle.getString("leader") groupLeader = bundle.getString("leader")
leaderCreateChallenge = bundle.getBoolean("leaderCreateChallenge", false)
// Emoji keyboard stuff
popup
popup.setSizeForSoftKeyboard()
popup.setOnDismissListener { changeEmojiKeyboardIcon(false) }
popup.setOnSoftKeyboardOpenCloseListener(object : EmojiPopup.OnSoftKeyboardOpenCloseListener {
override fun onKeyboardOpen(keyBoardHeight: Int) {
}
override fun onKeyboardClose() {
if (popup.isShowing) {
popup.dismiss()
}
}
})
popup.setOnEmojiconClickedListener { emojicon ->
if (currentFocus == null || !isEmojiEditText(currentFocus) || emojicon == null) {
return@setOnEmojiconClickedListener
}
val emojiEditText = currentFocus as? EmojiEditText
val start = emojiEditText?.selectionStart ?: 0
val end = emojiEditText?.selectionEnd ?: 0
if (start < 0) {
emojiEditText?.append(emojicon.emoji)
} else {
emojiEditText?.text?.replace(Math.min(start, end),
Math.max(start, end), emojicon.emoji, 0,
emojicon.emoji.length)
}
} }
popup.setOnEmojiconBackspaceClickedListener { _ -> if (groupType == "party") {
if (isEmojiEditText(currentFocus)) { privacyWrapper.visibility = View.GONE
val event = KeyEvent( privacySeparator.visibility = View.GONE
0, 0, 0, KeyEvent.KEYCODE_DEL, 0, 0, 0, 0, KeyEvent.KEYCODE_ENDCALL)
currentFocus?.dispatchKeyEvent(event)
}
} }
emojiButton.setOnClickListener(EmojiClickListener(groupDescriptionEditText)) if (groupID != null) {
fillForm()
if (this.groupID != null) {
this.fillForm()
} }
cancelButton.setOnClickListener {
finish()
KeyboardUtil.dismissKeyboard(this)
}
saveButton.setOnClickListener {
finishActivitySuccessfuly()
}
} }
@ -111,43 +74,11 @@ class GroupFormActivity : BaseActivity() {
} }
private fun fillForm() { private fun fillForm() {
this.groupNameEditText.setText(this.groupName) groupNameEditText.setText(groupName)
this.groupDescriptionEditText.setText(this.groupDescription) groupDescriptionEditText.setText(groupDescription)
this.privacyWrapper.visibility = View.GONE leaderCreateChallengeSwitch.isChecked = leaderCreateChallenge
} privacyWrapper.visibility = View.GONE
saveButton.text = getString(R.string.save)
override fun onCreateOptionsMenu(menu: Menu): Boolean {
// Inflate the menu; this adds items to the action bar if it is present.
menuInflater.inflate(R.menu.menu_save, menu)
return true
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
// 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
if (id == R.id.action_save_changes) {
finishActivitySuccessfuly()
return true
}
return super.onOptionsItemSelected(item)
}
private fun isEmojiEditText(view: View?): Boolean {
return view is EmojiEditText
}
private fun changeEmojiKeyboardIcon(keyboardOpened: Boolean) {
if (keyboardOpened) {
emojiButton.setImageDrawable(ContextCompat.getDrawable(this, R.drawable.ic_keyboard_grey600_24dp))
} else {
emojiButton.setImageDrawable(ContextCompat.getDrawable(this, R.drawable.ic_emoticon_grey600_24dp))
}
} }
override fun onSupportNavigateUp(): Boolean { override fun onSupportNavigateUp(): Boolean {
@ -165,7 +96,9 @@ class GroupFormActivity : BaseActivity() {
val resultIntent = Intent() val resultIntent = Intent()
val bundle = Bundle() val bundle = Bundle()
bundle.putString("name", this.groupNameEditText.text.toString()) bundle.putString("name", this.groupNameEditText.text.toString())
bundle.putString("groupType", groupType)
bundle.putString("description", MarkdownParser.parseCompiled(this.groupDescriptionEditText.text)) bundle.putString("description", MarkdownParser.parseCompiled(this.groupDescriptionEditText.text))
bundle.putBoolean("leaderCreateChallenge", leaderCreateChallengeSwitch.isActivated)
bundle.putString("leader", this.groupLeader) bundle.putString("leader", this.groupLeader)
resultIntent.putExtras(bundle) resultIntent.putExtras(bundle)
setResult(Activity.RESULT_OK, resultIntent) setResult(Activity.RESULT_OK, resultIntent)
@ -173,28 +106,6 @@ class GroupFormActivity : BaseActivity() {
KeyboardUtil.dismissKeyboard(this) KeyboardUtil.dismissKeyboard(this)
} }
private inner class EmojiClickListener(internal var view: EmojiEditText) : View.OnClickListener {
override fun onClick(v: View) {
if (!popup.isShowing) {
if (popup.isKeyBoardOpen == true) {
popup.showAtBottom()
changeEmojiKeyboardIcon(true)
} else {
view.isFocusableInTouchMode = true
view.requestFocus()
popup.showAtBottomPending()
val inputMethodManager = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
inputMethodManager.showSoftInput(view, InputMethodManager.SHOW_IMPLICIT)
changeEmojiKeyboardIcon(true)
}
} else {
popup.dismiss()
changeEmojiKeyboardIcon(false)
}
}
}
companion object { companion object {
const val GROUP_FORM_ACTIVITY = 11 const val GROUP_FORM_ACTIVITY = 11

View file

@ -1,5 +1,6 @@
package com.habitrpg.android.habitica.ui.fragments.social package com.habitrpg.android.habitica.ui.fragments.social
import android.app.Activity
import android.content.ClipData import android.content.ClipData
import android.content.ClipboardManager import android.content.ClipboardManager
import android.content.Context import android.content.Context
@ -7,24 +8,24 @@ import android.content.Intent
import android.graphics.Bitmap import android.graphics.Bitmap
import android.graphics.Shader import android.graphics.Shader
import android.graphics.drawable.BitmapDrawable import android.graphics.drawable.BitmapDrawable
import android.net.Uri
import android.os.Bundle import android.os.Bundle
import android.text.method.LinkMovementMethod import android.text.method.LinkMovementMethod
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.core.net.toUri
import com.habitrpg.android.habitica.R import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.components.AppComponent import com.habitrpg.android.habitica.components.AppComponent
import com.habitrpg.android.habitica.data.SocialRepository import com.habitrpg.android.habitica.data.SocialRepository
import com.habitrpg.android.habitica.data.UserRepository import com.habitrpg.android.habitica.data.UserRepository
import com.habitrpg.android.habitica.extensions.backgroundCompat import com.habitrpg.android.habitica.extensions.backgroundCompat
import com.habitrpg.android.habitica.extensions.notNull import com.habitrpg.android.habitica.extensions.notNull
import com.habitrpg.android.habitica.helpers.MainNavigationController
import com.habitrpg.android.habitica.helpers.RxErrorHandler import com.habitrpg.android.habitica.helpers.RxErrorHandler
import com.habitrpg.android.habitica.models.invitations.PartyInvite import com.habitrpg.android.habitica.models.invitations.PartyInvite
import com.habitrpg.android.habitica.models.members.Member import com.habitrpg.android.habitica.models.members.Member
import com.habitrpg.android.habitica.models.social.Group import com.habitrpg.android.habitica.models.social.Group
import com.habitrpg.android.habitica.models.user.User import com.habitrpg.android.habitica.models.user.User
import com.habitrpg.android.habitica.ui.activities.GroupFormActivity
import com.habitrpg.android.habitica.ui.activities.MainActivity import com.habitrpg.android.habitica.ui.activities.MainActivity
import com.habitrpg.android.habitica.ui.fragments.BaseFragment import com.habitrpg.android.habitica.ui.fragments.BaseFragment
import com.habitrpg.android.habitica.ui.helpers.DataBindingUtils import com.habitrpg.android.habitica.ui.helpers.DataBindingUtils
@ -71,7 +72,7 @@ class GroupInformationFragment : BaseFragment() {
updateGroup(group) updateGroup(group)
buttonPartyInviteAccept.setOnClickListener { _ -> buttonPartyInviteAccept.setOnClickListener {
val userId = user?.invitations?.party?.id val userId = user?.invitations?.party?.id
if (userId != null) { if (userId != null) {
socialRepository.joinGroup(userId) socialRepository.joinGroup(userId)
@ -83,7 +84,7 @@ class GroupInformationFragment : BaseFragment() {
} }
} }
buttonPartyInviteReject.setOnClickListener { _ -> buttonPartyInviteReject.setOnClickListener {
val userId = user?.invitations?.party?.id val userId = user?.invitations?.party?.id
if (userId != null) { if (userId != null) {
socialRepository.rejectGroupInvite(userId) socialRepository.rejectGroupInvite(userId)
@ -91,7 +92,7 @@ class GroupInformationFragment : BaseFragment() {
} }
} }
username_textview.setOnClickListener { _ -> username_textview.setOnClickListener {
val clipboard = context?.getSystemService(Context.CLIPBOARD_SERVICE) as? ClipboardManager val clipboard = context?.getSystemService(Context.CLIPBOARD_SERVICE) as? ClipboardManager
val clip = ClipData.newPlainText(context?.getString(R.string.username), user?.username) val clip = ClipData.newPlainText(context?.getString(R.string.username), user?.username)
clipboard?.primaryClip = clip clipboard?.primaryClip = clip
@ -102,16 +103,21 @@ class GroupInformationFragment : BaseFragment() {
} }
craetePartyButton.setOnClickListener { craetePartyButton.setOnClickListener {
val browserIntent = Intent(Intent.ACTION_VIEW, "https://habitica.com/party".toUri()) val bundle = Bundle()
startActivity(browserIntent) bundle.putString("groupType", "party")
bundle.putString("leader", user?.id)
val intent = Intent(activity, GroupFormActivity::class.java)
intent.putExtras(bundle)
intent.flags = Intent.FLAG_ACTIVITY_REORDER_TO_FRONT
startActivityForResult(intent, GroupFormActivity.GROUP_FORM_ACTIVITY)
} }
context.notNull { context -> context.notNull { context ->
DataBindingUtils.loadImage("timeTravelersShop_background_fall") { DataBindingUtils.loadImage("timeTravelersShop_background_fall") {bitmap ->
val aspectRatio = it.width / it.height.toFloat() val aspectRatio = bitmap.width / bitmap.height.toFloat()
val height = context.resources.getDimension(R.dimen.shop_height).toInt() val height = context.resources.getDimension(R.dimen.shop_height).toInt()
val width = Math.round(height * aspectRatio) val width = Math.round(height * aspectRatio)
val drawable = BitmapDrawable(context.resources, Bitmap.createScaledBitmap(it, width, height, false)) val drawable = BitmapDrawable(context.resources, Bitmap.createScaledBitmap(bitmap, width, height, false))
drawable.tileModeX = Shader.TileMode.REPEAT drawable.tileModeX = Shader.TileMode.REPEAT
Observable.just(drawable) Observable.just(drawable)
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
@ -125,6 +131,37 @@ class GroupInformationFragment : BaseFragment() {
groupSummaryView.movementMethod = LinkMovementMethod.getInstance() groupSummaryView.movementMethod = LinkMovementMethod.getInstance()
} }
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
when (requestCode) {
GroupFormActivity.GROUP_FORM_ACTIVITY -> {
if (resultCode == Activity.RESULT_OK) {
val bundle = data?.extras
if (bundle?.getString("groupType") == "party") {
socialRepository.createGroup(bundle.getString("name"),
bundle.getString("description"),
bundle.getString("leader"),
"party",
bundle.getString("privacy"),
bundle.getBoolean("leaderCreateChallenge"))
.flatMap {
userRepository.retrieveUser(false)
}
.subscribe(Consumer {
}, RxErrorHandler.handleEmptyError())
} else {
this.socialRepository.updateGroup(this.group,
bundle?.getString("name"),
bundle?.getString("description"),
bundle?.getString("leader"),
bundle?.getBoolean("leaderCreateChallenge"))
.subscribe(Consumer { }, RxErrorHandler.handleEmptyError())
}
}
}
}
}
private fun refresh() { private fun refresh() {
if (group != null) { if (group != null) {
compositeSubscription.add(socialRepository.retrieveGroup(group?.id ?: "").subscribe(Consumer {}, RxErrorHandler.handleEmptyError())) compositeSubscription.add(socialRepository.retrieveGroup(group?.id ?: "").subscribe(Consumer {}, RxErrorHandler.handleEmptyError()))

View file

@ -180,11 +180,13 @@ class GuildFragment : BaseMainFragment() {
private fun displayEditForm() { private fun displayEditForm() {
val bundle = Bundle() val bundle = Bundle()
bundle.putString("groupID", this.guild?.id) bundle.putString("groupID", guild?.id)
bundle.putString("name", this.guild?.name) bundle.putString("name", guild?.name)
bundle.putString("description", this.guild?.description) bundle.putString("description", guild?.description)
bundle.putString("privacy", this.guild?.privacy) bundle.putString("privacy", guild?.privacy)
bundle.putString("leader", this.guild?.leaderID) bundle.putString("leader", guild?.leaderID)
bundle.putBoolean("leaderCreateChallenge", guild?.leaderOnlyChallenges ?: true)
val intent = Intent(activity, GroupFormActivity::class.java) val intent = Intent(activity, GroupFormActivity::class.java)
intent.putExtras(bundle) intent.putExtras(bundle)
@ -202,7 +204,7 @@ class GuildFragment : BaseMainFragment() {
bundle?.getString("name"), bundle?.getString("name"),
bundle?.getString("description"), bundle?.getString("description"),
bundle?.getString("leader"), bundle?.getString("leader"),
bundle?.getString("privacy")) bundle?.getBoolean("leaderCreateChallenge"))
.subscribe(Consumer { }, RxErrorHandler.handleEmptyError()) .subscribe(Consumer { }, RxErrorHandler.handleEmptyError())
} }
} }

View file

@ -195,7 +195,7 @@ class PartyDetailFragment constructor(private val viewModel: PartyViewModel) : B
val builder = AlertDialog.Builder(activity) val builder = AlertDialog.Builder(activity)
.setMessage(R.string.leave_party_confirmation) .setMessage(R.string.leave_party_confirmation)
.setPositiveButton(R.string.yes) { _, _ -> .setPositiveButton(R.string.yes) { _, _ ->
viewModel.leaveGroup { activity?.supportFragmentManager?.beginTransaction()?.remove(this)?.commit() } viewModel.leaveGroup { }
}.setNegativeButton(R.string.no) { _, _ -> } }.setNegativeButton(R.string.no) { _, _ -> }
builder.show() builder.show()
} }

View file

@ -12,8 +12,7 @@ import androidx.lifecycle.ViewModelProviders
import androidx.viewpager.widget.ViewPager import androidx.viewpager.widget.ViewPager
import com.habitrpg.android.habitica.R import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.components.AppComponent import com.habitrpg.android.habitica.components.AppComponent
import com.habitrpg.android.habitica.extensions.notNull import com.habitrpg.android.habitica.helpers.RxErrorHandler
import com.habitrpg.android.habitica.helpers.RemoteConfigManager
import com.habitrpg.android.habitica.models.social.Group import com.habitrpg.android.habitica.models.social.Group
import com.habitrpg.android.habitica.ui.activities.GroupFormActivity import com.habitrpg.android.habitica.ui.activities.GroupFormActivity
import com.habitrpg.android.habitica.ui.activities.PartyInviteActivity import com.habitrpg.android.habitica.ui.activities.PartyInviteActivity
@ -24,15 +23,14 @@ import com.habitrpg.android.habitica.ui.helpers.bindView
import com.habitrpg.android.habitica.ui.helpers.resetViews import com.habitrpg.android.habitica.ui.helpers.resetViews
import com.habitrpg.android.habitica.ui.viewmodels.GroupViewType import com.habitrpg.android.habitica.ui.viewmodels.GroupViewType
import com.habitrpg.android.habitica.ui.viewmodels.PartyViewModel import com.habitrpg.android.habitica.ui.viewmodels.PartyViewModel
import io.reactivex.functions.Consumer
import java.util.* import java.util.*
import javax.inject.Inject
class PartyFragment : BaseMainFragment() { class PartyFragment : BaseMainFragment() {
@Inject
lateinit var configRepository: RemoteConfigManager
private val viewPager: ViewPager? by bindView(R.id.viewPager) private val viewPager: ViewPager? by bindView(R.id.viewPager)
private var firstFragment: Fragment? = null
private var partyMemberListFragment: PartyMemberListFragment? = null private var partyMemberListFragment: PartyMemberListFragment? = null
private var chatFragment: ChatFragment? = null private var chatFragment: ChatFragment? = null
private var viewPagerAdapter: androidx.fragment.app.FragmentPagerAdapter? = null private var viewPagerAdapter: androidx.fragment.app.FragmentPagerAdapter? = null
@ -62,10 +60,26 @@ class PartyFragment : BaseMainFragment() {
viewModel.loadPartyID() viewModel.loadPartyID()
// Get the full group data compositeSubscription.add(userRepository.getUser()
if (userHasParty()) { .map {
viewModel.retrieveGroup {} it.hasParty()
} }
.distinctUntilChanged()
.subscribe(Consumer {
val fragment = firstFragment
if (fragment != null) {
childFragmentManager.beginTransaction().remove(fragment).commit()
}
viewPager?.adapter?.notifyDataSetChanged()
if (it) {
viewModel.retrieveGroup {}
tabLayout?.visibility = View.VISIBLE
} else {
tabLayout?.visibility = View.GONE
}
}, RxErrorHandler.handleEmptyError()))
viewPager?.currentItem = 0 viewPager?.currentItem = 0
@ -74,10 +88,6 @@ class PartyFragment : BaseMainFragment() {
this.tutorialText = getString(R.string.tutorial_party) this.tutorialText = getString(R.string.tutorial_party)
} }
private fun userHasParty(): Boolean {
return user?.party?.id?.isNotEmpty() == true
}
override fun injectFragment(component: AppComponent) { override fun injectFragment(component: AppComponent) {
component.inject(this) component.inject(this)
} }
@ -102,6 +112,9 @@ class PartyFragment : BaseMainFragment() {
if (group != null && this.user != null) { if (group != null && this.user != null) {
if (group.leaderID == this.user?.id) { if (group.leaderID == this.user?.id) {
inflater.inflate(R.menu.menu_party_admin, menu) inflater.inflate(R.menu.menu_party_admin, menu)
if (group.memberCount > 1) {
menu.findItem(R.id.menu_guild_leave).isVisible = false
}
} else { } else {
inflater.inflate(R.menu.menu_party, menu) inflater.inflate(R.menu.menu_party, menu)
} }
@ -128,9 +141,6 @@ class PartyFragment : BaseMainFragment() {
.setMessage(context?.getString(R.string.leave_party_confirmation)) .setMessage(context?.getString(R.string.leave_party_confirmation))
.setPositiveButton(context?.getString(R.string.yes)) { _, _ -> .setPositiveButton(context?.getString(R.string.yes)) { _, _ ->
viewModel.leaveGroup { viewModel.leaveGroup {
parentFragment.notNull { fragment ->
activity?.supportFragmentManager?.beginTransaction()?.remove(fragment)?.commit()
}
} }
} }
.setNegativeButton(context?.getString(R.string.no)) { dialog, _ -> dialog.dismiss() } .setNegativeButton(context?.getString(R.string.no)) { dialog, _ -> dialog.dismiss() }
@ -147,8 +157,10 @@ class PartyFragment : BaseMainFragment() {
val group = viewModel.getGroupData().value val group = viewModel.getGroupData().value
bundle.putString("groupID", group?.id) bundle.putString("groupID", group?.id)
bundle.putString("name", group?.name) bundle.putString("name", group?.name)
bundle.putString("groupType", group?.type)
bundle.putString("description", group?.description) bundle.putString("description", group?.description)
bundle.putString("leader", group?.leaderID) bundle.putString("leader", group?.leaderID)
bundle.putBoolean("leaderCreateChallenge", group?.leaderOnlyChallenges ?: false)
val intent = Intent(activity, GroupFormActivity::class.java) val intent = Intent(activity, GroupFormActivity::class.java)
intent.putExtras(bundle) intent.putExtras(bundle)
@ -161,7 +173,7 @@ class PartyFragment : BaseMainFragment() {
when (requestCode) { when (requestCode) {
GroupFormActivity.GROUP_FORM_ACTIVITY -> { GroupFormActivity.GROUP_FORM_ACTIVITY -> {
if (resultCode == Activity.RESULT_OK) { if (resultCode == Activity.RESULT_OK) {
viewModel.updateGroup(data?.extras) viewModel.updateOrCreateGroup(data?.extras)
} }
} }
PartyInviteActivity.RESULT_SEND_INVITES -> { PartyInviteActivity.RESULT_SEND_INVITES -> {
@ -201,18 +213,19 @@ class PartyFragment : BaseMainFragment() {
override fun getItem(position: Int): androidx.fragment.app.Fragment { override fun getItem(position: Int): androidx.fragment.app.Fragment {
return when (position) { return when (position) {
0 -> { 0 -> {
if (user?.hasParty() == true) { firstFragment = if (user?.hasParty() == true) {
val detailFragment = PartyDetailFragment(viewModel) val detailFragment = PartyDetailFragment(viewModel)
detailFragment detailFragment
} else { } else {
GroupInformationFragment.newInstance(null, user) GroupInformationFragment.newInstance(null, user)
} }
firstFragment
} }
1 -> { 1 -> {
if (chatFragment == null) { if (chatFragment == null) {
chatFragment = ChatFragment(viewModel) chatFragment = ChatFragment(viewModel)
} }
chatFragment ?: Fragment() chatFragment
} }
2 -> { 2 -> {
if (partyMemberListFragment == null) { if (partyMemberListFragment == null) {
@ -221,15 +234,15 @@ class PartyFragment : BaseMainFragment() {
partyMemberListFragment?.setPartyId(user?.party?.id ?: "") partyMemberListFragment?.setPartyId(user?.party?.id ?: "")
} }
} }
partyMemberListFragment ?: Fragment() partyMemberListFragment
} }
else -> Fragment() else -> Fragment()
} } ?: Fragment()
} }
override fun getCount(): Int { override fun getCount(): Int {
return if (viewModel.getGroupData().value == null) { return if (user?.hasParty() != true) {
1 1
} else { } else {
3 3
@ -244,6 +257,14 @@ class PartyFragment : BaseMainFragment() {
else -> "" else -> ""
} ?: "" } ?: ""
} }
override fun getItemPosition(fragment: Any): Int {
return if ((fragment is GroupInformationFragment && user?.hasParty() == true) || (fragment is PartyDetailFragment && user?.hasParty() != true)) {
POSITION_NONE
} else {
POSITION_UNCHANGED
}
}
} }
this.viewPager?.adapter = viewPagerAdapter this.viewPager?.adapter = viewPagerAdapter
@ -266,6 +287,4 @@ class PartyFragment : BaseMainFragment() {
}) })
tabLayout?.setupWithViewPager(viewPager) tabLayout?.setupWithViewPager(viewPager)
} }
} }

View file

@ -27,7 +27,7 @@ class AutocompleteAdapter(val context: Context, val socialRepository: SocialRepo
override fun performFiltering(constraint: CharSequence?): FilterResults { override fun performFiltering(constraint: CharSequence?): FilterResults {
val filterResults = FilterResults() val filterResults = FilterResults()
if (constraint != null && constraint.isNotEmpty()) { if (constraint != null && constraint.isNotEmpty()) {
if (constraint[0] == '@' && socialRepository != null) { if (constraint[0] == '@' && constraint.length >= 3 && socialRepository != null) {
isAutocompletingUsers = true isAutocompletingUsers = true
userResults = socialRepository.findUsernames(constraint.toString().drop(1), autocompleteContext, groupID).blockingFirst(arrayListOf()) userResults = socialRepository.findUsernames(constraint.toString().drop(1), autocompleteContext, groupID).blockingFirst(arrayListOf())
filterResults.values = userResults filterResults.values = userResults

View file

@ -89,15 +89,23 @@ open class GroupViewModel: BaseViewModel() {
.subscribe(Consumer { }, RxErrorHandler.handleEmptyError())) .subscribe(Consumer { }, RxErrorHandler.handleEmptyError()))
} }
fun updateGroup(bundle: Bundle?) {
fun updateOrCreateGroup(bundle: Bundle?) {
if (group.value == null) { if (group.value == null) {
return socialRepository.createGroup(bundle?.getString("name"),
bundle?.getString("description"),
bundle?.getString("leader"),
bundle?.getString("groupType"),
bundle?.getString("privacy"),
bundle?.getBoolean("leaderCreateChallenge"))
} else {
disposable.add(socialRepository.updateGroup(group.value, bundle?.getString("name"),
bundle?.getString("description"),
bundle?.getString("leader"),
bundle?.getBoolean("leaderCreateChallenge"))
.subscribe(Consumer { }, RxErrorHandler.handleEmptyError()))
} }
disposable.add(socialRepository.updateGroup(group.value, bundle?.getString("name"),
bundle?.getString("description"),
bundle?.getString("leader"),
bundle?.getString("privacy"))
.subscribe(Consumer { }, RxErrorHandler.handleEmptyError()))
} }
fun leaveGroup(function: () -> Unit) { fun leaveGroup(function: () -> Unit) {
@ -148,10 +156,13 @@ open class GroupViewModel: BaseViewModel() {
} }
fun retrieveGroupChat(onComplete: () -> Unit) { fun retrieveGroupChat(onComplete: () -> Unit) {
groupIDSubject.value?.value.notNull { val groupID = groupIDSubject.value?.value
disposable.add(socialRepository.retrieveGroupChat(it).subscribe(Consumer { if (groupID.isNullOrEmpty()) {
onComplete() onComplete()
}, RxErrorHandler.handleEmptyError())) return
} }
disposable.add(socialRepository.retrieveGroupChat(groupID).subscribe(Consumer {
onComplete()
}, RxErrorHandler.handleEmptyError()))
} }
} }

View file

@ -106,6 +106,16 @@ class GroupSerialization : JsonDeserializer<Group>, JsonSerializer<Group> {
} }
} }
} }
if (obj.has("leaderOnly")) {
val leaderOnly = obj.getAsJsonObject("leaderOnly")
if (leaderOnly.has("challenges")) {
group.leaderOnlyChallenges = leaderOnly.get("challenges").asBoolean
}
if (leaderOnly.has("getGems")) {
group.leaderOnlyGetGems = leaderOnly.get("getGems").asBoolean
}
}
return group return group
} }
@ -118,6 +128,10 @@ class GroupSerialization : JsonDeserializer<Group>, JsonSerializer<Group> {
obj.addProperty("type", src.type) obj.addProperty("type", src.type)
obj.addProperty("type", src.type) obj.addProperty("type", src.type)
obj.addProperty("leader", src.leaderID) obj.addProperty("leader", src.leaderID)
val leaderOnly = JsonObject()
leaderOnly.addProperty("challenges", src.leaderOnlyChallenges)
leaderOnly.addProperty("getGems", src.leaderOnlyGetGems)
obj.add("leaderOnly", leaderOnly)
return obj return obj
} }
} }