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