diff --git a/.gitignore b/.gitignore index dff36d0fd..26c1fa60d 100644 --- a/.gitignore +++ b/.gitignore @@ -62,3 +62,4 @@ captures /Habitica/prod /Habitica/release /fastlane/secret-key.json +/fastlane/report.xml diff --git a/Habitica/AndroidManifest.xml b/Habitica/AndroidManifest.xml index d1feb14c8..7c64dc2ae 100644 --- a/Habitica/AndroidManifest.xml +++ b/Habitica/AndroidManifest.xml @@ -2,8 +2,6 @@ @@ -66,7 +64,7 @@ + + + + diff --git a/Habitica/res/layout/activity_verify_username.xml b/Habitica/res/layout/activity_verify_username.xml index 70b161460..93fc44b8c 100644 --- a/Habitica/res/layout/activity_verify_username.xml +++ b/Habitica/res/layout/activity_verify_username.xml @@ -130,6 +130,23 @@ android:textColor="@color/white" android:text="@string/confirm_username" android:layout_marginTop="@dimen/spacing_large"/> + + + - + - - + + + + + + + + - - - - - - + + + + + + + + + + + + + + + + + + + + - \ No newline at end of file + android:layout_width="match_parent" + android:layout_height="wrap_content" + app:namePlate="Justin" + app:npcDrawable="@drawable/justin_textbox" + android:layout_marginTop="@dimen/spacing_large"/> + + \ No newline at end of file diff --git a/Habitica/res/layout/preference_category.xml b/Habitica/res/layout/preference_category.xml index 6a0517c13..067b3ff8f 100644 --- a/Habitica/res/layout/preference_category.xml +++ b/Habitica/res/layout/preference_category.xml @@ -12,7 +12,7 @@ --> - APIToken Username E-mail - https://habitica.com + https://habitrpg-delta.herokuapp.com ACCEPT_PARTY_INVITE diff --git a/Habitica/res/values/strings.xml b/Habitica/res/values/strings.xml index 0c23e4e0f..0730902f6 100644 --- a/Habitica/res/values/strings.xml +++ b/Habitica/res/values/strings.xml @@ -561,7 +561,7 @@ Login with Facebook Login with Google Back - Oh, you must be new here. I’m Justin, I’ll be your guide in Habitica.\n\nTo start, you’ll need to create an avatar. + Oh, you must be new here. I’m Justin, I’ll be your guide in Habitica.\n\nFirst, what should we call you? Feel free to change what I picked. When you’re all set, let’s create your avatar! Randomize Extras Skin Color @@ -830,4 +830,7 @@ %s ・Lvl %d Enter a Recipient\'s username Username copied to clipboard + One of these Veteran Pets will be waiting for you after you’ve finished confirming! + What should we call you? + Display names must be between 1 and 30 characters diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/MainActivity.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/MainActivity.kt index 15910705e..33faf08ca 100755 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/MainActivity.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/MainActivity.kt @@ -172,7 +172,6 @@ open class MainActivity : BaseActivity(), TutorialView.OnTutorialReaction { @SuppressLint("ObsoleteSdkInt") public override fun onCreate(savedInstanceState: Bundle?) { - Trace.beginSection("MainActivity.launch") super.onCreate(savedInstanceState) val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this) @@ -315,7 +314,6 @@ open class MainActivity : BaseActivity(), TutorialView.OnTutorialReaction { AmplitudeManager.sendEvent("open notification", AmplitudeManager.EVENT_CATEGORY_BEHAVIOUR, AmplitudeManager.EVENT_HITTYPE_EVENT, additionalData) NotificationOpenHandler.handleOpenedByNotification(identifier, intent, this, user) } - Trace.endSection() } override fun onPause() { diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/SetupActivity.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/SetupActivity.kt index c797078f5..b51b12876 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/SetupActivity.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/SetupActivity.kt @@ -1,5 +1,6 @@ package com.habitrpg.android.habitica.ui.activities +import android.content.Context import android.content.Intent import android.graphics.drawable.Drawable import android.os.Build @@ -12,6 +13,7 @@ import android.support.v4.view.ViewPager import android.support.v7.content.res.AppCompatResources import android.support.v7.preference.PreferenceManager import android.view.View +import android.view.inputmethod.InputMethodManager import android.widget.Button import com.habitrpg.android.habitica.R import com.habitrpg.android.habitica.api.HostConfig @@ -33,9 +35,9 @@ import com.habitrpg.android.habitica.ui.helpers.bindView import com.habitrpg.android.habitica.ui.views.FadingViewPager import com.viewpagerindicator.IconPageIndicator import com.viewpagerindicator.IconPagerAdapter +import io.reactivex.BackpressureStrategy import io.reactivex.functions.Consumer import org.greenrobot.eventbus.Subscribe -import org.solovyev.android.checkout.Inventory import java.util.* import javax.inject.Inject @@ -57,6 +59,7 @@ class SetupActivity : BaseActivity(), ViewPager.OnPageChangeListener { private val previousButton: Button by bindView(R.id.previousButton) private val indicator: IconPageIndicator by bindView(R.id.view_pager_indicator) + internal var welcomeFragment: WelcomeFragment? = null internal var avatarSetupFragment: AvatarSetupFragment? = null internal var taskSetupFragment: TaskSetupFragment? = null internal var user: User? = null @@ -81,8 +84,8 @@ class SetupActivity : BaseActivity(), ViewPager.OnPageChangeListener { val currentDeviceLanguage = Locale.getDefault().language for (language in resources.getStringArray(R.array.LanguageValues)) { if (language == currentDeviceLanguage) { - apiClient.registrationLanguage(currentDeviceLanguage) - .subscribe(Consumer { }, RxErrorHandler.handleEmptyError()) + compositeSubscription.add(apiClient.registrationLanguage(currentDeviceLanguage) + .subscribe(Consumer { }, RxErrorHandler.handleEmptyError())) } } @@ -123,14 +126,14 @@ class SetupActivity : BaseActivity(), ViewPager.OnPageChangeListener { @Subscribe fun onEvent(event: UpdateUserCommand) { - this.userRepository.updateUser(user, event.updateData) - .subscribe(Consumer { this.onUserReceived(it) }, RxErrorHandler.handleEmptyError()) + compositeSubscription.add(this.userRepository.updateUser(user, event.updateData) + .subscribe(Consumer { this.onUserReceived(it) }, RxErrorHandler.handleEmptyError())) } @Subscribe fun onEvent(event: EquipCommand) { - this.inventoryRepository.equip(user, event.type, event.key) - .subscribe(Consumer { }, RxErrorHandler.handleEmptyError()) + compositeSubscription.add(this.inventoryRepository.equip(user, event.type, event.key) + .subscribe(Consumer { }, RxErrorHandler.handleEmptyError())) } private fun nextClicked() { @@ -147,6 +150,11 @@ class SetupActivity : BaseActivity(), ViewPager.OnPageChangeListener { newTasks.notNull { this.taskRepository.createTasks(it).subscribe(Consumer { onUserReceived(user) }, RxErrorHandler.handleEmptyError()) } + } else if (pager.currentItem == 0) { + confirmNames(welcomeFragment?.displayName ?: "", welcomeFragment?.username ?: "") + + val imm = getSystemService(Context.INPUT_METHOD_SERVICE) as? InputMethodManager + imm?.hideSoftInputFromWindow(currentFocus?.windowToken, 0) } this.pager.currentItem = this.pager.currentItem + 1 } @@ -167,6 +175,19 @@ class SetupActivity : BaseActivity(), ViewPager.OnPageChangeListener { previousButton.setCompoundDrawablesWithIntrinsicBounds(leftDrawable, null, null, null) } + private fun setNextButtonEnabled(enabled: Boolean) { + nextButton.isEnabled = enabled + val rightDrawable = AppCompatResources.getDrawable(this, R.drawable.forward_arrow_enabled) + if (enabled) { + nextButton.setTextColor(ContextCompat.getColor(this, R.color.white)) + rightDrawable?.alpha = 255 + } else { + nextButton.setTextColor(ContextCompat.getColor(this, R.color.white_50_alpha)) + rightDrawable?.alpha = 127 + } + nextButton.setCompoundDrawablesWithIntrinsicBounds(null, null, rightDrawable, null) + } + override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) { } @@ -220,6 +241,12 @@ class SetupActivity : BaseActivity(), ViewPager.OnPageChangeListener { finish() } + private fun confirmNames(displayName: String, username: String) { + compositeSubscription.add(userRepository.updateUser(null, "profile.name", displayName) + .flatMap { userRepository.updateLoginName(username).toFlowable() } + .subscribe(Consumer { }, RxErrorHandler.handleEmptyError())) + } + private inner class ViewPageAdapter(fm: FragmentManager) : FragmentPagerAdapter(fm), IconPagerAdapter { override fun getItem(position: Int): Fragment { @@ -238,7 +265,14 @@ class SetupActivity : BaseActivity(), ViewPager.OnPageChangeListener { taskSetupFragment = fragment fragment } - else -> { WelcomeFragment() } + else -> { + val fragment = WelcomeFragment() + welcomeFragment = fragment + welcomeFragment?.nameValidEvents?.toFlowable(BackpressureStrategy.DROP)?.subscribe { + setNextButtonEnabled(it) + } + fragment + } } } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/VerifyUsernameActivity.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/VerifyUsernameActivity.kt index 8bf398d8f..52b39072e 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/VerifyUsernameActivity.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/VerifyUsernameActivity.kt @@ -76,10 +76,10 @@ class VerifyUsernameActivity: BaseActivity() { } }) - verificationEvents.toFlowable(BackpressureStrategy.DROP) + compositeSubscription.add(verificationEvents.toFlowable(BackpressureStrategy.DROP) .throttleLast(1, TimeUnit.SECONDS) .flatMap { userRepository.verifyUsername(usernameEditText.text.toString()) } - .subscribe(Consumer { + .subscribe { if (it.isUsable) { usernameEditText.setCompoundDrawablesWithIntrinsicBounds(null, null, checkmarkIcon, null) issuesTextView.visibility = View.GONE @@ -94,11 +94,11 @@ class VerifyUsernameActivity: BaseActivity() { private fun confirmNames() { confirmUsernameButton.isEnabled = false - userRepository.updateUser(null, "profile.name", displayNameEditText.text.toString()) + compositeSubscription.add(userRepository.updateUser(null, "profile.name", displayNameEditText.text.toString()) .flatMap { userRepository.updateLoginName(usernameEditText.text.toString()).toFlowable() } .doOnComplete { showConfirmationAndFinish() } .doOnEach { confirmUsernameButton.isEnabled = true } - .subscribe(Consumer { }, RxErrorHandler.handleEmptyError()) + .subscribe(Consumer { }, RxErrorHandler.handleEmptyError())) } private fun showConfirmationAndFinish() { diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/setup/WelcomeFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/setup/WelcomeFragment.kt index d3239d5d8..b9ba4dd16 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/setup/WelcomeFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/setup/WelcomeFragment.kt @@ -1,29 +1,59 @@ package com.habitrpg.android.habitica.ui.fragments.setup +import android.graphics.drawable.BitmapDrawable +import android.graphics.drawable.Drawable import android.os.Bundle +import android.support.v4.content.ContextCompat +import android.text.Editable +import android.text.TextWatcher import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import android.widget.ImageView +import android.widget.EditText +import android.widget.TextView import com.habitrpg.android.habitica.R import com.habitrpg.android.habitica.components.AppComponent +import com.habitrpg.android.habitica.data.UserRepository import com.habitrpg.android.habitica.extensions.inflate import com.habitrpg.android.habitica.helpers.AmplitudeManager +import com.habitrpg.android.habitica.helpers.RxErrorHandler import com.habitrpg.android.habitica.ui.SpeechBubbleView import com.habitrpg.android.habitica.ui.fragments.BaseFragment import com.habitrpg.android.habitica.ui.helpers.bindView import com.habitrpg.android.habitica.ui.helpers.resetViews import com.habitrpg.android.habitica.ui.views.HabiticaIconsHelper -import java.util.HashMap +import io.reactivex.BackpressureStrategy +import io.reactivex.functions.Consumer +import io.reactivex.subjects.PublishSubject +import java.util.* +import java.util.concurrent.TimeUnit +import javax.inject.Inject class WelcomeFragment : BaseFragment() { + val nameValidEvents = PublishSubject.create() + + @Inject + lateinit var userRepository: UserRepository + private val speechBubbleView: SpeechBubbleView? by bindView(R.id.speech_bubble) - private val heartIconView: ImageView? by bindView(R.id.heart_icon) - private val magicIconView: ImageView? by bindView(R.id.magic_icon) - private val expIconView: ImageView? by bindView(R.id.exp_icon) - private val goldIconView: ImageView? by bindView(R.id.gold_icon) - private val gemIconView: ImageView? by bindView(R.id.gem_icon) + private val displayNameEditText: EditText by bindView(R.id.display_name_edit_text) + private val usernameEditText: EditText by bindView(R.id.username_edit_text) + private val issuesTextView: TextView by bindView(R.id.issues_text_view) + + private val displayNameVerificationEvents = PublishSubject.create() + private val usernameVerificationEvents = PublishSubject.create() + + private val checkmarkIcon: Drawable by lazy { + BitmapDrawable(resources, HabiticaIconsHelper.imageOfCheckmark(ContextCompat.getColor(context!!, R.color.green_50), 1f)) + } + private val alertIcon: Drawable by lazy { + BitmapDrawable(resources, HabiticaIconsHelper.imageOfAlertIcon()) + } + val username: String + get() = usernameEditText.text.toString() + val displayName: String + get() = displayNameEditText.text.toString() override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { super.onCreateView(inflater, container, savedInstanceState) @@ -42,11 +72,64 @@ class WelcomeFragment : BaseFragment() { speechBubbleView?.animateText(context?.getString(R.string.welcome_text) ?: "") - heartIconView?.setImageBitmap(HabiticaIconsHelper.imageOfHeartLightBg()) - expIconView?.setImageBitmap(HabiticaIconsHelper.imageOfExperience()) - magicIconView?.setImageBitmap(HabiticaIconsHelper.imageOfMagic()) - goldIconView?.setImageBitmap(HabiticaIconsHelper.imageOfGold()) - gemIconView?.setImageBitmap(HabiticaIconsHelper.imageOfGem()) + super.onCreate(savedInstanceState) + + displayNameEditText.addTextChangedListener(object: TextWatcher { + override fun afterTextChanged(p0: Editable?) { + } + + override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) { + } + + override fun onTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) { + displayNameVerificationEvents.onNext(p0.toString()) + } + }) + usernameEditText.addTextChangedListener(object: TextWatcher { + override fun afterTextChanged(p0: Editable?) { + } + + override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) { + } + + override fun onTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) { + usernameVerificationEvents.onNext(p0.toString()) + } + }) + + compositeSubscription.add(displayNameVerificationEvents.toFlowable(BackpressureStrategy.DROP) + .map { it.length in 1..30 } + .subscribe { + if (it) { + displayNameEditText.setCompoundDrawablesWithIntrinsicBounds(null, null, checkmarkIcon, null) + issuesTextView.visibility = View.GONE + } else { + displayNameEditText.setCompoundDrawablesWithIntrinsicBounds(null, null, alertIcon, null) + issuesTextView.visibility = View.VISIBLE + issuesTextView.text = context?.getString(R.string.display_name_length_error) + } + }) + compositeSubscription.add(usernameVerificationEvents.toFlowable(BackpressureStrategy.DROP) + .throttleLast(1, TimeUnit.SECONDS) + .flatMap { userRepository.verifyUsername(usernameEditText.text.toString()) } + .subscribe { + if (it.isUsable) { + usernameEditText.setCompoundDrawablesWithIntrinsicBounds(null, null, checkmarkIcon, null) + issuesTextView.visibility = View.GONE + } else { + usernameEditText.setCompoundDrawablesWithIntrinsicBounds(null, null, alertIcon, null) + issuesTextView.visibility = View.VISIBLE + issuesTextView.text = it.issues.joinToString("\n") + } + nameValidEvents.onNext(it.isUsable) + }) + + compositeSubscription.add(userRepository.getUser().firstElement().subscribe { + displayNameEditText.setText(it.profile?.name) + displayNameVerificationEvents.onNext(it.profile?.name ?: "") + usernameEditText.setText(it.username) + usernameVerificationEvents.onNext(it.username ?: "") + }) } override fun injectFragment(component: AppComponent) { diff --git a/build.gradle b/build.gradle index b64d9f9e3..ca9c682a9 100644 --- a/build.gradle +++ b/build.gradle @@ -1,7 +1,7 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { - ext.kotlin_version = '1.2.71' + ext.kotlin_version = '1.3.0' ext.build_tools_version = '28.0.3' ext.sdk_version = 28 diff --git a/gradle.properties b/gradle.properties index 6c5180d38..52d65d23c 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,3 +1,4 @@ org.gradle.configureondemand=true org.gradle.daemon=true -org.gradle.jvmargs=-Xmx6656M \ No newline at end of file +org.gradle.jvmargs=-Xmx6656M +org.gradle.warning.mode=all \ No newline at end of file