Implement new bottom navigation design
|
|
@ -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')) {
|
||||||
|
|
|
||||||
BIN
Habitica/res/drawable-hdpi/bottom_navigation_inset.png
Normal file
|
After Width: | Height: | Size: 2 KiB |
BIN
Habitica/res/drawable-hdpi/fab_background.png
Normal file
|
After Width: | Height: | Size: 4.3 KiB |
BIN
Habitica/res/drawable-hdpi/fab_plus.png
Normal file
|
After Width: | Height: | Size: 316 B |
BIN
Habitica/res/drawable-mdpi/bottom_navigation_inset.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
Habitica/res/drawable-mdpi/fab_background.png
Normal file
|
After Width: | Height: | Size: 3.1 KiB |
BIN
Habitica/res/drawable-mdpi/fab_plus.png
Normal file
|
After Width: | Height: | Size: 202 B |
BIN
Habitica/res/drawable-xhdpi/bottom_navigation_inset.png
Normal file
|
After Width: | Height: | Size: 2.9 KiB |
BIN
Habitica/res/drawable-xhdpi/fab_background.png
Normal file
|
After Width: | Height: | Size: 6 KiB |
BIN
Habitica/res/drawable-xhdpi/fab_plus.png
Normal file
|
After Width: | Height: | Size: 344 B |
BIN
Habitica/res/drawable-xxhdpi/bottom_navigation_inset.png
Normal file
|
After Width: | Height: | Size: 4.8 KiB |
BIN
Habitica/res/drawable-xxhdpi/fab_background.png
Normal file
|
After Width: | Height: | Size: 9.5 KiB |
BIN
Habitica/res/drawable-xxhdpi/fab_plus.png
Normal file
|
After Width: | Height: | Size: 516 B |
BIN
Habitica/res/drawable-xxxhdpi/bottom_navigation_inset.png
Normal file
|
After Width: | Height: | Size: 6.9 KiB |
BIN
Habitica/res/drawable-xxxhdpi/fab_background.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
Habitica/res/drawable-xxxhdpi/fab_plus.png
Normal file
|
After Width: | Height: | Size: 688 B |
6
Habitica/res/drawable/nav_icon_colors.xml
Normal 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>
|
||||||
BIN
Habitica/res/fab_background.png
Normal file
|
After Width: | Height: | Size: 2 KiB |
|
|
@ -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"
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
|
|
|
||||||
38
Habitica/res/layout/bottom_navigation_item.xml
Normal 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>
|
||||||
79
Habitica/res/layout/main_navigation_view.xml
Normal 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>
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
18
Habitica/res/menu/main_menu_tasks.xml
Normal 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>
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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(),
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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);
|
|
||||||
}
|
|
||||||
|
|
@ -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);
|
|
||||||
}
|
|
||||||
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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);
|
|
||||||
}
|
|
||||||
|
|
@ -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);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||