Implement new bottom navigation design

This commit is contained in:
Phillip Thelen 2019-06-06 14:33:29 +02:00
parent 69cb75c543
commit a05ea079d1
53 changed files with 332 additions and 2966 deletions

View file

@ -119,7 +119,7 @@ dependencies {
implementation 'com.google.firebase:firebase-core:16.0.9' implementation 'com.google.firebase:firebase-core:16.0.9'
implementation 'com.google.firebase:firebase-messaging:18.0.0' implementation 'com.google.firebase:firebase-messaging:18.0.0'
implementation 'com.google.firebase:firebase-config:17.0.0' implementation 'com.google.firebase:firebase-config:17.0.0'
implementation 'com.google.firebase:firebase-perf:17.0.0' implementation 'com.google.firebase:firebase-perf:17.0.2'
implementation 'com.google.android.gms:play-services-auth:16.0.1' implementation 'com.google.android.gms:play-services-auth:16.0.1'
implementation 'io.realm:android-adapters:3.1.0' implementation 'io.realm:android-adapters:3.1.0'
implementation(project(':seeds-sdk')) { implementation(project(':seeds-sdk')) {

Binary file not shown.

After

Width:  |  Height:  |  Size: 2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 316 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 202 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 344 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 516 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 688 B

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_checked="true" android:color="@android:color/white" />
<item android:state_pressed="true" android:state_enabled="true" android:color="@android:color/white" />
<item android:color="@color/brand_500" />
</selector>

Binary file not shown.

After

Width:  |  Height:  |  Size: 2 KiB

View file

@ -179,7 +179,7 @@
</com.google.android.material.appbar.AppBarLayout> </com.google.android.material.appbar.AppBarLayout>
<FrameLayout <FrameLayout
android:id="@+id/floating_menu_wrapper" android:id="@+id/snackbar_container"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_alignParentBottom="true" android:layout_alignParentBottom="true"

View file

@ -115,37 +115,31 @@
android:visibility="gone"/> android:visibility="gone"/>
</com.google.android.material.appbar.AppBarLayout> </com.google.android.material.appbar.AppBarLayout>
<LinearLayout <RelativeLayout
android:id="@+id/bottom_wrapper" android:id="@+id/bottom_wrapper"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="match_parent"
android:minHeight="60dp"
android:orientation="vertical" android:orientation="vertical"
android:layout_gravity="bottom|center_horizontal" android:layout_gravity="bottom|center_horizontal"
android:layout_alignParentBottom="true" android:layout_alignParentBottom="true"
app:layout_behavior="com.habitrpg.android.habitica.ui.helpers.FloatingActionMenuBehavior" app:layout_behavior="com.habitrpg.android.habitica.ui.helpers.FloatingActionMenuBehavior"
android:padding="0dp"> android:padding="0dp"
<androidx.coordinatorlayout.widget.CoordinatorLayout android:layout_margin="0dp">
android:id="@+id/floating_menu_wrapper" <FrameLayout
android:id="@+id/snackbar_container"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="90dp"
android:layout_gravity="bottom|right" android:layout_alignBottom="@id/bottom_navigation"
android:layout_marginBottom="-5dp" android:layout_marginBottom="68dp"/>
/> <com.habitrpg.android.habitica.ui.views.navigation.HabiticaBottomNavigationView
<com.habitrpg.android.habitica.ui.views.bottombar.BottomBar
android:id="@+id/bottom_navigation" android:id="@+id/bottom_navigation"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="60dp" android:layout_height="wrap_content"
android:background="?colorPrimary" android:layout_alignParentBottom="true"
app:bb_tabXmlResource="@xml/main_menu_tasks" android:layout_alignParentStart="true"
app:bb_inActiveTabColor="?colorPrimaryDistinct" android:layout_alignParentEnd="true" />
app:bb_activeTabColor="@color/white" </RelativeLayout>
app:bb_badgesHideWhenActive="true"
app:bb_badgeBackgroundColor="?colorBadgeBackground"
android:layout_gravity="bottom"
android:layout_marginBottom="-5dp"
/>
</LinearLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout> </androidx.coordinatorlayout.widget.CoordinatorLayout>
<FrameLayout <FrameLayout
android:layout_width="match_parent" android:layout_width="match_parent"

View file

@ -0,0 +1,38 @@
<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="vertical"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
tools:parentTag="android.widget.LinearLayout"
tools:background="@color/brand_100">
<ImageView
android:id="@+id/icon_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
tools:src="@drawable/icon_habits_selected"
android:layout_gravity="center"/>
<TextView
android:id="@+id/title_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@color/brand_500"
tools:text="@string/habits"
android:layout_gravity="center"
android:textSize="12sp"
android:paddingStart="@dimen/spacing_small"
android:paddingEnd="@dimen/spacing_small"
/>
<TextView
android:id="@+id/selected_title_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@color/white"
tools:text="@string/habits"
android:layout_gravity="center"
android:textSize="12sp"
android:fontFamily="@string/font_family_medium"
android:paddingStart="@dimen/spacing_small"
android:paddingEnd="@dimen/spacing_small"
/>
</merge>

View file

@ -0,0 +1,79 @@
<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
tools:parentTag="android.widget.RelativeLayout">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="62dp"
android:layout_alignParentTop="true"
android:layout_marginTop="40dp"
android:gravity="center_vertical"
android:clickable="true">
<View
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="match_parent"
android:background="@color/brand_100" />
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/bottom_navigation_inset"/>
<View
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="match_parent"
android:background="@color/brand_100" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="62dp"
android:layout_alignParentTop="true"
android:layout_marginTop="40dp"
android:gravity="center_vertical">
<com.habitrpg.android.habitica.ui.views.navigation.BottomNavigationItem
android:id="@+id/tab_habits"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
app:title="@string/habits"
app:iconDrawable="@drawable/icon_habits_selected"/>
<com.habitrpg.android.habitica.ui.views.navigation.BottomNavigationItem
android:layout_width="0dp"
android:id="@+id/tab_dailies"
android:layout_height="wrap_content"
android:layout_weight="1"
app:title="@string/dailies"
app:iconDrawable="@drawable/icon_dailies_selected"/>
<androidx.legacy.widget.Space
android:layout_width="40dp"
android:layout_height="wrap_content" />
<com.habitrpg.android.habitica.ui.views.navigation.BottomNavigationItem
android:id="@+id/tab_todos"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
app:title="@string/todos"
app:iconDrawable="@drawable/icon_todos_selected"/>
<com.habitrpg.android.habitica.ui.views.navigation.BottomNavigationItem
android:id="@+id/tab_rewards"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
app:title="@string/rewards"
app:iconDrawable="@drawable/icon_rewards_selected"/>
</LinearLayout>
<ImageButton
android:id="@+id/add"
android:layout_width="80dp"
android:layout_height="80dp"
android:src="@drawable/fab_plus"
android:layout_centerHorizontal="true"
android:layout_alignParentTop="true"
android:background="@drawable/fab_background" />
</merge>

View file

@ -13,7 +13,7 @@
android:paddingStart="6dp" android:paddingStart="6dp"
android:gravity="center" android:gravity="center"
tools:background="@drawable/snackbar_background_green" tools:background="@drawable/snackbar_background_green"
android:layout_marginBottom="16dp" android:layout_marginBottom="32dp"
android:layout_gravity="center_horizontal" android:layout_gravity="center_horizontal"
android:elevation="24dp"> android:elevation="24dp">
<ImageView <ImageView

View file

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="@+id/tab_habits"
android:title="@string/habits"
android:icon="@drawable/icon_habits_selected" />
<item android:id="@+id/tab_dailies"
android:title="@string/dailies"
android:icon="@drawable/icon_dailies_selected" />
<item android:id="@+id/add"
android:icon="@drawable/md_transparent"
/>
<item android:id="@+id/tab_todos"
android:title="@string/todos"
android:icon="@drawable/icon_todos_selected" />
<item android:id="@+id/tab_rewards"
android:title="@string/rewards"
android:icon="@drawable/icon_rewards_selected" />
</menu>

View file

@ -12,6 +12,7 @@
<attr name="statsColor" format="color" /> <attr name="statsColor" format="color" />
<attr name="statsTitle" format="string" /> <attr name="statsTitle" format="string" />
<attr name="title" format="string" /> <attr name="title" format="string" />
<attr name="iconDrawable" format="integer" />
<attr name="identifier" format="string" /> <attr name="identifier" format="string" />
<attr name="hasLightBackground" format="boolean" /> <attr name="hasLightBackground" format="boolean" />
<attr name="barForegroundColor" format="color" /> <attr name="barForegroundColor" format="color" />
@ -46,7 +47,7 @@
<attr name="npcDrawable" format="integer" /> <attr name="npcDrawable" format="integer" />
</declare-styleable> </declare-styleable>
<declare-styleable name="AvatarCategoryView"> <declare-styleable name="AvatarCategoryView">
<attr name="iconDrawable" format="integer" /> <attr name="iconDrawable" />
<attr name="categoryTitle" format="string" /> <attr name="categoryTitle" format="string" />
</declare-styleable> </declare-styleable>
<declare-styleable name="ValueBar"> <declare-styleable name="ValueBar">
@ -119,4 +120,8 @@
<attr name="bb_titleTypeFace" format="string" /> <attr name="bb_titleTypeFace" format="string" />
<attr name="bb_showShadow" format="boolean" /> <attr name="bb_showShadow" format="boolean" />
</declare-styleable> </declare-styleable>
<declare-styleable name="BottomNavigationItem">
<attr name="title" />
<attr name="iconDrawable" />
</declare-styleable>
</resources> </resources>

View file

@ -68,8 +68,8 @@ import com.habitrpg.android.habitica.ui.views.HabiticaIconsHelper
import com.habitrpg.android.habitica.ui.views.HabiticaSnackbar import com.habitrpg.android.habitica.ui.views.HabiticaSnackbar
import com.habitrpg.android.habitica.ui.views.HabiticaSnackbar.SnackbarDisplayType import com.habitrpg.android.habitica.ui.views.HabiticaSnackbar.SnackbarDisplayType
import com.habitrpg.android.habitica.ui.views.ValueBar import com.habitrpg.android.habitica.ui.views.ValueBar
import com.habitrpg.android.habitica.ui.views.bottombar.BottomBar
import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaAlertDialog import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaAlertDialog
import com.habitrpg.android.habitica.ui.views.navigation.HabiticaBottomNavigationView
import com.habitrpg.android.habitica.ui.views.yesterdailies.YesterdailyDialog import com.habitrpg.android.habitica.ui.views.yesterdailies.YesterdailyDialog
import com.habitrpg.android.habitica.userpicture.BitmapUtils import com.habitrpg.android.habitica.userpicture.BitmapUtils
import com.habitrpg.android.habitica.widget.AvatarStatsWidgetProvider import com.habitrpg.android.habitica.widget.AvatarStatsWidgetProvider
@ -121,8 +121,8 @@ open class MainActivity : BaseActivity(), TutorialView.OnTutorialReaction {
@Inject @Inject
internal lateinit var appConfigManager: AppConfigManager internal lateinit var appConfigManager: AppConfigManager
val floatingMenuWrapper: ViewGroup by bindView(R.id.floating_menu_wrapper) val snackbarContainer: ViewGroup by bindView(R.id.snackbar_container)
internal val bottomNavigation: BottomBar by bindView(R.id.bottom_navigation) internal val bottomNavigation: HabiticaBottomNavigationView by bindView(R.id.bottom_navigation)
private val appBar: AppBarLayout by bindView(R.id.appbar) private val appBar: AppBarLayout by bindView(R.id.appbar)
internal val toolbar: Toolbar by bindView(R.id.toolbar) internal val toolbar: Toolbar by bindView(R.id.toolbar)
@ -467,7 +467,7 @@ open class MainActivity : BaseActivity(), TutorialView.OnTutorialReaction {
val pet = event.usingPet val pet = event.usingPet
compositeSubscription.add(this.inventoryRepository.feedPet(event.usingPet, event.usingFood) compositeSubscription.add(this.inventoryRepository.feedPet(event.usingPet, event.usingFood)
.subscribe(Consumer { feedResponse -> .subscribe(Consumer { feedResponse ->
HabiticaSnackbar.showSnackbar(floatingMenuWrapper, getString(R.string.notification_pet_fed, pet.text), SnackbarDisplayType.NORMAL) HabiticaSnackbar.showSnackbar(snackbarContainer, getString(R.string.notification_pet_fed, pet.text), SnackbarDisplayType.NORMAL)
if (feedResponse.value == -1) { if (feedResponse.value == -1) {
val mountWrapper = View.inflate(this, R.layout.pet_imageview, null) as? FrameLayout val mountWrapper = View.inflate(this, R.layout.pet_imageview, null) as? FrameLayout
val mountImageView = mountWrapper?.findViewById(R.id.pet_imageview) as? SimpleDraweeView val mountImageView = mountWrapper?.findViewById(R.id.pet_imageview) as? SimpleDraweeView
@ -499,12 +499,12 @@ open class MainActivity : BaseActivity(), TutorialView.OnTutorialReaction {
internal fun displayTaskScoringResponse(data: TaskScoringResult?) { internal fun displayTaskScoringResponse(data: TaskScoringResult?) {
if (user != null && data != null) { if (user != null && data != null) {
compositeSubscription.add(notifyUserUseCase.observable(NotifyUserUseCase.RequestValues(this, floatingMenuWrapper, compositeSubscription.add(notifyUserUseCase.observable(NotifyUserUseCase.RequestValues(this, snackbarContainer,
user, data.experienceDelta, data.healthDelta, data.goldDelta, data.manaDelta, if (userIsOnQuest) data.questDamage else 0.0, data.hasLeveledUp)) user, data.experienceDelta, data.healthDelta, data.goldDelta, data.manaDelta, if (userIsOnQuest) data.questDamage else 0.0, data.hasLeveledUp))
.subscribe(Consumer { }, RxErrorHandler.handleEmptyError())) .subscribe(Consumer { }, RxErrorHandler.handleEmptyError()))
} }
compositeSubscription.add(displayItemDropUseCase.observable(DisplayItemDropUseCase.RequestValues(data, this, floatingMenuWrapper)) compositeSubscription.add(displayItemDropUseCase.observable(DisplayItemDropUseCase.RequestValues(data, this, snackbarContainer))
.subscribe(Consumer { }, RxErrorHandler.handleEmptyError())) .subscribe(Consumer { }, RxErrorHandler.handleEmptyError()))
} }
@ -689,7 +689,7 @@ open class MainActivity : BaseActivity(), TutorialView.OnTutorialReaction {
@Subscribe @Subscribe
fun showSnackBarEvent(event: ShowSnackbarEvent) { fun showSnackBarEvent(event: ShowSnackbarEvent) {
HabiticaSnackbar.showSnackbar(floatingMenuWrapper, event.leftImage, event.title, event.text, event.specialView, event.rightIcon, event.rightTextColor, event.rightText, event.type) HabiticaSnackbar.showSnackbar(snackbarContainer, event.leftImage, event.title, event.text, event.specialView, event.rightIcon, event.rightTextColor, event.rightText, event.type)
} }
@Subscribe @Subscribe

View file

@ -30,7 +30,7 @@ abstract class BaseMainFragment : BaseFragment() {
val collapsingToolbar get() = activity?.toolbar val collapsingToolbar get() = activity?.toolbar
val toolbarAccessoryContainer get() = activity?.toolbarAccessoryContainer val toolbarAccessoryContainer get() = activity?.toolbarAccessoryContainer
val bottomNavigation get() = activity?.bottomNavigation val bottomNavigation get() = activity?.bottomNavigation
val floatingMenuWrapper get() = activity?.floatingMenuWrapper val floatingMenuWrapper get() = activity?.snackbarContainer
var usesTabLayout: Boolean = false var usesTabLayout: Boolean = false
var hidesToolbar: Boolean = false var hidesToolbar: Boolean = false
var usesBottomNavigation = false var usesBottomNavigation = false
@ -56,8 +56,6 @@ abstract class BaseMainFragment : BaseFragment() {
} }
if (this.usesBottomNavigation) { if (this.usesBottomNavigation) {
bottomNavigation?.removeOnTabSelectListener()
bottomNavigation?.removeOnTabReselectListener()
bottomNavigation?.visibility = View.VISIBLE bottomNavigation?.visibility = View.VISIBLE
} else { } else {
bottomNavigation?.visibility = View.GONE bottomNavigation?.visibility = View.GONE

View file

@ -110,10 +110,10 @@ class SkillsFragment : BaseMainFragment() {
adapter?.mana = response.user.stats?.mp ?: 0.0 adapter?.mana = response.user.stats?.mp ?: 0.0
val activity = activity ?: return val activity = activity ?: return
if ("special" == usedSkill?.habitClass) { if ("special" == usedSkill?.habitClass) {
showSnackbar(activity.floatingMenuWrapper, context?.getString(R.string.used_skill_without_mana, usedSkill.text), HabiticaSnackbar.SnackbarDisplayType.BLUE) showSnackbar(activity.snackbarContainer, context?.getString(R.string.used_skill_without_mana, usedSkill.text), HabiticaSnackbar.SnackbarDisplayType.BLUE)
} else { } else {
context.notNull { context.notNull {
showSnackbar(activity.floatingMenuWrapper, null, showSnackbar(activity.snackbarContainer, null,
context?.getString(R.string.used_skill_without_mana, usedSkill?.text), context?.getString(R.string.used_skill_without_mana, usedSkill?.text),
BitmapDrawable(resources, HabiticaIconsHelper.imageOfMagic()), BitmapDrawable(resources, HabiticaIconsHelper.imageOfMagic()),
ContextCompat.getColor(it, R.color.blue_10), "-" + usedSkill?.mana, ContextCompat.getColor(it, R.color.blue_10), "-" + usedSkill?.mana,

View file

@ -195,7 +195,7 @@ class ChatFragment : BaseFragment(), SwipeRefreshLayout.OnRefreshListener {
clipMan?.primaryClip = messageText clipMan?.primaryClip = messageText
val activity = activity as? MainActivity val activity = activity as? MainActivity
if (activity != null) { if (activity != null) {
showSnackbar(activity.floatingMenuWrapper, getString(R.string.chat_message_copied), SnackbarDisplayType.NORMAL) showSnackbar(activity.snackbarContainer, getString(R.string.chat_message_copied), SnackbarDisplayType.NORMAL)
} }
} }

View file

@ -250,7 +250,7 @@ class ChatListFragment : BaseFragment(), SwipeRefreshLayout.OnRefreshListener {
clipMan?.primaryClip = messageText clipMan?.primaryClip = messageText
val activity = activity as? MainActivity val activity = activity as? MainActivity
if (activity != null) { if (activity != null) {
showSnackbar(activity.floatingMenuWrapper, getString(R.string.chat_message_copied), SnackbarDisplayType.NORMAL) showSnackbar(activity.snackbarContainer, getString(R.string.chat_message_copied), SnackbarDisplayType.NORMAL)
} }
} }

View file

@ -102,7 +102,7 @@ class GroupInformationFragment : BaseFragment() {
clipboard?.primaryClip = clip clipboard?.primaryClip = clip
val activity = activity as? MainActivity val activity = activity as? MainActivity
if (activity != null) { if (activity != null) {
HabiticaSnackbar.showSnackbar(activity.floatingMenuWrapper, getString(R.string.username_copied), HabiticaSnackbar.SnackbarDisplayType.NORMAL) HabiticaSnackbar.showSnackbar(activity.snackbarContainer, getString(R.string.username_copied), HabiticaSnackbar.SnackbarDisplayType.NORMAL)
} }
} }

View file

@ -80,7 +80,7 @@ class GuildDetailFragment : BaseFragment() {
viewModel?.leaveGroup { viewModel?.leaveGroup {
val activity = activity as? MainActivity val activity = activity as? MainActivity
if (activity != null) { if (activity != null) {
HabiticaSnackbar.showSnackbar(activity.floatingMenuWrapper, getString(R.string.left_guild), HabiticaSnackbar.SnackbarDisplayType.NORMAL) HabiticaSnackbar.showSnackbar(activity.snackbarContainer, getString(R.string.left_guild), HabiticaSnackbar.SnackbarDisplayType.NORMAL)
} }
} }
} }
@ -88,7 +88,7 @@ class GuildDetailFragment : BaseFragment() {
viewModel?.joinGroup { viewModel?.joinGroup {
val activity = activity as? MainActivity val activity = activity as? MainActivity
if (activity != null) { if (activity != null) {
HabiticaSnackbar.showSnackbar(activity.floatingMenuWrapper, getString(R.string.joined_guild), HabiticaSnackbar.SnackbarDisplayType.NORMAL) HabiticaSnackbar.showSnackbar(activity.snackbarContainer, getString(R.string.joined_guild), HabiticaSnackbar.SnackbarDisplayType.NORMAL)
} }
} }
} }

View file

@ -143,7 +143,7 @@ class InboxMessageListFragment : BaseMainFragment(), androidx.swiperefreshlayout
clipMan?.primaryClip = messageText clipMan?.primaryClip = messageText
val activity = getActivity() as? MainActivity val activity = getActivity() as? MainActivity
if (activity != null) { if (activity != null) {
showSnackbar(activity.floatingMenuWrapper, getString(R.string.chat_message_copied), HabiticaSnackbar.SnackbarDisplayType.NORMAL) showSnackbar(activity.snackbarContainer, getString(R.string.chat_message_copied), HabiticaSnackbar.SnackbarDisplayType.NORMAL)
} }
} }

View file

@ -99,7 +99,7 @@ open class TaskRecyclerViewFragment : BaseFragment(), androidx.swiperefreshlayou
.doOnNext { soundManager.loadAndPlayAudio(SoundManager.SoundTodo) } .doOnNext { soundManager.loadAndPlayAudio(SoundManager.SoundTodo) }
.flatMap { taskRepository.taskChecked(user, it.first, it.second == TaskDirection.UP, false) { _ -> .flatMap { taskRepository.taskChecked(user, it.first, it.second == TaskDirection.UP, false) { _ ->
(activity as? MainActivity)?.let { activity -> (activity as? MainActivity)?.let { activity ->
HabiticaSnackbar.showSnackbar(activity.floatingMenuWrapper, null, getString(R.string.notification_purchase_reward), HabiticaSnackbar.showSnackbar(activity.snackbarContainer, null, getString(R.string.notification_purchase_reward),
BitmapDrawable(resources, HabiticaIconsHelper.imageOfGold()), BitmapDrawable(resources, HabiticaIconsHelper.imageOfGold()),
ContextCompat.getColor(activity, R.color.yellow_10), ContextCompat.getColor(activity, R.color.yellow_10),
"-" + it.first.value.toInt(), "-" + it.first.value.toInt(),

View file

@ -5,7 +5,6 @@ import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.view.* import android.view.*
import androidx.fragment.app.FragmentPagerAdapter import androidx.fragment.app.FragmentPagerAdapter
import com.github.clans.fab.FloatingActionButton
import com.github.clans.fab.FloatingActionMenu import com.github.clans.fab.FloatingActionMenu
import com.habitrpg.android.habitica.HabiticaBaseApplication import com.habitrpg.android.habitica.HabiticaBaseApplication
import com.habitrpg.android.habitica.R import com.habitrpg.android.habitica.R
@ -44,32 +43,13 @@ class TasksFragment : BaseMainFragment() {
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? { savedInstanceState: Bundle?): View? {
this.usesTabLayout = false this.usesTabLayout = false
this.usesBottomNavigation = true this.usesBottomNavigation = true
this.displayingTaskForm = false this.displayingTaskForm = false
super.onCreateView(inflater, container, savedInstanceState) super.onCreateView(inflater, container, savedInstanceState)
val v = inflater.inflate(R.layout.fragment_viewpager, container, false) val v = inflater.inflate(R.layout.fragment_viewpager, container, false)
viewPager = v.findViewById(R.id.viewPager) viewPager = v.findViewById(R.id.viewPager)
val view = inflater.inflate(R.layout.floating_menu_tasks, floatingMenuWrapper, true)
floatingMenu = if (FloatingActionMenu::class.java == view.javaClass) {
view as? FloatingActionMenu
} else {
val frame = view as? ViewGroup
frame?.findViewById(R.id.fab_menu)
}
val habitFab = floatingMenu?.findViewById<FloatingActionButton>(R.id.fab_new_habit)
habitFab?.setOnClickListener { openNewTaskActivity(Task.TYPE_HABIT) }
val dailyFab = floatingMenu?.findViewById<FloatingActionButton>(R.id.fab_new_daily)
dailyFab?.setOnClickListener { openNewTaskActivity(Task.TYPE_DAILY) }
val todoFab = floatingMenu?.findViewById<FloatingActionButton>(R.id.fab_new_todo)
todoFab?.setOnClickListener { openNewTaskActivity(Task.TYPE_TODO) }
val rewardFab = floatingMenu?.findViewById<FloatingActionButton>(R.id.fab_new_reward)
rewardFab?.setOnClickListener { openNewTaskActivity(Task.TYPE_REWARD) }
floatingMenu?.setOnMenuButtonLongClickListener { this.onFloatingMenuLongClicked() }
loadTaskLists() loadTaskLists()
return v return v
@ -78,33 +58,25 @@ class TasksFragment : BaseMainFragment() {
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()
bottomNavigation?.setBadgesHideWhenActive(true) bottomNavigation?.onTabSelectedListener = {
bottomNavigation?.setOnTabSelectListener { tabId -> when (it) {
when (tabId) { Task.TYPE_HABIT -> viewPager?.currentItem = 0
R.id.tab_habits -> viewPager?.currentItem = 0 Task.TYPE_DAILY -> viewPager?.currentItem = 1
R.id.tab_dailies -> viewPager?.currentItem = 1 Task.TYPE_TODO -> viewPager?.currentItem = 2
R.id.tab_todos -> viewPager?.currentItem = 2 Task.TYPE_REWARD -> viewPager?.currentItem = 3
R.id.tab_rewards -> viewPager?.currentItem = 3
} }
updateBottomBarBadges() updateBottomBarBadges()
} }
bottomNavigation?.onAddListener = {
openNewTaskActivity(it)
}
} }
override fun onDestroy() { override fun onDestroy() {
tagRepository.close() tagRepository.close()
bottomNavigation?.removeOnTabSelectListener()
super.onDestroy() super.onDestroy()
} }
private fun onFloatingMenuLongClicked(): Boolean {
val currentFragment = activeFragment
if (currentFragment != null) {
val className = currentFragment.className
openNewTaskActivity(className)
}
return true
}
override fun injectFragment(component: UserComponent) { override fun injectFragment(component: UserComponent) {
component.inject(this) component.inject(this)
} }
@ -145,7 +117,6 @@ class TasksFragment : BaseMainFragment() {
} }
} }
dialog.setListener(object : TaskFilterDialog.OnFilterCompletedListener { dialog.setListener(object : TaskFilterDialog.OnFilterCompletedListener {
override fun onFilterCompleted(activeTaskFilter: String?, activeTags: MutableList<String>) { override fun onFilterCompleted(activeTaskFilter: String?, activeTags: MutableList<String>) {
if (viewFragmentsDictionary == null) { if (viewFragmentsDictionary == null) {
return return
@ -204,7 +175,7 @@ class TasksFragment : BaseMainFragment() {
} }
override fun onPageSelected(position: Int) { override fun onPageSelected(position: Int) {
bottomNavigation?.selectTabAtPosition(position) bottomNavigation?.selectedPosition = position
updateFilterIcon() updateFilterIcon()
} }
@ -233,7 +204,7 @@ class TasksFragment : BaseMainFragment() {
if (bottomNavigation == null) { if (bottomNavigation == null) {
return return
} }
tutorialRepository.getTutorialSteps(Arrays.asList("habits", "dailies", "todos", "rewards")).subscribe(Consumer { tutorialSteps -> compositeSubscription.add(tutorialRepository.getTutorialSteps(Arrays.asList("habits", "dailies", "todos", "rewards")).subscribe(Consumer { tutorialSteps ->
val activeTutorialFragments = ArrayList<String>() val activeTutorialFragments = ArrayList<String>()
for (step in tutorialSteps) { for (step in tutorialSteps) {
var id = -1 var id = -1
@ -256,13 +227,13 @@ class TasksFragment : BaseMainFragment() {
} }
else -> "" else -> ""
} }
val tab = bottomNavigation?.getTabWithId(id) /*val tab = bottomNavigation?.id(id)
if (step.shouldDisplay()) { if (step.shouldDisplay()) {
tab?.setBadgeCount(1) tab?.setBadgeCount(1)
activeTutorialFragments.add(taskType) activeTutorialFragments.add(taskType)
} else { } else {
tab?.removeBadge() tab?.removeBadge()
} }*/
} }
if (activeTutorialFragments.size == 1) { if (activeTutorialFragments.size == 1) {
val fragment = viewFragmentsDictionary?.get(indexForTaskType(activeTutorialFragments[0])) val fragment = viewFragmentsDictionary?.get(indexForTaskType(activeTutorialFragments[0]))
@ -273,7 +244,7 @@ class TasksFragment : BaseMainFragment() {
} }
} }
} }
}, RxErrorHandler.handleEmptyError()) }, RxErrorHandler.handleEmptyError()))
} }
// endregion // endregion

View file

@ -3,15 +3,16 @@ package com.habitrpg.android.habitica.ui.helpers;
// https://gist.github.com/lodlock/e3cd12130bad70a098db // https://gist.github.com/lodlock/e3cd12130bad70a098db
import android.content.Context; import android.content.Context;
import androidx.annotation.Nullable;
import androidx.coordinatorlayout.widget.CoordinatorLayout;
import com.google.android.material.snackbar.Snackbar;
import androidx.core.view.ViewCompat;
import android.util.AttributeSet; import android.util.AttributeSet;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import androidx.annotation.Nullable;
import androidx.coordinatorlayout.widget.CoordinatorLayout;
import androidx.core.view.ViewCompat;
import com.github.clans.fab.FloatingActionMenu; import com.github.clans.fab.FloatingActionMenu;
import com.google.android.material.snackbar.Snackbar;
import com.habitrpg.android.habitica.R; import com.habitrpg.android.habitica.R;
import java.util.List; import java.util.List;
@ -59,7 +60,7 @@ public class FloatingActionMenuBehavior extends CoordinatorLayout.Behavior {
.translationY(translationY) .translationY(translationY)
.setListener(null); .setListener(null);
} else { } else {
ViewCompat.setTranslationY(child, translationY); child.setTranslationY(translationY);
} }
this.mTranslationY = translationY; this.mTranslationY = translationY;
@ -75,7 +76,7 @@ public class FloatingActionMenuBehavior extends CoordinatorLayout.Behavior {
for (int z = dependencies.size(); i < z; ++i) { for (int z = dependencies.size(); i < z; ++i) {
View view = (View) dependencies.get(i); View view = (View) dependencies.get(i);
if (view instanceof Snackbar.SnackbarLayout && parent.doViewsOverlap(child, view)) { if (view instanceof Snackbar.SnackbarLayout && parent.doViewsOverlap(child, view)) {
minOffset = Math.min(minOffset, ViewCompat.getTranslationY(view) - (float) view.getHeight()); minOffset = Math.min(minOffset, view.getTranslationY() - (float) view.getHeight());
} }
} }
@ -84,7 +85,7 @@ public class FloatingActionMenuBehavior extends CoordinatorLayout.Behavior {
@Override @Override
public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout, View child, View directTargetChild, View target, int nestedScrollAxes) { public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout, View child, View directTargetChild, View target, int nestedScrollAxes) {
ViewGroup fabContainer = child.findViewById(R.id.floating_menu_wrapper); ViewGroup fabContainer = child.findViewById(R.id.snackbar_container);
if (fabContainer.getChildCount() > 0) { if (fabContainer.getChildCount() > 0) {
if (fabContainer.getChildAt(0).getClass().equals(FloatingActionMenu.class)) { if (fabContainer.getChildAt(0).getClass().equals(FloatingActionMenu.class)) {
fab = (FloatingActionMenu) fabContainer.getChildAt(0); fab = (FloatingActionMenu) fabContainer.getChildAt(0);

View file

@ -1,18 +1,17 @@
package com.habitrpg.android.habitica.ui.views package com.habitrpg.android.habitica.ui.views
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable
import androidx.annotation.ColorInt
import com.google.android.material.snackbar.BaseTransientBottomBar
import com.google.android.material.snackbar.Snackbar
import androidx.core.content.ContextCompat
import androidx.core.view.ViewCompat
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 android.widget.ImageView import android.widget.ImageView
import android.widget.LinearLayout import android.widget.LinearLayout
import android.widget.TextView import android.widget.TextView
import androidx.annotation.ColorInt
import androidx.core.content.ContextCompat
import androidx.core.view.ViewCompat
import com.google.android.material.snackbar.BaseTransientBottomBar
import com.google.android.material.snackbar.Snackbar
import com.habitrpg.android.habitica.R import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.ui.helpers.NavbarUtils import com.habitrpg.android.habitica.ui.helpers.NavbarUtils
@ -87,13 +86,17 @@ private constructor(parent: ViewGroup, content: View, callback: ContentViewCallb
override fun animateContentIn(delay: Int, duration: Int) { override fun animateContentIn(delay: Int, duration: Int) {
content.scaleY = 0f content.scaleY = 0f
content.scaleX = 0f
ViewCompat.animate(content).scaleY(1f).setDuration(duration.toLong()).startDelay = delay.toLong() ViewCompat.animate(content).scaleY(1f).setDuration(duration.toLong()).startDelay = delay.toLong()
ViewCompat.animate(content).scaleX(1f).setDuration(duration.toLong()).startDelay = delay.toLong()
ViewCompat.animate(content).alpha(1f).setDuration(duration.toLong()).startDelay = delay.toLong() ViewCompat.animate(content).alpha(1f).setDuration(duration.toLong()).startDelay = delay.toLong()
} }
override fun animateContentOut(delay: Int, duration: Int) { override fun animateContentOut(delay: Int, duration: Int) {
content.scaleY = 1f content.scaleY = 1f
content.scaleX = 1f
ViewCompat.animate(content).scaleY(0f).setDuration(duration.toLong()).startDelay = delay.toLong() ViewCompat.animate(content).scaleY(0f).setDuration(duration.toLong()).startDelay = delay.toLong()
ViewCompat.animate(content).scaleX(0f).setDuration(duration.toLong()).startDelay = delay.toLong()
ViewCompat.animate(content).alpha(0f).setDuration(duration.toLong()).startDelay = delay.toLong() ViewCompat.animate(content).alpha(0f).setDuration(duration.toLong()).startDelay = delay.toLong()
} }
} }

View file

@ -1,58 +0,0 @@
/*
* BottomBar library for Android
* Copyright (c) 2016 Iiro Krankka (http://github.com/roughike).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.habitrpg.android.habitica.ui.views.bottombar;
import android.graphics.drawable.ShapeDrawable;
import android.graphics.drawable.shapes.OvalShape;
import androidx.annotation.ColorInt;
import androidx.annotation.IntRange;
import androidx.annotation.NonNull;
/*
* BottomBar library for Android
* Copyright (c) 2016 Iiro Krankka (http://github.com/roughike).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
class BadgeCircle {
/**
* Creates a new circle for the Badge background.
*
* @param size the width and height for the circle
* @param color the activeIconColor for the circle
* @return a nice and adorable circle.
*/
@NonNull
static ShapeDrawable make(@IntRange(from = 0) int size, @ColorInt int color) {
ShapeDrawable indicator = new ShapeDrawable(new OvalShape());
indicator.setIntrinsicWidth(size);
indicator.setIntrinsicHeight(size);
indicator.getPaint().setColor(color);
return indicator;
}
}

View file

@ -1,14 +0,0 @@
package com.habitrpg.android.habitica.ui.views.bottombar;
import android.content.Context;
import androidx.annotation.NonNull;
import android.widget.FrameLayout;
/**
* Created by iiro on 29.8.2016.
*/
public class BadgeContainer extends FrameLayout {
public BadgeContainer(@NonNull Context context) {
super(context);
}
}

View file

@ -1,26 +0,0 @@
package com.habitrpg.android.habitica.ui.views.bottombar;
import androidx.annotation.NonNull;
class BatchTabPropertyApplier {
private final BottomBar bottomBar;
interface TabPropertyUpdater {
void update(BottomBarTab tab);
}
BatchTabPropertyApplier(@NonNull BottomBar bottomBar) {
this.bottomBar = bottomBar;
}
void applyToAllTabs(@NonNull TabPropertyUpdater propertyUpdater) {
int tabCount = bottomBar.getTabCount();
if (tabCount > 0) {
for (int i = 0; i < tabCount; i++) {
BottomBarTab tab = bottomBar.getTabAtPosition(i);
propertyUpdater.update(tab);
}
}
}
}

View file

@ -1,172 +0,0 @@
package com.habitrpg.android.habitica.ui.views.bottombar;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.ShapeDrawable;
import android.os.Build;
import android.view.Gravity;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.widget.TextView;
import com.habitrpg.android.habitica.R;
import androidx.appcompat.widget.AppCompatImageView;
import androidx.appcompat.widget.AppCompatTextView;
import androidx.core.view.ViewCompat;
/*
* BottomBar library for Android
* Copyright (c) 2016 Iiro Krankka (http://github.com/roughike).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
class BottomBarBadge extends AppCompatTextView {
private int count;
private boolean isVisible = false;
BottomBarBadge(Context context) {
super(context);
}
/**
* Set the unread / new item / whatever count for this Badge.
*
* @param count the value this Badge should show.
*/
void setCount(int count) {
this.count = count;
setText(String.valueOf(count));
}
/**
* Get the currently showing count for this Badge.
*
* @return current count for the Badge.
*/
int getCount() {
return count;
}
/**
* Shows the badge with a neat little scale animation.
*/
void show() {
isVisible = true;
ViewCompat.animate(this)
.setDuration(150)
.alpha(1)
.scaleX(1)
.scaleY(1)
.start();
}
/**
* Hides the badge with a neat little scale animation.
*/
void hide() {
isVisible = false;
ViewCompat.animate(this)
.setDuration(150)
.alpha(0)
.scaleX(0)
.scaleY(0)
.start();
}
/**
* Is this badge currently visible?
*
* @return true is this badge is visible, otherwise false.
*/
boolean isVisible() {
return isVisible;
}
void attachToTab(BottomBarTab tab, int backgroundColor) {
ViewGroup.LayoutParams params = new ViewGroup.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
setLayoutParams(params);
setGravity(Gravity.CENTER);
MiscUtils.setTextAppearance(this, R.style.BB_BottomBarBadge_Text);
setColoredCircleBackground(backgroundColor);
wrapTabAndBadgeInSameContainer(tab);
}
void setColoredCircleBackground(int circleColor) {
int innerPadding = MiscUtils.dpToPixel(getContext(), 1);
ShapeDrawable backgroundCircle = BadgeCircle.make(innerPadding * 3, circleColor);
setPadding(innerPadding, innerPadding, innerPadding, innerPadding);
setBackgroundCompat(backgroundCircle);
}
private void wrapTabAndBadgeInSameContainer(final BottomBarTab tab) {
ViewGroup tabContainer = (ViewGroup) tab.getParent();
tabContainer.removeView(tab);
final BadgeContainer badgeContainer = new BadgeContainer(getContext());
badgeContainer.setLayoutParams(new ViewGroup.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
badgeContainer.addView(tab);
badgeContainer.addView(this);
tabContainer.addView(badgeContainer, tab.getIndexInTabContainer());
badgeContainer.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@SuppressWarnings("deprecation")
@Override
public void onGlobalLayout() {
badgeContainer.getViewTreeObserver().removeGlobalOnLayoutListener(this);
adjustPositionAndSize(tab);
}
});
}
void removeFromTab(BottomBarTab tab) {
BadgeContainer badgeAndTabContainer = (BadgeContainer) getParent();
ViewGroup originalTabContainer = (ViewGroup) badgeAndTabContainer.getParent();
badgeAndTabContainer.removeView(tab);
originalTabContainer.removeView(badgeAndTabContainer);
originalTabContainer.addView(tab, tab.getIndexInTabContainer());
}
void adjustPositionAndSize(BottomBarTab tab) {
AppCompatImageView iconView = tab.getIconView();
ViewGroup.LayoutParams params = getLayoutParams();
int size = Math.max(getWidth(), getHeight());
float xOffset = (float) (iconView.getWidth() / 1.25);
setX(iconView.getX() + xOffset);
setTranslationY(10);
if (params.width != size || params.height != size) {
params.width = size;
params.height = size;
setLayoutParams(params);
}
}
@SuppressWarnings("deprecation")
private void setBackgroundCompat(Drawable background) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
setBackground(background);
} else {
setBackgroundDrawable(background);
}
}
}

View file

@ -1,730 +0,0 @@
package com.habitrpg.android.habitica.ui.views.bottombar;
import android.animation.ArgbEvaluator;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Typeface;
import android.os.Build;
import android.os.Bundle;
import android.os.Parcelable;
import androidx.annotation.ColorInt;
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
import androidx.appcompat.widget.AppCompatImageView;
import androidx.core.view.ViewCompat;
import androidx.core.view.ViewPropertyAnimatorCompat;
import android.view.Gravity;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import android.widget.TextView;
import com.habitrpg.android.habitica.R;
/*
* BottomBar library for Android
* Copyright (c) 2016 Iiro Krankka (http://github.com/roughike).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
public class BottomBarTab extends LinearLayout {
@VisibleForTesting
static final String STATE_BADGE_COUNT = "STATE_BADGE_COUNT_FOR_TAB_";
private static final long ANIMATION_DURATION = 150;
private static final float ACTIVE_TITLE_SCALE = 1;
private static final float INACTIVE_FIXED_TITLE_SCALE = 0.86f;
private static final float ACTIVE_SHIFTING_TITLELESS_ICON_SCALE = 1.24f;
private static final float INACTIVE_SHIFTING_TITLELESS_ICON_SCALE = 1f;
private final int sixDps;
private final int eightDps;
private final int sixteenDps;
@VisibleForTesting
BottomBarBadge badge;
private Type type = Type.FIXED;
private boolean isTitleless;
private int iconResId;
private String title;
private float inActiveAlpha;
private float activeAlpha;
private int inActiveColor;
private int activeColor;
private int barColorWhenSelected;
private int badgeBackgroundColor;
private boolean badgeHidesWhenActive;
private AppCompatImageView iconView;
private TextView titleView;
private boolean isActive;
private int indexInContainer;
private int titleTextAppearanceResId;
private Typeface titleTypeFace;
BottomBarTab(Context context) {
super(context);
sixDps = MiscUtils.dpToPixel(context, 6);
eightDps = MiscUtils.dpToPixel(context, 8);
sixteenDps = MiscUtils.dpToPixel(context, 16);
}
void setConfig(@NonNull Config config) {
setInActiveAlpha(config.inActiveTabAlpha);
setActiveAlpha(config.activeTabAlpha);
setInActiveColor(config.inActiveTabColor);
setActiveColor(config.activeTabColor);
setBarColorWhenSelected(config.barColorWhenSelected);
setBadgeBackgroundColor(config.badgeBackgroundColor);
setBadgeHidesWhenActive(config.badgeHidesWhenSelected);
setTitleTextAppearance(config.titleTextAppearance);
setTitleTypeface(config.titleTypeFace);
}
void prepareLayout() {
inflate(getContext(), getLayoutResource(), this);
setOrientation(VERTICAL);
setGravity(isTitleless? Gravity.CENTER : Gravity.CENTER_HORIZONTAL);
setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
setBackgroundResource(MiscUtils.getDrawableRes(getContext(), R.attr.selectableItemBackgroundBorderless));
iconView = findViewById(R.id.bb_bottom_bar_icon);
iconView.setImageResource(iconResId);
if (type != Type.TABLET && !isTitleless) {
titleView = findViewById(R.id.bb_bottom_bar_title);
titleView.setVisibility(VISIBLE);
if (type == Type.SHIFTING) {
findViewById(R.id.spacer).setVisibility(VISIBLE);
}
updateTitle();
}
updateCustomTextAppearance();
updateCustomTypeface();
}
@VisibleForTesting
int getLayoutResource() {
int layoutResource;
switch (type) {
case FIXED:
layoutResource = R.layout.bb_bottom_bar_item_fixed;
break;
case SHIFTING:
layoutResource = R.layout.bb_bottom_bar_item_shifting;
break;
case TABLET:
layoutResource = R.layout.bb_bottom_bar_item_fixed_tablet;
break;
default:
// should never happen
throw new RuntimeException("Unknown BottomBarTab type.");
}
return layoutResource;
}
private void updateTitle() {
if (titleView != null) {
titleView.setText(title);
}
}
@SuppressWarnings("deprecation")
private void updateCustomTextAppearance() {
if (titleView == null || titleTextAppearanceResId == 0) {
return;
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
titleView.setTextAppearance(titleTextAppearanceResId);
} else {
titleView.setTextAppearance(getContext(), titleTextAppearanceResId);
}
titleView.setTag(R.id.bb_bottom_bar_appearance_id, titleTextAppearanceResId);
}
private void updateCustomTypeface() {
if (titleTypeFace != null && titleView != null) {
titleView.setTypeface(titleTypeFace);
}
}
Type getType() {
return type;
}
void setType(Type type) {
this.type = type;
}
boolean isTitleless() {
return isTitleless;
}
void setIsTitleless(boolean isTitleless) {
if (isTitleless && getIconResId() == 0) {
throw new IllegalStateException("This tab is supposed to be " +
"icon only, yet it has no icon specified. Index in " +
"container: " + getIndexInTabContainer());
}
this.isTitleless = isTitleless;
}
public ViewGroup getOuterView() {
return (ViewGroup) getParent();
}
AppCompatImageView getIconView() {
return iconView;
}
int getIconResId() {
return iconResId;
}
void setIconResId(int iconResId) {
this.iconResId = iconResId;
}
TextView getTitleView() {
return titleView;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
updateTitle();
}
public float getInActiveAlpha() {
return inActiveAlpha;
}
public void setInActiveAlpha(float inActiveAlpha) {
this.inActiveAlpha = inActiveAlpha;
if (!isActive) {
setAlphas(inActiveAlpha);
}
}
public float getActiveAlpha() {
return activeAlpha;
}
public void setActiveAlpha(float activeAlpha) {
this.activeAlpha = activeAlpha;
if (isActive) {
setAlphas(activeAlpha);
}
}
public int getInActiveColor() {
return inActiveColor;
}
public void setInActiveColor(int inActiveColor) {
this.inActiveColor = inActiveColor;
if (!isActive) {
setColors(inActiveColor);
}
}
public int getActiveColor() {
return activeColor;
}
public void setActiveColor(int activeIconColor) {
this.activeColor = activeIconColor;
if (isActive) {
setColors(activeColor);
}
}
public int getBarColorWhenSelected() {
return barColorWhenSelected;
}
public void setBarColorWhenSelected(int barColorWhenSelected) {
this.barColorWhenSelected = barColorWhenSelected;
}
public int getBadgeBackgroundColor() {
return badgeBackgroundColor;
}
public void setBadgeBackgroundColor(int badgeBackgroundColor) {
this.badgeBackgroundColor = badgeBackgroundColor;
if (badge != null) {
badge.setColoredCircleBackground(badgeBackgroundColor);
}
}
public boolean getBadgeHidesWhenActive() {
return badgeHidesWhenActive;
}
public void setBadgeHidesWhenActive(boolean hideWhenActive) {
this.badgeHidesWhenActive = hideWhenActive;
}
int getCurrentDisplayedIconColor() {
Object tag = iconView.getTag(R.id.bb_bottom_bar_color_id);
if (tag instanceof Integer) {
return (int) tag;
}
return 0;
}
int getCurrentDisplayedTitleColor() {
if (titleView != null) {
return titleView.getCurrentTextColor();
}
return 0;
}
int getCurrentDisplayedTextAppearance() {
Object tag = titleView.getTag(R.id.bb_bottom_bar_appearance_id);
if (titleView != null && tag instanceof Integer) {
return (int) tag;
}
return 0;
}
public void setBadgeCount(int count) {
if (count <= 0) {
if (badge != null) {
badge.removeFromTab(this);
badge = null;
}
return;
}
if (badge == null) {
badge = new BottomBarBadge(getContext());
badge.attachToTab(this, badgeBackgroundColor);
}
badge.setCount(count);
if (isActive && badgeHidesWhenActive) {
badge.hide();
}
}
public void removeBadge() {
setBadgeCount(0);
}
boolean isActive() {
return isActive;
}
boolean hasActiveBadge() {
return badge != null;
}
int getIndexInTabContainer() {
return indexInContainer;
}
void setIndexInContainer(int indexInContainer) {
this.indexInContainer = indexInContainer;
}
void setIconTint(int tint) {
iconView.setColorFilter(tint);
}
public int getTitleTextAppearance() {
return titleTextAppearanceResId;
}
@SuppressWarnings("deprecation")
void setTitleTextAppearance(int resId) {
this.titleTextAppearanceResId = resId;
updateCustomTextAppearance();
}
public void setTitleTypeface(Typeface typeface) {
this.titleTypeFace = typeface;
updateCustomTypeface();
}
public Typeface getTitleTypeFace() {
return titleTypeFace;
}
void select(boolean animate) {
isActive = true;
if (animate) {
animateIcon(activeAlpha, ACTIVE_SHIFTING_TITLELESS_ICON_SCALE);
animateTitle(sixDps, ACTIVE_TITLE_SCALE, activeAlpha);
animateColors(inActiveColor, activeColor);
} else {
setTitleScale(ACTIVE_TITLE_SCALE);
setTopPadding(sixDps);
setIconScale(ACTIVE_SHIFTING_TITLELESS_ICON_SCALE);
setColors(activeColor);
setAlphas(activeAlpha);
}
setSelected(true);
if (badge != null && badgeHidesWhenActive) {
badge.hide();
}
}
void deselect(boolean animate) {
isActive = false;
boolean isShifting = type == Type.SHIFTING;
float titleScale = isShifting ? 0 : INACTIVE_FIXED_TITLE_SCALE;
int iconPaddingTop = isShifting ? sixteenDps : eightDps;
if (animate) {
animateTitle(iconPaddingTop, titleScale, inActiveAlpha);
animateIcon(inActiveAlpha, INACTIVE_SHIFTING_TITLELESS_ICON_SCALE);
animateColors(activeColor, inActiveColor);
} else {
setTitleScale(titleScale);
setTopPadding(iconPaddingTop);
setIconScale(INACTIVE_SHIFTING_TITLELESS_ICON_SCALE);
setColors(inActiveColor);
setAlphas(inActiveAlpha);
}
setSelected(false);
if (!isShifting && badge != null && !badge.isVisible()) {
badge.show();
}
}
private void animateColors(int previousColor, int color) {
ValueAnimator anim = new ValueAnimator();
anim.setIntValues(previousColor, color);
anim.setEvaluator(new ArgbEvaluator());
anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
setColors((Integer) valueAnimator.getAnimatedValue());
}
});
anim.setDuration(150);
anim.start();
}
private void setColors(int color) {
if (iconView != null) {
iconView.setColorFilter(color);
iconView.setTag(R.id.bb_bottom_bar_color_id, color);
}
if (titleView != null) {
titleView.setTextColor(color);
}
}
private void setAlphas(float alpha) {
if (iconView != null) {
ViewCompat.setAlpha(iconView, alpha);
}
if (titleView != null) {
ViewCompat.setAlpha(titleView, alpha);
}
}
void updateWidth(float endWidth, boolean animated) {
if (!animated) {
getLayoutParams().width = (int) endWidth;
if (!isActive && badge != null) {
badge.adjustPositionAndSize(this);
badge.show();
}
return;
}
float start = getWidth();
ValueAnimator animator = ValueAnimator.ofFloat(start, endWidth);
animator.setDuration(150);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animator) {
ViewGroup.LayoutParams params = getLayoutParams();
if (params == null) return;
params.width = Math.round((float) animator.getAnimatedValue());
setLayoutParams(params);
}
});
// Workaround to avoid using faulty onAnimationEnd() listener
postDelayed(new Runnable() {
@Override
public void run() {
if (!isActive && badge != null) {
clearAnimation();
badge.adjustPositionAndSize(BottomBarTab.this);
badge.show();
}
}
}, animator.getDuration());
animator.start();
}
private void updateBadgePosition() {
if (badge != null) {
badge.adjustPositionAndSize(this);
}
}
private void setTopPaddingAnimated(int start, int end) {
if (type == Type.TABLET || isTitleless) {
return;
}
ValueAnimator paddingAnimator = ValueAnimator.ofInt(start, end);
paddingAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
iconView.setPadding(
iconView.getPaddingLeft(),
(Integer) animation.getAnimatedValue(),
iconView.getPaddingRight(),
iconView.getPaddingBottom()
);
}
});
paddingAnimator.setDuration(ANIMATION_DURATION);
paddingAnimator.start();
}
private void animateTitle(int padding, float scale, float alpha) {
if (type == Type.TABLET && isTitleless) {
return;
}
setTopPaddingAnimated(iconView.getPaddingTop(), padding);
ViewPropertyAnimatorCompat titleAnimator = ViewCompat.animate(titleView)
.setDuration(ANIMATION_DURATION)
.scaleX(scale)
.scaleY(scale);
titleAnimator.alpha(alpha);
titleAnimator.start();
}
private void animateIconScale(float scale) {
ViewCompat.animate(iconView)
.setDuration(ANIMATION_DURATION)
.scaleX(scale)
.scaleY(scale)
.start();
}
private void animateIcon(float alpha, float scale) {
ViewCompat.animate(iconView)
.setDuration(ANIMATION_DURATION)
.alpha(alpha)
.start();
if (isTitleless && type == Type.SHIFTING) {
animateIconScale(scale);
}
}
private void setTopPadding(int topPadding) {
if (type == Type.TABLET || isTitleless) {
return;
}
iconView.setPadding(
iconView.getPaddingLeft(),
topPadding,
iconView.getPaddingRight(),
iconView.getPaddingBottom()
);
}
private void setTitleScale(float scale) {
if (type == Type.TABLET || isTitleless) {
return;
}
ViewCompat.setScaleX(titleView, scale);
ViewCompat.setScaleY(titleView, scale);
}
private void setIconScale(float scale) {
if (isTitleless && type == Type.SHIFTING) {
ViewCompat.setScaleX(iconView, scale);
ViewCompat.setScaleY(iconView, scale);
}
}
@Override
public Parcelable onSaveInstanceState() {
if (badge != null) {
Bundle bundle = saveState();
bundle.putParcelable("superstate", super.onSaveInstanceState());
return bundle;
}
return super.onSaveInstanceState();
}
@VisibleForTesting
Bundle saveState() {
Bundle outState = new Bundle();
outState.putInt(STATE_BADGE_COUNT + getIndexInTabContainer(), badge.getCount());
return outState;
}
@Override
public void onRestoreInstanceState(Parcelable state) {
if (state instanceof Bundle) {
Bundle bundle = (Bundle) state;
restoreState(bundle);
state = bundle.getParcelable("superstate");
}
super.onRestoreInstanceState(state);
}
@VisibleForTesting
void restoreState(Bundle savedInstanceState) {
int previousBadgeCount = savedInstanceState.getInt(STATE_BADGE_COUNT + getIndexInTabContainer());
setBadgeCount(previousBadgeCount);
}
enum Type {
FIXED, SHIFTING, TABLET
}
public static class Config {
private final float inActiveTabAlpha;
private final float activeTabAlpha;
private final int inActiveTabColor;
private final int activeTabColor;
private final int barColorWhenSelected;
private final int badgeBackgroundColor;
private final int titleTextAppearance;
private final Typeface titleTypeFace;
private boolean badgeHidesWhenSelected = true;
private Config(Builder builder) {
this.inActiveTabAlpha = builder.inActiveTabAlpha;
this.activeTabAlpha = builder.activeTabAlpha;
this.inActiveTabColor = builder.inActiveTabColor;
this.activeTabColor = builder.activeTabColor;
this.barColorWhenSelected = builder.barColorWhenSelected;
this.badgeBackgroundColor = builder.badgeBackgroundColor;
this.badgeHidesWhenSelected = builder.hidesBadgeWhenSelected;
this.titleTextAppearance = builder.titleTextAppearance;
this.titleTypeFace = builder.titleTypeFace;
}
public static class Builder {
private float inActiveTabAlpha;
private float activeTabAlpha;
private int inActiveTabColor;
private int activeTabColor;
private int barColorWhenSelected;
private int badgeBackgroundColor;
private boolean hidesBadgeWhenSelected = true;
private int titleTextAppearance;
private Typeface titleTypeFace;
public Builder inActiveTabAlpha(float alpha) {
this.inActiveTabAlpha = alpha;
return this;
}
public Builder activeTabAlpha(float alpha) {
this.activeTabAlpha = alpha;
return this;
}
public Builder inActiveTabColor(@ColorInt int color) {
this.inActiveTabColor = color;
return this;
}
public Builder activeTabColor(@ColorInt int color) {
this.activeTabColor = color;
return this;
}
public Builder barColorWhenSelected(@ColorInt int color) {
this.barColorWhenSelected = color;
return this;
}
public Builder badgeBackgroundColor(@ColorInt int color) {
this.badgeBackgroundColor = color;
return this;
}
public Builder hideBadgeWhenSelected(boolean hide) {
this.hidesBadgeWhenSelected = hide;
return this;
}
public Builder titleTextAppearance(int titleTextAppearance) {
this.titleTextAppearance = titleTextAppearance;
return this;
}
public Builder titleTypeFace(Typeface titleTypeFace) {
this.titleTypeFace = titleTypeFace;
return this;
}
public Config build() {
return new Config(this);
}
}
}
}

View file

@ -1,153 +0,0 @@
package com.habitrpg.android.habitica.ui.views.bottombar;
import android.os.Build;
import androidx.annotation.NonNull;
import androidx.coordinatorlayout.widget.CoordinatorLayout;
import androidx.core.view.ViewCompat;
import androidx.core.view.ViewPropertyAnimatorCompat;
import androidx.interpolator.view.animation.LinearOutSlowInInterpolator;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.Interpolator;
import com.google.android.material.snackbar.Snackbar;
/**
* Created by Nikola D. on 3/15/2016.
*
* Credit goes to Nikola Despotoski:
* https://github.com/NikolaDespotoski
*/
class BottomNavigationBehavior<V extends View> extends VerticalScrollingBehavior<V> {
private static final Interpolator INTERPOLATOR = new LinearOutSlowInInterpolator();
private final int bottomNavHeight;
private final int defaultOffset;
private boolean isTablet = false;
private ViewPropertyAnimatorCompat mTranslationAnimator;
private boolean hidden = false;
private int mSnackbarHeight = -1;
private final BottomNavigationWithSnackbar mWithSnackBarImpl = new LollipopBottomNavWithSnackBarImpl();
private boolean mScrollingEnabled = true;
BottomNavigationBehavior(int bottomNavHeight, int defaultOffset, boolean tablet) {
this.bottomNavHeight = bottomNavHeight;
this.defaultOffset = defaultOffset;
isTablet = tablet;
}
@Override
public boolean layoutDependsOn(CoordinatorLayout parent, V child, View dependency) {
mWithSnackBarImpl.updateSnackbar(parent, dependency, child);
return dependency instanceof Snackbar.SnackbarLayout;
}
@Override
public void onNestedVerticalOverScroll(CoordinatorLayout coordinatorLayout, V child, @ScrollDirection int direction, int currentOverScroll, int totalOverScroll) {
}
@Override
public void onDependentViewRemoved(CoordinatorLayout parent, V child, View dependency) {
updateScrollingForSnackbar(dependency, true);
super.onDependentViewRemoved(parent, child, dependency);
}
private void updateScrollingForSnackbar(View dependency, boolean enabled) {
if (!isTablet && dependency instanceof Snackbar.SnackbarLayout) {
mScrollingEnabled = enabled;
}
}
@Override
public boolean onDependentViewChanged(CoordinatorLayout parent, V child, View dependency) {
updateScrollingForSnackbar(dependency, false);
return super.onDependentViewChanged(parent, child, dependency);
}
@Override
public void onDirectionNestedPreScroll(CoordinatorLayout coordinatorLayout, V child, View target, int dx, int dy, int[] consumed, @ScrollDirection int scrollDirection) {
handleDirection(child, scrollDirection);
}
private void handleDirection(V child, int scrollDirection) {
if (!mScrollingEnabled) return;
if (scrollDirection == ScrollDirection.SCROLL_DIRECTION_DOWN && hidden) {
hidden = false;
animateOffset(child, defaultOffset);
} else if (scrollDirection == ScrollDirection.SCROLL_DIRECTION_UP && !hidden) {
hidden = true;
animateOffset(child, bottomNavHeight + defaultOffset);
}
}
@Override
protected boolean onNestedDirectionFling(CoordinatorLayout coordinatorLayout, V child, View target, float velocityX, float velocityY, @ScrollDirection int scrollDirection) {
handleDirection(child, scrollDirection);
return true;
}
private void animateOffset(final V child, final int offset) {
ensureOrCancelAnimator(child);
mTranslationAnimator.translationY(offset).start();
}
private void ensureOrCancelAnimator(V child) {
if (mTranslationAnimator == null) {
mTranslationAnimator = ViewCompat.animate(child);
mTranslationAnimator.setDuration(300);
mTranslationAnimator.setInterpolator(INTERPOLATOR);
} else {
mTranslationAnimator.cancel();
}
}
void setHidden(@NonNull V view, boolean bottomLayoutHidden) {
if (!bottomLayoutHidden && hidden) {
animateOffset(view, defaultOffset);
} else if (bottomLayoutHidden && !hidden) {
animateOffset(view, bottomNavHeight + defaultOffset);
}
hidden = bottomLayoutHidden;
}
static <V extends View> BottomNavigationBehavior<V> from(@NonNull V view) {
ViewGroup.LayoutParams params = view.getLayoutParams();
if (!(params instanceof CoordinatorLayout.LayoutParams)) {
throw new IllegalArgumentException("The view is not a child of CoordinatorLayout");
}
CoordinatorLayout.Behavior behavior = ((CoordinatorLayout.LayoutParams) params)
.getBehavior();
if (behavior instanceof BottomNavigationBehavior) {
// noinspection unchecked
return (BottomNavigationBehavior<V>) behavior;
}
throw new IllegalArgumentException("The view is not associated with BottomNavigationBehavior");
}
private interface BottomNavigationWithSnackbar {
void updateSnackbar(CoordinatorLayout parent, View dependency, View child);
}
private class LollipopBottomNavWithSnackBarImpl implements BottomNavigationWithSnackbar {
@Override
public void updateSnackbar(CoordinatorLayout parent, View dependency, View child) {
if (!isTablet && dependency instanceof Snackbar.SnackbarLayout) {
if (mSnackbarHeight == -1) {
mSnackbarHeight = dependency.getHeight();
}
if (ViewCompat.getTranslationY(child) != 0) return;
int targetPadding = (mSnackbarHeight + bottomNavHeight - defaultOffset);
dependency.setPadding(dependency.getPaddingLeft(),
dependency.getPaddingTop(), dependency.getPaddingRight(), targetPadding
);
}
}
}
}

View file

@ -1,121 +0,0 @@
package com.habitrpg.android.habitica.ui.views.bottombar;
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.os.Build;
import androidx.annotation.AttrRes;
import androidx.annotation.ColorInt;
import androidx.annotation.Dimension;
import androidx.annotation.DrawableRes;
import androidx.annotation.NonNull;
import androidx.annotation.Px;
import androidx.annotation.StyleRes;
import android.util.DisplayMetrics;
import android.util.TypedValue;
import android.widget.TextView;
import static androidx.annotation.Dimension.DP;
/*
* BottomBar library for Android
* Copyright (c) 2016 Iiro Krankka (http://github.com/roughike).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
class MiscUtils {
@NonNull protected static TypedValue getTypedValue(@NonNull Context context, @AttrRes int resId) {
TypedValue tv = new TypedValue();
context.getTheme().resolveAttribute(resId, tv, true);
return tv;
}
@ColorInt
protected static int getColor(@NonNull Context context, @AttrRes int color) {
return getTypedValue(context, color).data;
}
@DrawableRes
protected static int getDrawableRes(@NonNull Context context, @AttrRes int drawable) {
return getTypedValue(context, drawable).resourceId;
}
/**
* Converts dps to pixels nicely.
*
* @param context the Context for getting the resources
* @param dp dimension in dps
* @return dimension in pixels
*/
protected static int dpToPixel(@NonNull Context context, @Dimension(unit = DP) float dp) {
Resources resources = context.getResources();
DisplayMetrics metrics = resources.getDisplayMetrics();
try {
return (int) (dp * metrics.density);
} catch (NoSuchFieldError ignored) {
return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, metrics);
}
}
/**
* Converts pixels to dps just as well.
*
* @param context the Context for getting the resources
* @param px dimension in pixels
* @return dimension in dps
*/
protected static int pixelToDp(@NonNull Context context, @Px int px) {
DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics();
return Math.round(px / displayMetrics.density);
}
/**
* Returns screen width.
*
* @param context Context to get resources and device specific display metrics
* @return screen width
*/
protected static int getScreenWidth(@NonNull Context context) {
DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics();
return (int) (displayMetrics.widthPixels / displayMetrics.density);
}
/**
* A convenience method for setting text appearance.
*
* @param textView a TextView which textAppearance to modify.
* @param resId a style resource for the text appearance.
*/
@SuppressWarnings("deprecation")
protected static void setTextAppearance(@NonNull TextView textView, @StyleRes int resId) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
textView.setTextAppearance(resId);
} else {
textView.setTextAppearance(textView.getContext(), resId);
}
}
/**
* Determine if the current UI Mode is Night Mode.
*
* @param context Context to get the configuration.
* @return true if the night mode is enabled, otherwise false.
*/
protected static boolean isNightMode(@NonNull Context context) {
int currentNightMode = context.getResources().getConfiguration().uiMode
& Configuration.UI_MODE_NIGHT_MASK;
return currentNightMode == Configuration.UI_MODE_NIGHT_YES;
}
}

View file

@ -1,30 +0,0 @@
package com.habitrpg.android.habitica.ui.views.bottombar;
import androidx.annotation.IdRes;
/*
* BottomBar library for Android
* Copyright (c) 2016 Iiro Krankka (http://github.com/roughike).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
public interface OnTabReselectListener {
/**
* The method being called when currently visible {@link BottomBarTab} is
* reselected. Use this method for scrolling to the top of your content,
* as recommended by the Material Design spec
*
* @param tabId the {@link BottomBarTab} that was reselected.
*/
void onTabReSelected(@IdRes int tabId);
}

View file

@ -1,32 +0,0 @@
package com.habitrpg.android.habitica.ui.views.bottombar;
import androidx.annotation.IdRes;
/*
* BottomBar library for Android
* Copyright (c) 2016 Iiro Krankka (http://github.com/roughike).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
public interface OnTabSelectListener {
/**
* The method being called when currently visible {@link BottomBarTab} changes.
*
* This listener is fired for the first time after the items have been set and
* also after a configuration change, such as when screen orientation changes
* from portrait to landscape.
*
* @param tabId the new visible {@link BottomBarTab}
*/
void onTabSelected(@IdRes int tabId);
}

View file

@ -1,55 +0,0 @@
package com.habitrpg.android.habitica.ui.views.bottombar;
/**
* Settings specific for a shy BottomBar.
*/
public class ShySettings {
private BottomBar bottomBar;
private Boolean pendingIsVisibleInShyMode;
ShySettings(BottomBar bottomBar) {
this.bottomBar = bottomBar;
}
void shyHeightCalculated() {
updatePendingShyVisibility();
}
/**
* Shows the BottomBar if it was hidden, with a translate animation.
*/
public void showBar() {
toggleIsVisibleInShyMode(true);
}
/**
* Hides the BottomBar in if it was visible, with a translate animation.
*/
public void hideBar() {
toggleIsVisibleInShyMode(false);
}
private void toggleIsVisibleInShyMode(boolean visible) {
if (!bottomBar.isShy()) {
return;
}
if (bottomBar.isShyHeightAlreadyCalculated()) {
BottomNavigationBehavior<BottomBar> behavior = BottomNavigationBehavior.from(bottomBar);
if (behavior != null) {
boolean isHidden = !visible;
behavior.setHidden(bottomBar, isHidden);
}
} else {
pendingIsVisibleInShyMode = true;
}
}
private void updatePendingShyVisibility() {
if (pendingIsVisibleInShyMode != null) {
toggleIsVisibleInShyMode(pendingIsVisibleInShyMode);
pendingIsVisibleInShyMode = null;
}
}
}

View file

@ -1,210 +0,0 @@
package com.habitrpg.android.habitica.ui.views.bottombar;
import android.content.Context;
import android.content.res.XmlResourceParser;
import android.graphics.Color;
import androidx.annotation.CheckResult;
import androidx.annotation.ColorInt;
import androidx.annotation.IntRange;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.StringDef;
import androidx.annotation.XmlRes;
import androidx.core.content.ContextCompat;
import org.xmlpull.v1.XmlPullParserException;
import java.io.IOException;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.List;
import static com.habitrpg.android.habitica.ui.views.bottombar.TabParser.TabAttribute.ACTIVE_COLOR;
import static com.habitrpg.android.habitica.ui.views.bottombar.TabParser.TabAttribute.BADGE_BACKGROUND_COLOR;
import static com.habitrpg.android.habitica.ui.views.bottombar.TabParser.TabAttribute.BADGE_HIDES_WHEN_ACTIVE;
import static com.habitrpg.android.habitica.ui.views.bottombar.TabParser.TabAttribute.BAR_COLOR_WHEN_SELECTED;
import static com.habitrpg.android.habitica.ui.views.bottombar.TabParser.TabAttribute.ICON;
import static com.habitrpg.android.habitica.ui.views.bottombar.TabParser.TabAttribute.ID;
import static com.habitrpg.android.habitica.ui.views.bottombar.TabParser.TabAttribute.INACTIVE_COLOR;
import static com.habitrpg.android.habitica.ui.views.bottombar.TabParser.TabAttribute.IS_TITLELESS;
import static com.habitrpg.android.habitica.ui.views.bottombar.TabParser.TabAttribute.TITLE;
/**
* Created by iiro on 21.7.2016.
*
* BottomBar library for Android
* Copyright (c) 2016 Iiro Krankka (http://github.com/roughike).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
class TabParser {
private static final String TAB_TAG = "tab";
private static final int AVG_NUMBER_OF_TABS = 5;
private static final int COLOR_NOT_SET = -1;
private static final int RESOURCE_NOT_FOUND = 0;
@NonNull
private final Context context;
@NonNull
private final BottomBarTab.Config defaultTabConfig;
@NonNull
private final XmlResourceParser parser;
@Nullable
private List<BottomBarTab> tabs = null;
TabParser(@NonNull Context context, @NonNull BottomBarTab.Config defaultTabConfig, @XmlRes int tabsXmlResId) {
this.context = context;
this.defaultTabConfig = defaultTabConfig;
this.parser = context.getResources().getXml(tabsXmlResId);
}
@CheckResult
@NonNull
public List<BottomBarTab> parseTabs() {
if (tabs == null) {
tabs = new ArrayList<>(AVG_NUMBER_OF_TABS);
try {
int eventType;
do {
eventType = parser.next();
if (eventType == XmlResourceParser.START_TAG && TAB_TAG.equals(parser.getName())) {
BottomBarTab bottomBarTab = parseNewTab(parser, tabs.size());
tabs.add(bottomBarTab);
}
} while (eventType != XmlResourceParser.END_DOCUMENT);
} catch (IOException | XmlPullParserException e) {
e.printStackTrace();
throw new TabParserException();
}
}
return tabs;
}
@NonNull
private BottomBarTab parseNewTab(@NonNull XmlResourceParser parser, @IntRange(from = 0) int containerPosition) {
BottomBarTab workingTab = tabWithDefaults();
workingTab.setIndexInContainer(containerPosition);
final int numberOfAttributes = parser.getAttributeCount();
for (int i = 0; i < numberOfAttributes; i++) {
@TabAttribute
String attrName = parser.getAttributeName(i);
switch (attrName) {
case ID:
workingTab.setId(parser.getIdAttributeResourceValue(i));
break;
case ICON:
workingTab.setIconResId(parser.getAttributeResourceValue(i, RESOURCE_NOT_FOUND));
break;
case TITLE:
workingTab.setTitle(getTitleValue(parser, i));
break;
case INACTIVE_COLOR:
int inactiveColor = getColorValue(parser, i);
if (inactiveColor == COLOR_NOT_SET) continue;
workingTab.setInActiveColor(inactiveColor);
break;
case ACTIVE_COLOR:
int activeColor = getColorValue(parser, i);
if (activeColor == COLOR_NOT_SET) continue;
workingTab.setActiveColor(activeColor);
break;
case BAR_COLOR_WHEN_SELECTED:
int barColorWhenSelected = getColorValue(parser, i);
if (barColorWhenSelected == COLOR_NOT_SET) continue;
workingTab.setBarColorWhenSelected(barColorWhenSelected);
break;
case BADGE_BACKGROUND_COLOR:
int badgeBackgroundColor = getColorValue(parser, i);
if (badgeBackgroundColor == COLOR_NOT_SET) continue;
workingTab.setBadgeBackgroundColor(badgeBackgroundColor);
break;
case BADGE_HIDES_WHEN_ACTIVE:
boolean badgeHidesWhenActive = parser.getAttributeBooleanValue(i, true);
workingTab.setBadgeHidesWhenActive(badgeHidesWhenActive);
break;
case IS_TITLELESS:
boolean isTitleless = parser.getAttributeBooleanValue(i, false);
workingTab.setIsTitleless(isTitleless);
break;
}
}
return workingTab;
}
@NonNull
private BottomBarTab tabWithDefaults() {
BottomBarTab tab = new BottomBarTab(context);
tab.setConfig(defaultTabConfig);
return tab;
}
@NonNull
private String getTitleValue(@NonNull XmlResourceParser parser, @IntRange(from = 0) int attrIndex) {
int titleResource = parser.getAttributeResourceValue(attrIndex, 0);
return titleResource == RESOURCE_NOT_FOUND
? parser.getAttributeValue(attrIndex) : context.getString(titleResource);
}
@ColorInt
private int getColorValue(@NonNull XmlResourceParser parser, @IntRange(from = 0) int attrIndex) {
int colorResource = parser.getAttributeResourceValue(attrIndex, 0);
if (colorResource == RESOURCE_NOT_FOUND) {
try {
String colorValue = parser.getAttributeValue(attrIndex);
return Color.parseColor(colorValue);
} catch (Exception ignored) {
return COLOR_NOT_SET;
}
}
return ContextCompat.getColor(context, colorResource);
}
@Retention(RetentionPolicy.SOURCE)
@StringDef({
ID,
ICON,
TITLE,
INACTIVE_COLOR,
ACTIVE_COLOR,
BAR_COLOR_WHEN_SELECTED,
BADGE_BACKGROUND_COLOR,
BADGE_HIDES_WHEN_ACTIVE,
IS_TITLELESS
})
@interface TabAttribute {
String ID = "id";
String ICON = "icon";
String TITLE = "title";
String INACTIVE_COLOR = "inActiveColor";
String ACTIVE_COLOR = "activeColor";
String BAR_COLOR_WHEN_SELECTED = "barColorWhenSelected";
String BADGE_BACKGROUND_COLOR = "badgeBackgroundColor";
String BADGE_HIDES_WHEN_ACTIVE = "badgeHidesWhenActive";
String IS_TITLELESS = "iconOnly";
}
@SuppressWarnings("WeakerAccess")
public static class TabParserException extends RuntimeException {
// This class is just to be able to have a type of Runtime Exception that will make it clear where the error originated.
}
}

View file

@ -1,33 +0,0 @@
package com.habitrpg.android.habitica.ui.views.bottombar;
import androidx.annotation.IdRes;
/*
* BottomBar library for Android
* Copyright (c) 2016 Iiro Krankka (http://github.com/roughike).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
public interface TabSelectionInterceptor {
/**
* The method being called when currently visible {@link BottomBarTab} is about to change.
* <p>
* This listener is fired when the current {@link BottomBar} is about to change. This gives
* an opportunity to interrupt the {@link BottomBarTab} change.
*
* @param oldTabId the currently visible {@link BottomBarTab}
* @param newTabId the {@link BottomBarTab} that will be switched to
* @return true if you want to override/stop the tab change, false to continue as normal
*/
boolean shouldInterceptTabSelection(@IdRes int oldTabId, @IdRes int newTabId);
}

View file

@ -1,150 +0,0 @@
package com.habitrpg.android.habitica.ui.views.bottombar;
import android.content.Context;
import android.os.Parcelable;
import androidx.annotation.IntDef;
import androidx.annotation.NonNull;
import androidx.coordinatorlayout.widget.CoordinatorLayout;
import androidx.core.view.WindowInsetsCompat;
import android.util.AttributeSet;
import android.view.View;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/**
* Created by Nikola D. on 11/22/2015.
*
* Credit goes to Nikola Despotoski:
* https://github.com/NikolaDespotoski
*/
abstract class VerticalScrollingBehavior<V extends View> extends CoordinatorLayout.Behavior<V> {
private int totalDyUnconsumed = 0;
private int totalDy = 0;
@ScrollDirection
private int overScrollDirection = ScrollDirection.SCROLL_NONE;
@ScrollDirection
private int scrollDirection = ScrollDirection.SCROLL_NONE;
VerticalScrollingBehavior(Context context, AttributeSet attrs) {
super(context, attrs);
}
VerticalScrollingBehavior() {
super();
}
@Retention(RetentionPolicy.SOURCE)
@IntDef({ScrollDirection.SCROLL_DIRECTION_UP, ScrollDirection.SCROLL_DIRECTION_DOWN, ScrollDirection.SCROLL_NONE})
@interface ScrollDirection {
int SCROLL_DIRECTION_UP = 1;
int SCROLL_DIRECTION_DOWN = -1;
int SCROLL_NONE = 0;
}
/*
@return Overscroll direction: SCROLL_DIRECTION_UP, CROLL_DIRECTION_DOWN, SCROLL_NONE
*/
@ScrollDirection
int getOverScrollDirection() {
return overScrollDirection;
}
/**
* @return Scroll direction: SCROLL_DIRECTION_UP, SCROLL_DIRECTION_DOWN, SCROLL_NONE
*/
@ScrollDirection
int getScrollDirection() {
return scrollDirection;
}
/**
* @param coordinatorLayout
* @param child
* @param direction Direction of the overscroll: SCROLL_DIRECTION_UP, SCROLL_DIRECTION_DOWN
* @param currentOverScroll Unconsumed value, negative or positive based on the direction;
* @param totalOverScroll Cumulative value for current direction
*/
abstract void onNestedVerticalOverScroll(CoordinatorLayout coordinatorLayout, V child, @ScrollDirection int direction, int currentOverScroll, int totalOverScroll);
/**
* @param scrollDirection Direction of the overscroll: SCROLL_DIRECTION_UP, SCROLL_DIRECTION_DOWN
*/
abstract void onDirectionNestedPreScroll(CoordinatorLayout coordinatorLayout, V child, View target, int dx, int dy, int[] consumed, @ScrollDirection int scrollDirection);
@Override
public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout, V child, View directTargetChild, View target, int nestedScrollAxes) {
return (nestedScrollAxes & View.SCROLL_AXIS_VERTICAL) != 0;
}
@Override
public void onNestedScrollAccepted(CoordinatorLayout coordinatorLayout, V child, View directTargetChild, View target, int nestedScrollAxes) {
super.onNestedScrollAccepted(coordinatorLayout, child, directTargetChild, target, nestedScrollAxes);
}
@Override
public void onStopNestedScroll(CoordinatorLayout coordinatorLayout, V child, View target) {
super.onStopNestedScroll(coordinatorLayout, child, target);
}
@Override
public void onNestedScroll(CoordinatorLayout coordinatorLayout, V child, View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) {
super.onNestedScroll(coordinatorLayout, child, target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed);
if (dyUnconsumed > 0 && totalDyUnconsumed < 0) {
totalDyUnconsumed = 0;
overScrollDirection = ScrollDirection.SCROLL_DIRECTION_UP;
} else if (dyUnconsumed < 0 && totalDyUnconsumed > 0) {
totalDyUnconsumed = 0;
overScrollDirection = ScrollDirection.SCROLL_DIRECTION_DOWN;
}
totalDyUnconsumed += dyUnconsumed;
onNestedVerticalOverScroll(coordinatorLayout, child, overScrollDirection, dyConsumed, totalDyUnconsumed);
}
@Override
public void onNestedPreScroll(CoordinatorLayout coordinatorLayout, V child, View target, int dx, int dy, int[] consumed) {
super.onNestedPreScroll(coordinatorLayout, child, target, dx, dy, consumed);
if (dy > 0 && totalDy < 0) {
totalDy = 0;
scrollDirection = ScrollDirection.SCROLL_DIRECTION_UP;
} else if (dy < 0 && totalDy > 0) {
totalDy = 0;
scrollDirection = ScrollDirection.SCROLL_DIRECTION_DOWN;
}
totalDy += dy;
onDirectionNestedPreScroll(coordinatorLayout, child, target, dx, dy, consumed, scrollDirection);
}
@Override
public boolean onNestedFling(CoordinatorLayout coordinatorLayout, V child, View target, float velocityX, float velocityY, boolean consumed) {
super.onNestedFling(coordinatorLayout, child, target, velocityX, velocityY, consumed);
scrollDirection = velocityY > 0 ? ScrollDirection.SCROLL_DIRECTION_UP : ScrollDirection.SCROLL_DIRECTION_DOWN;
return onNestedDirectionFling(coordinatorLayout, child, target, velocityX, velocityY, scrollDirection);
}
abstract boolean onNestedDirectionFling(CoordinatorLayout coordinatorLayout, V child, View target, float velocityX, float velocityY, @ScrollDirection int scrollDirection);
@Override
public boolean onNestedPreFling(CoordinatorLayout coordinatorLayout, V child, View target, float velocityX, float velocityY) {
return super.onNestedPreFling(coordinatorLayout, child, target, velocityX, velocityY);
}
@NonNull
@Override
public WindowInsetsCompat onApplyWindowInsets(CoordinatorLayout coordinatorLayout, V child, WindowInsetsCompat insets) {
return super.onApplyWindowInsets(coordinatorLayout, child, insets);
}
@Override
public Parcelable onSaveInstanceState(CoordinatorLayout parent, V child) {
return super.onSaveInstanceState(parent, child);
}
}

View file

@ -0,0 +1,52 @@
package com.habitrpg.android.habitica.ui.views.navigation
import android.content.Context
import android.graphics.PorterDuff
import android.util.AttributeSet
import android.view.View
import android.widget.ImageView
import android.widget.LinearLayout
import android.widget.TextView
import androidx.core.content.ContextCompat
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.extensions.inflate
import com.habitrpg.android.habitica.ui.helpers.bindView
class BottomNavigationItem @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : LinearLayout(context, attrs, defStyleAttr) {
private val iconView: ImageView by bindView(R.id.icon_view)
private val selectedTitleView: TextView by bindView(R.id.selected_title_view)
private val titleView: TextView by bindView(R.id.title_view)
var isActive = false
set(value) {
field = value
if (isActive) {
selectedTitleView.visibility = View.VISIBLE
titleView.visibility = View.GONE
iconView.drawable.setColorFilter(ContextCompat.getColor(context, R.color.white), PorterDuff.Mode.MULTIPLY )
} else {
selectedTitleView.visibility = View.GONE
titleView.visibility = View.VISIBLE
iconView.drawable.setColorFilter(ContextCompat.getColor(context, R.color.brand_500), PorterDuff.Mode.MULTIPLY )
}
}
init {
inflate(R.layout.bottom_navigation_item, true)
val attributes = context.theme?.obtainStyledAttributes(
attrs,
R.styleable.BottomNavigationItem,
0, 0)
if (attributes != null) {
iconView.setImageDrawable(attributes.getDrawable(R.styleable.BottomNavigationItem_iconDrawable))
titleView.text = attributes.getString(R.styleable.BottomNavigationItem_title)
selectedTitleView.text = attributes.getString(R.styleable.BottomNavigationItem_title)
}
orientation = VERTICAL
}
}

View file

@ -0,0 +1,66 @@
package com.habitrpg.android.habitica.ui.views.navigation
import android.content.Context
import android.util.AttributeSet
import android.widget.ImageButton
import android.widget.RelativeLayout
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.extensions.inflate
import com.habitrpg.android.habitica.models.tasks.Task
import com.habitrpg.android.habitica.ui.helpers.bindView
class HabiticaBottomNavigationView @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : RelativeLayout(context, attrs, defStyleAttr) {
var selectedPosition: Int
get() {
return when (activeTaskType) {
Task.TYPE_DAILY -> 1
Task.TYPE_REWARD -> 2
Task.TYPE_TODO -> 3
else -> 0
}
}
set(value) {
activeTaskType = when (value) {
1 -> Task.TYPE_DAILY
2 -> Task.TYPE_TODO
3 -> Task.TYPE_REWARD
else -> Task.TYPE_HABIT
}
}
var onTabSelectedListener: ((String) -> Unit)? = null
var onAddListener: ((String) -> Unit)? = null
var activeTaskType: String = Task.TYPE_HABIT
set(value) {
field = value
updateItemSelection()
onTabSelectedListener?.invoke(value)
}
private val habitsTab: BottomNavigationItem by bindView(R.id.tab_habits)
private val dailiesTab: BottomNavigationItem by bindView(R.id.tab_dailies)
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)
init {
inflate(R.layout.main_navigation_view, true)
habitsTab.setOnClickListener { activeTaskType = Task.TYPE_HABIT }
dailiesTab.setOnClickListener { activeTaskType = Task.TYPE_DAILY }
todosTab.setOnClickListener { activeTaskType = Task.TYPE_TODO }
rewardsTab.setOnClickListener { activeTaskType = Task.TYPE_REWARD }
addButton.setOnClickListener {
onAddListener?.invoke(activeTaskType)
}
updateItemSelection()
}
private fun updateItemSelection() {
habitsTab.isActive = activeTaskType == Task.TYPE_HABIT
dailiesTab.isActive = activeTaskType == Task.TYPE_DAILY
todosTab.isActive = activeTaskType == Task.TYPE_TODO
rewardsTab.isActive = activeTaskType == Task.TYPE_REWARD
}
}