diff --git a/Habitica/res/drawable-hdpi/fab_submenu_background.png b/Habitica/res/drawable-hdpi/fab_submenu_background.png
new file mode 100644
index 000000000..2dca2398a
Binary files /dev/null and b/Habitica/res/drawable-hdpi/fab_submenu_background.png differ
diff --git a/Habitica/res/drawable-mdpi/fab_submenu_background.png b/Habitica/res/drawable-mdpi/fab_submenu_background.png
new file mode 100644
index 000000000..a751eaa30
Binary files /dev/null and b/Habitica/res/drawable-mdpi/fab_submenu_background.png differ
diff --git a/Habitica/res/drawable-xhdpi/fab_submenu_background.png b/Habitica/res/drawable-xhdpi/fab_submenu_background.png
new file mode 100644
index 000000000..0215b96bb
Binary files /dev/null and b/Habitica/res/drawable-xhdpi/fab_submenu_background.png differ
diff --git a/Habitica/res/drawable-xxhdpi/fab_submenu_background.png b/Habitica/res/drawable-xxhdpi/fab_submenu_background.png
new file mode 100644
index 000000000..e77a6846c
Binary files /dev/null and b/Habitica/res/drawable-xxhdpi/fab_submenu_background.png differ
diff --git a/Habitica/res/drawable-xxxhdpi/fab_submenu_background.png b/Habitica/res/drawable-xxxhdpi/fab_submenu_background.png
new file mode 100644
index 000000000..3eb560ac5
Binary files /dev/null and b/Habitica/res/drawable-xxxhdpi/fab_submenu_background.png differ
diff --git a/Habitica/res/drawable/bottom_submenu_label_bg.xml b/Habitica/res/drawable/bottom_submenu_label_bg.xml
new file mode 100644
index 000000000..246cb63b2
--- /dev/null
+++ b/Habitica/res/drawable/bottom_submenu_label_bg.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/Habitica/res/layout/activity_main.xml b/Habitica/res/layout/activity_main.xml
index 65abd301c..85441ced8 100644
--- a/Habitica/res/layout/activity_main.xml
+++ b/Habitica/res/layout/activity_main.xml
@@ -123,7 +123,6 @@
android:orientation="vertical"
android:layout_gravity="bottom|center_horizontal"
android:layout_alignParentBottom="true"
- app:layout_behavior="com.habitrpg.android.habitica.ui.helpers.FloatingActionMenuBehavior"
android:padding="0dp"
android:layout_margin="0dp">
@@ -31,6 +33,7 @@
tools:text="@string/habits"
android:layout_gravity="center"
android:textSize="12sp"
+ android:letterSpacing="0.05"
android:fontFamily="@string/font_family_medium"
android:paddingStart="@dimen/spacing_small"
android:paddingEnd="@dimen/spacing_small"
diff --git a/Habitica/res/layout/bottom_navigation_submenu.xml b/Habitica/res/layout/bottom_navigation_submenu.xml
new file mode 100644
index 000000000..c27ab691d
--- /dev/null
+++ b/Habitica/res/layout/bottom_navigation_submenu.xml
@@ -0,0 +1,35 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/Habitica/res/layout/main_navigation_view.xml b/Habitica/res/layout/main_navigation_view.xml
index be983d27b..67b315bf3 100644
--- a/Habitica/res/layout/main_navigation_view.xml
+++ b/Habitica/res/layout/main_navigation_view.xml
@@ -8,10 +8,10 @@
tools:parentTag="android.widget.RelativeLayout">
+ android:layout_alignBottom="@id/bottom_navigation_background"
+ android:layout_marginBottom="21dp"
+ android:background="@drawable/fab_background"
+ android:contentDescription="@string/new_task" />
+
\ No newline at end of file
diff --git a/Habitica/res/values/strings.xml b/Habitica/res/values/strings.xml
index b478dfc12..b312c2482 100644
--- a/Habitica/res/values/strings.xml
+++ b/Habitica/res/values/strings.xml
@@ -934,4 +934,5 @@
Switch to list view
Switch to grid view
Confirm deletion
+ New Task
diff --git a/Habitica/res/xml/remote_config_defaults.xml b/Habitica/res/xml/remote_config_defaults.xml
index 9a7d6784c..ee24650c7 100644
--- a/Habitica/res/xml/remote_config_defaults.xml
+++ b/Habitica/res/xml/remote_config_defaults.xml
@@ -44,5 +44,9 @@
supportEmail
admin@habitica.com
+
+ flipAddTaskBehaviour
+ true
+
\ No newline at end of file
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/AppConfigManager.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/AppConfigManager.kt
index b855c2f1f..1b63a6932 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/AppConfigManager.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/AppConfigManager.kt
@@ -62,4 +62,8 @@ class AppConfigManager {
fun enableLocalTaskScoring(): Boolean {
return remoteConfig.getBoolean("enableLocalTaskScoring")
}
+
+ fun flipAddTaskBehaviour(): Boolean {
+ return remoteConfig.getBoolean("flipAddTaskBehaviour")
+ }
}
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/tasks/TasksFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/tasks/TasksFragment.kt
index 6a216a776..97461a51a 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/tasks/TasksFragment.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/tasks/TasksFragment.kt
@@ -12,6 +12,7 @@ import com.habitrpg.android.habitica.components.UserComponent
import com.habitrpg.android.habitica.data.TagRepository
import com.habitrpg.android.habitica.events.TaskTappedEvent
import com.habitrpg.android.habitica.helpers.AmplitudeManager
+import com.habitrpg.android.habitica.helpers.AppConfigManager
import com.habitrpg.android.habitica.helpers.RxErrorHandler
import com.habitrpg.android.habitica.helpers.TaskFilterHelper
import com.habitrpg.android.habitica.models.tasks.Task
@@ -30,6 +31,8 @@ class TasksFragment : BaseMainFragment() {
lateinit var taskFilterHelper: TaskFilterHelper
@Inject
lateinit var tagRepository: TagRepository
+ @Inject
+ lateinit var appConfigManager: AppConfigManager
private var refreshItem: MenuItem? = null
private var floatingMenu: FloatingActionMenu? = null
@@ -70,6 +73,7 @@ class TasksFragment : BaseMainFragment() {
bottomNavigation?.onAddListener = {
openNewTaskActivity(it)
}
+ bottomNavigation?.flipAddBehaviour = appConfigManager.flipAddTaskBehaviour()
}
override fun onDestroy() {
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/navigation/BottomNavigationSubmenuItem.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/navigation/BottomNavigationSubmenuItem.kt
new file mode 100644
index 000000000..a6ef2a2f0
--- /dev/null
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/navigation/BottomNavigationSubmenuItem.kt
@@ -0,0 +1,46 @@
+package com.habitrpg.android.habitica.ui.views.navigation
+
+import android.content.Context
+import android.graphics.drawable.Drawable
+import android.util.AttributeSet
+import android.widget.ImageView
+import android.widget.RelativeLayout
+import android.widget.TextView
+import com.habitrpg.android.habitica.R
+import com.habitrpg.android.habitica.extensions.inflate
+import com.habitrpg.android.habitica.ui.helpers.bindView
+
+class BottomNavigationSubmenuItem @JvmOverloads constructor(
+ context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
+) : RelativeLayout(context, attrs, defStyleAttr) {
+
+ val measuredTitleWidth: Int
+ get() {
+ titleView.measure(width, height)
+ return titleView.measuredWidth
+ }
+ private val iconView: ImageView by bindView(R.id.icon_view)
+ private val titleView: TextView by bindView(R.id.title_view)
+
+ var icon: Drawable? = null
+ set(value) {
+ field = value
+ iconView.setImageDrawable(value)
+ }
+ var title: String? = null
+ set(value) {
+ field = value
+ titleView.text = title
+ }
+
+ init {
+ inflate(R.layout.bottom_navigation_submenu, true)
+ }
+
+ fun setTitleWidth(width: Int) {
+ val layoutParams = titleView.layoutParams as? LayoutParams
+ layoutParams?.width = width
+ titleView.layoutParams = layoutParams
+ }
+
+}
\ No newline at end of file
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/navigation/HabiticaBottomNavigationView.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/navigation/HabiticaBottomNavigationView.kt
index f3f6abab0..93a374a52 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/navigation/HabiticaBottomNavigationView.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/navigation/HabiticaBottomNavigationView.kt
@@ -3,7 +3,10 @@ package com.habitrpg.android.habitica.ui.views.navigation
import android.content.Context
import android.util.AttributeSet
import android.widget.ImageButton
+import android.widget.LinearLayout
import android.widget.RelativeLayout
+import androidx.core.view.ViewCompat
+import androidx.core.view.children
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.extensions.inflate
import com.habitrpg.android.habitica.models.tasks.Task
@@ -13,6 +16,8 @@ class HabiticaBottomNavigationView @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : RelativeLayout(context, attrs, defStyleAttr) {
+ var flipAddBehaviour = true
+ private var isShowingSubmenu: Boolean = false
var selectedPosition: Int
get() {
return when (activeTaskType) {
@@ -44,6 +49,7 @@ class HabiticaBottomNavigationView @JvmOverloads constructor(
private val todosTab: BottomNavigationItem by bindView(R.id.tab_todos)
private val rewardsTab: BottomNavigationItem by bindView(R.id.tab_rewards)
private val addButton: ImageButton by bindView(R.id.add)
+ private val submenuWrapper: LinearLayout by bindView(R.id.submenu_wrapper)
init {
inflate(R.layout.main_navigation_view, true)
@@ -52,11 +58,90 @@ class HabiticaBottomNavigationView @JvmOverloads constructor(
todosTab.setOnClickListener { activeTaskType = Task.TYPE_TODO }
rewardsTab.setOnClickListener { activeTaskType = Task.TYPE_REWARD }
addButton.setOnClickListener {
- onAddListener?.invoke(activeTaskType)
+ if (flipAddBehaviour) {
+ if (isShowingSubmenu) {
+ hideSubmenu()
+ } else {
+ onAddListener?.invoke(activeTaskType)
+ }
+ } else {
+ showSubmenu()
+ }
+ }
+ addButton.setOnLongClickListener {
+ if (flipAddBehaviour) {
+ showSubmenu()
+ } else {
+ onAddListener?.invoke(activeTaskType)
+ }
+ true
}
updateItemSelection()
}
+ private fun showSubmenu() {
+ isShowingSubmenu = true
+ var pos = 4
+ submenuWrapper.removeAllViews()
+ for (taskType in listOf(Task.TYPE_HABIT, Task.TYPE_DAILY, Task.TYPE_TODO, Task.TYPE_REWARD)) {
+ val view = BottomNavigationSubmenuItem(context)
+ when (taskType) {
+ Task.TYPE_HABIT -> {
+ view.icon = context.getDrawable(R.drawable.add_habit)
+ view.title = context.getString(R.string.habit)
+ }
+ Task.TYPE_DAILY -> {
+ view.icon = context.getDrawable(R.drawable.add_daily)
+ view.title = context.getString(R.string.daily)
+ }
+ Task.TYPE_TODO -> {
+ view.icon = context.getDrawable(R.drawable.add_todo)
+ view.title = context.getString(R.string.todo)
+ }
+ Task.TYPE_REWARD -> {
+ view.icon = context.getDrawable(R.drawable.add_rewards)
+ view.title = context.getString(R.string.reward)
+ }
+ }
+ view.setOnClickListener {
+ onAddListener?.invoke(taskType)
+ hideSubmenu()
+ }
+ submenuWrapper.addView(view)
+ view.alpha = 0f
+ view.scaleY = 0.7f
+ ViewCompat.animate(view).alpha(1f).setDuration(250.toLong()).startDelay = (100 * pos).toLong()
+ ViewCompat.animate(view).scaleY(1f).setDuration(250.toLong()).startDelay = (100 * pos).toLong()
+ pos -= 1
+ }
+ var widestWidth = 0
+ for (view in submenuWrapper.children) {
+ if (view is BottomNavigationSubmenuItem) {
+ val width = view.measuredTitleWidth
+ if (widestWidth < width) {
+ widestWidth = width
+ }
+ }
+ }
+ for (view in submenuWrapper.children) {
+ if (view is BottomNavigationSubmenuItem) {
+ view.setTitleWidth(widestWidth)
+ }
+ }
+ }
+
+ private fun hideSubmenu() {
+ isShowingSubmenu = false
+ var pos = 0
+ for (view in submenuWrapper.children) {
+ view.alpha = 1f
+ view.scaleY = 1f
+ ViewCompat.animate(view).alpha(0f).setDuration(200.toLong()).startDelay = (150 * pos).toLong()
+ ViewCompat.animate(view).scaleY(0.7f).setDuration(250.toLong()).setStartDelay((100 * pos).toLong()).withEndAction { submenuWrapper.removeView(view) }
+ pos += 1
+ }
+ }
+
private fun updateItemSelection() {
habitsTab.isActive = activeTaskType == Task.TYPE_HABIT
dailiesTab.isActive = activeTaskType == Task.TYPE_DAILY