mirror of
https://github.com/sudoxnym/habitica-android.git
synced 2026-04-14 19:56:32 +00:00
Fix layout issues on devices with a notch
This commit is contained in:
parent
a40c5ae9dc
commit
c7f0346ff8
55 changed files with 3413 additions and 234 deletions
|
|
@ -61,7 +61,7 @@ dependencies {
|
|||
kapt 'com.google.dagger:dagger-compiler:2.17'
|
||||
compileOnly 'javax.annotation:javax.annotation-api:1.3.1'
|
||||
//App Compatibility and Material Design
|
||||
implementation 'androidx.appcompat:appcompat:1.0.1'
|
||||
implementation 'androidx.appcompat:appcompat:1.0.2'
|
||||
implementation 'com.google.android.material:material:1.1.0-alpha01'
|
||||
implementation 'androidx.recyclerview:recyclerview:1.0.0'
|
||||
implementation 'androidx.legacy:legacy-preference-v14:1.0.0'
|
||||
|
|
@ -118,7 +118,6 @@ dependencies {
|
|||
implementation 'com.google.firebase:firebase-core:11.4.2'
|
||||
implementation 'com.google.firebase:firebase-messaging:11.4.2'
|
||||
implementation 'com.google.android.gms:play-services-auth:11.4.2'
|
||||
implementation 'com.roughike:bottom-bar:2.3.1'
|
||||
implementation 'io.realm:android-adapters:3.0.0'
|
||||
implementation(project(':seeds-sdk')) {
|
||||
exclude group: 'com.google.android.gms'
|
||||
|
|
|
|||
2
Habitica/proguard-rules.pro
vendored
2
Habitica/proguard-rules.pro
vendored
|
|
@ -174,7 +174,7 @@
|
|||
-dontwarn rx.**
|
||||
-dontwarn com.android.volley.toolbox.**
|
||||
-dontwarn com.facebook.infer.**
|
||||
-dontwarn com.roughike.bottombar.**
|
||||
-dontwarn com.habitrpg.android.habitica.ui.views.bottombar.**
|
||||
-dontwarn com.viewpagerindicator.**
|
||||
#-ignorewarnings
|
||||
|
||||
|
|
|
|||
24
Habitica/res/drawable-v21/bb_bottom_bar_item_container.xml
Normal file
24
Habitica/res/drawable-v21/bb_bottom_bar_item_container.xml
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<merge xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/bb_bottom_bar_outer_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<View
|
||||
android:id="@+id/bb_bottom_bar_background_overlay"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:visibility="invisible" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/bb_bottom_bar_item_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:gravity="center"
|
||||
android:orientation="horizontal" />
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
</merge>
|
||||
8
Habitica/res/drawable/bb_bottom_bar_top_shadow.xml
Normal file
8
Habitica/res/drawable/bb_bottom_bar_top_shadow.xml
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<gradient
|
||||
android:angle="90"
|
||||
android:startColor="#1F000000"
|
||||
android:endColor="#00000000"
|
||||
android:type="linear" />
|
||||
</shape>
|
||||
|
|
@ -120,7 +120,7 @@
|
|||
android:layout_gravity="bottom|right"
|
||||
android:layout_marginBottom="-5dp"
|
||||
/>
|
||||
<com.roughike.bottombar.BottomBar
|
||||
<com.habitrpg.android.habitica.ui.views.bottombar.BottomBar
|
||||
android:id="@+id/bottom_navigation"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="60dp"
|
||||
|
|
|
|||
31
Habitica/res/layout/bb_bottom_bar_item_container.xml
Normal file
31
Habitica/res/layout/bb_bottom_bar_item_container.xml
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<merge xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<View
|
||||
android:id="@+id/bb_bottom_bar_shadow"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/bb_fake_shadow_height"
|
||||
android:background="@drawable/bb_bottom_bar_top_shadow"
|
||||
android:visibility="gone"/>
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/bb_bottom_bar_outer_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<View
|
||||
android:id="@+id/bb_bottom_bar_background_overlay"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:visibility="invisible"/>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/bb_bottom_bar_item_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:gravity="center"
|
||||
android:orientation="horizontal"/>
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
</merge>
|
||||
34
Habitica/res/layout/bb_bottom_bar_item_container_tablet.xml
Normal file
34
Habitica/res/layout/bb_bottom_bar_item_container_tablet.xml
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<merge xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/bb_bottom_bar_outer_container"
|
||||
android:layout_width="60dp"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<View
|
||||
android:id="@+id/bb_bottom_bar_background_overlay"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:visibility="invisible" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/bb_bottom_bar_item_container"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:layout_marginEnd="1dp"
|
||||
android:layout_marginRight="1dp"
|
||||
android:orientation="vertical"
|
||||
android:paddingBottom="8dp"
|
||||
android:paddingTop="8dp" />
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
<View
|
||||
android:id="@+id/bb_bottom_bar_shadow"
|
||||
android:layout_width="1dp"
|
||||
android:layout_height="match_parent"
|
||||
android:background="#EAEAEA" />
|
||||
|
||||
</merge>
|
||||
19
Habitica/res/layout/bb_bottom_bar_item_fixed.xml
Normal file
19
Habitica/res/layout/bb_bottom_bar_item_fixed.xml
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<merge xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<androidx.appcompat.widget.AppCompatImageView
|
||||
android:id="@+id/bb_bottom_bar_icon"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="top|center_horizontal"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/bb_bottom_bar_title"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="bottom|center_horizontal"
|
||||
android:textAppearance="@style/BB_BottomBarItem_Fixed.TitleAppearance"
|
||||
android:visibility="gone"
|
||||
style="@style/BB_BottomBarItem_TitleStyle"/>
|
||||
|
||||
</merge>
|
||||
14
Habitica/res/layout/bb_bottom_bar_item_fixed_tablet.xml
Normal file
14
Habitica/res/layout/bb_bottom_bar_item_fixed_tablet.xml
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingLeft="20dp"
|
||||
android:paddingRight="20dp"
|
||||
android:paddingTop="16dp"
|
||||
android:paddingBottom="16dp">
|
||||
|
||||
<androidx.appcompat.widget.AppCompatImageView
|
||||
android:id="@+id/bb_bottom_bar_icon"
|
||||
style="@style/BB_BottomBarItem_Tablet" />
|
||||
|
||||
</FrameLayout>
|
||||
26
Habitica/res/layout/bb_bottom_bar_item_shifting.xml
Normal file
26
Habitica/res/layout/bb_bottom_bar_item_shifting.xml
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<merge xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<androidx.appcompat.widget.AppCompatImageView
|
||||
android:id="@+id/bb_bottom_bar_icon"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:alpha="0.6"/>
|
||||
|
||||
<!-- We use this empty space to push the text to the bottom -->
|
||||
<Space
|
||||
android:id="@+id/spacer"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1"
|
||||
android:visibility="gone"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/bb_bottom_bar_title"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAppearance="@style/BB_BottomBarItem_Shifting.TitleAppearance"
|
||||
android:visibility="gone"
|
||||
style="@style/BB_BottomBarItem_TitleStyle"/>
|
||||
|
||||
</merge>
|
||||
10
Habitica/res/layout/bb_bottom_bar_item_titleless.xml
Normal file
10
Habitica/res/layout/bb_bottom_bar_item_titleless.xml
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<merge xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<androidx.appcompat.widget.AppCompatImageView
|
||||
android:id="@+id/bb_bottom_bar_icon"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center" />
|
||||
|
||||
</merge>
|
||||
|
|
@ -1,118 +1,117 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@color/white"
|
||||
android:orientation="vertical"
|
||||
tools:context="com.habitrpg.android.habitica.ui.fragments.NavigationDrawerFragment">
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@color/brand_200"
|
||||
android:orientation="vertical"
|
||||
tools:context="com.habitrpg.android.habitica.ui.fragments.NavigationDrawerFragment">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/menuHeaderView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="64dp"
|
||||
android:paddingLeft="16dp"
|
||||
android:paddingRight="16dp"
|
||||
android:layout_marginTop="16dp"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<com.habitrpg.android.habitica.ui.views.RoundedCornerLayout
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="40dp"
|
||||
android:layout_marginRight="@dimen/spacing_large">
|
||||
<com.habitrpg.android.habitica.ui.AvatarView
|
||||
android:id="@+id/avatarView"
|
||||
android:layout_width="70dp"
|
||||
android:layout_height="70dp"
|
||||
android:layout_gravity="center"/>
|
||||
</com.habitrpg.android.habitica.ui.views.RoundedCornerLayout>
|
||||
<LinearLayout
|
||||
android:id="@+id/menuHeaderView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="88dp"
|
||||
android:background="@color/brand_200"
|
||||
android:paddingTop="24dp"
|
||||
android:paddingLeft="16dp"
|
||||
android:paddingRight="16dp"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<com.habitrpg.android.habitica.ui.views.RoundedCornerLayout
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="40dp"
|
||||
android:layout_marginRight="@dimen/spacing_large">
|
||||
<com.habitrpg.android.habitica.ui.AvatarView
|
||||
android:id="@+id/avatarView"
|
||||
android:layout_width="70dp"
|
||||
android:layout_height="70dp"
|
||||
android:layout_gravity="center"/>
|
||||
</com.habitrpg.android.habitica.ui.views.RoundedCornerLayout>
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_weight="1"
|
||||
android:layout_width="0dp"
|
||||
android:layout_weight="1"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:orientation="vertical">
|
||||
<TextView
|
||||
android:id="@+id/toolbarTitle"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:orientation="vertical">
|
||||
<TextView
|
||||
android:id="@+id/toolbarTitle"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="1"
|
||||
android:singleLine="true"
|
||||
tools:text="Habitica"
|
||||
style="@style/Body1"
|
||||
android:textColor="@color/white"/>
|
||||
<TextView
|
||||
android:id="@+id/usernameTextView"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="1"
|
||||
android:singleLine="true"
|
||||
tools:text="\@username"
|
||||
android:textSize="12sp"
|
||||
android:visibility="gone"
|
||||
android:textColor="@color/white_80_alpha"/>
|
||||
</LinearLayout>
|
||||
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="45dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginLeft="16dp">
|
||||
<ImageButton
|
||||
android:id="@+id/messagesButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@color/transparent"
|
||||
android:src="@drawable/menu_messages"
|
||||
android:layout_centerVertical="true"/>
|
||||
<TextView
|
||||
android:id="@+id/messagesBadge"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="20dp"
|
||||
android:paddingTop="0dp"
|
||||
android:layout_alignTop="@id/messagesButton"
|
||||
android:layout_alignLeft="@id/messagesButton"
|
||||
tools:text="1"
|
||||
android:textColor="#FFF"
|
||||
android:textSize="12sp"
|
||||
android:background="@drawable/badge_circle"
|
||||
android:layout_marginTop="-12dp"
|
||||
android:layout_marginLeft="13dp"
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible"/>
|
||||
</RelativeLayout>
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="35dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginLeft="8dp">
|
||||
<ImageButton
|
||||
android:id="@+id/settingsButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@color/transparent"
|
||||
android:src="@drawable/menu_settings"
|
||||
android:layout_centerVertical="true"/>
|
||||
<TextView
|
||||
android:id="@+id/settingsBadge"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="20dp"
|
||||
android:paddingTop="0dp"
|
||||
android:layout_alignTop="@id/settingsButton"
|
||||
android:layout_alignLeft="@id/settingsButton"
|
||||
tools:text="1"
|
||||
android:textColor="#FFF"
|
||||
android:textSize="12sp"
|
||||
android:background="@drawable/badge_circle"
|
||||
android:layout_marginTop="-12dp"
|
||||
android:layout_marginLeft="13dp"
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible"/>
|
||||
</RelativeLayout>
|
||||
|
||||
android:ellipsize="end"
|
||||
android:maxLines="1"
|
||||
android:singleLine="true"
|
||||
tools:text="Habitica"
|
||||
style="@style/Body1"
|
||||
android:textColor="@color/white"/>
|
||||
<TextView
|
||||
android:id="@+id/usernameTextView"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="1"
|
||||
android:singleLine="true"
|
||||
tools:text="\@username"
|
||||
android:textSize="12sp"
|
||||
android:visibility="gone"
|
||||
android:textColor="@color/white_80_alpha"/>
|
||||
</LinearLayout>
|
||||
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="45dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginLeft="16dp">
|
||||
<ImageButton
|
||||
android:id="@+id/messagesButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@color/transparent"
|
||||
android:src="@drawable/menu_messages"
|
||||
android:layout_centerVertical="true"/>
|
||||
<TextView
|
||||
android:id="@+id/messagesBadge"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="20dp"
|
||||
android:paddingTop="0dp"
|
||||
android:layout_alignTop="@id/messagesButton"
|
||||
android:layout_alignLeft="@id/messagesButton"
|
||||
tools:text="1"
|
||||
android:textColor="#FFF"
|
||||
android:textSize="12sp"
|
||||
android:background="@drawable/badge_circle"
|
||||
android:layout_marginTop="-12dp"
|
||||
android:layout_marginLeft="13dp"
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible"/>
|
||||
</RelativeLayout>
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="35dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginLeft="8dp">
|
||||
<ImageButton
|
||||
android:id="@+id/settingsButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@color/transparent"
|
||||
android:src="@drawable/menu_settings"
|
||||
android:layout_centerVertical="true"/>
|
||||
<TextView
|
||||
android:id="@+id/settingsBadge"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="20dp"
|
||||
android:paddingTop="0dp"
|
||||
android:layout_alignTop="@id/settingsButton"
|
||||
android:layout_alignLeft="@id/settingsButton"
|
||||
tools:text="1"
|
||||
android:textColor="#FFF"
|
||||
android:textSize="12sp"
|
||||
android:background="@drawable/badge_circle"
|
||||
android:layout_marginTop="-12dp"
|
||||
android:layout_marginLeft="13dp"
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible"/>
|
||||
</RelativeLayout>
|
||||
|
||||
</LinearLayout>
|
||||
<com.habitrpg.android.habitica.ui.views.social.QuestMenuView
|
||||
android:id="@+id/questMenuView"
|
||||
android:layout_width="match_parent"
|
||||
|
|
@ -121,5 +120,6 @@
|
|||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/recyclerView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" />
|
||||
android:layout_height="match_parent"
|
||||
android:background="@color/white"/>
|
||||
</LinearLayout>
|
||||
|
|
|
|||
|
|
@ -6,10 +6,9 @@
|
|||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:scrollbarSize="3dp"
|
||||
android:scrollbarThumbVertical="@color/scrollbarThumb"
|
||||
android:paddingBottom="?attr/actionBarSize"
|
||||
android:scrollbars="vertical">
|
||||
|
||||
<LinearLayout
|
||||
<com.habitrpg.android.habitica.ui.views.PaddedLinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
|
@ -147,5 +146,5 @@
|
|||
app:equipmentTitle="@string/avatar_background" />
|
||||
</LinearLayout>
|
||||
</androidx.cardview.widget.CardView>
|
||||
</LinearLayout>
|
||||
</com.habitrpg.android.habitica.ui.views.PaddedLinearLayout>
|
||||
</androidx.core.widget.NestedScrollView>
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
app:maxHeightMultiplier="0.7">
|
||||
<LinearLayout
|
||||
<com.habitrpg.android.habitica.ui.views.PaddedLinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
|
|
@ -207,5 +207,5 @@
|
|||
android:text="@string/leave"
|
||||
style="@style/HabiticaButton.Red"/>
|
||||
</FrameLayout>
|
||||
</LinearLayout>
|
||||
</com.habitrpg.android.habitica.ui.views.PaddedLinearLayout>
|
||||
</androidx.core.widget.NestedScrollView>
|
||||
|
|
|
|||
|
|
@ -6,9 +6,6 @@
|
|||
android:layout_height="match_parent"
|
||||
android:background="#fff"
|
||||
app:layout_behavior="@string/appbar_scrolling_view_behavior">
|
||||
<FrameLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
<com.habitrpg.android.habitica.ui.helpers.RecyclerViewEmptySupport
|
||||
android:id="@+id/recyclerView"
|
||||
android:layout_width="match_parent"
|
||||
|
|
@ -57,5 +54,4 @@
|
|||
android:textColor="#66000000" />
|
||||
|
||||
</LinearLayout>
|
||||
</FrameLayout>
|
||||
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
|
||||
|
|
|
|||
|
|
@ -9,11 +9,10 @@
|
|||
android:scrollbars="vertical"
|
||||
android:paddingTop="@dimen/row_padding">
|
||||
|
||||
<LinearLayout
|
||||
<com.habitrpg.android.habitica.ui.views.PaddedLinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:paddingBottom="?attr/actionBarSize">
|
||||
android:orientation="vertical">
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
|
|
@ -145,5 +144,5 @@
|
|||
android:layout_width="match_parent"/>
|
||||
</LinearLayout>
|
||||
</androidx.cardview.widget.CardView>
|
||||
</LinearLayout>
|
||||
</com.habitrpg.android.habitica.ui.views.PaddedLinearLayout>
|
||||
</androidx.core.widget.NestedScrollView>
|
||||
|
|
@ -7,7 +7,7 @@
|
|||
android:scrollbarThumbVertical="@color/scrollbarThumb"
|
||||
android:scrollbars="vertical">
|
||||
|
||||
<LinearLayout
|
||||
<com.habitrpg.android.habitica.ui.views.PaddedLinearLayout
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
|
|
@ -32,5 +32,5 @@
|
|||
android:paddingRight="@dimen/card_padding"
|
||||
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||
/>
|
||||
</LinearLayout>
|
||||
</com.habitrpg.android.habitica.ui.views.PaddedLinearLayout>
|
||||
</androidx.core.widget.NestedScrollView>
|
||||
|
|
@ -10,7 +10,7 @@
|
|||
android:scrollbarSize="3dp"
|
||||
android:scrollbarThumbVertical="@color/scrollbarThumb"
|
||||
android:scrollbars="vertical">
|
||||
<LinearLayout
|
||||
<com.habitrpg.android.habitica.ui.views.PaddedLinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
|
|
@ -207,6 +207,6 @@
|
|||
</LinearLayout>
|
||||
|
||||
</androidx.cardview.widget.CardView>
|
||||
</LinearLayout>
|
||||
</com.habitrpg.android.habitica.ui.views.PaddedLinearLayout>
|
||||
</androidx.core.widget.NestedScrollView>
|
||||
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
|
||||
|
|
@ -9,7 +9,7 @@
|
|||
<androidx.core.widget.NestedScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
<LinearLayout
|
||||
<com.habitrpg.android.habitica.ui.views.PaddedLinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
|
|
@ -41,6 +41,6 @@
|
|||
android:layout_height="wrap_content"
|
||||
android:layout_margin="@dimen/card_horizontal_padding"
|
||||
android:text="@string/public_guilds" />
|
||||
</LinearLayout>
|
||||
</com.habitrpg.android.habitica.ui.views.PaddedLinearLayout>
|
||||
</androidx.core.widget.NestedScrollView>
|
||||
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
|
||||
|
|
@ -10,13 +10,13 @@
|
|||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:paddingBottom="?attr/actionBarSize">
|
||||
<LinearLayout
|
||||
<com.habitrpg.android.habitica.ui.views.PaddedLinearLayout
|
||||
android:id="@+id/inbox_messages"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:divider="?android:listDivider"
|
||||
android:showDividers="middle">
|
||||
</LinearLayout>
|
||||
</com.habitrpg.android.habitica.ui.views.PaddedLinearLayout>
|
||||
</androidx.core.widget.NestedScrollView>
|
||||
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@
|
|||
<androidx.core.widget.NestedScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
<LinearLayout
|
||||
<com.habitrpg.android.habitica.ui.views.PaddedLinearLayout
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
|
|
@ -217,6 +217,6 @@
|
|||
android:layout_margin="@dimen/spacing_large"
|
||||
style="@style/HabiticaButton.Red"
|
||||
android:text="@string/leave_party"/>
|
||||
</LinearLayout>
|
||||
</com.habitrpg.android.habitica.ui.views.PaddedLinearLayout>
|
||||
</androidx.core.widget.NestedScrollView>
|
||||
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
|
||||
|
|
@ -4,7 +4,7 @@
|
|||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
<LinearLayout
|
||||
<com.habitrpg.android.habitica.ui.views.PaddedLinearLayout
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
|
|
@ -156,5 +156,5 @@
|
|||
android:text="@string/quest.abort"
|
||||
style="@style/HabiticaButton.Red" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
</com.habitrpg.android.habitica.ui.views.PaddedLinearLayout>
|
||||
</androidx.core.widget.NestedScrollView>
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
<com.habitrpg.android.habitica.ui.views.PaddedRecylerView
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/recyclerView"
|
||||
android:layout_width="match_parent"
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@
|
|||
android:scrollbarThumbVertical="@color/scrollbarThumb"
|
||||
android:scrollbars="vertical"
|
||||
android:background="@color/white">
|
||||
<LinearLayout
|
||||
<com.habitrpg.android.habitica.ui.views.PaddedLinearLayout
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
|
|
@ -232,5 +232,5 @@
|
|||
android:textColor="@color/gray_100"
|
||||
android:layout_marginBottom="28dp"/>
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
</com.habitrpg.android.habitica.ui.views.PaddedLinearLayout>
|
||||
</androidx.core.widget.NestedScrollView>
|
||||
|
|
@ -4,7 +4,7 @@
|
|||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
<LinearLayout
|
||||
<com.habitrpg.android.habitica.ui.views.PaddedLinearLayout
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
|
|
@ -103,5 +103,5 @@
|
|||
android:layout_height="wrap_content"
|
||||
android:text="@string/tiers_descriptions"/>
|
||||
</com.habitrpg.android.habitica.ui.views.CollapsibleSectionView>
|
||||
</LinearLayout>
|
||||
</com.habitrpg.android.habitica.ui.views.PaddedLinearLayout>
|
||||
</androidx.core.widget.NestedScrollView>
|
||||
|
|
@ -2,7 +2,7 @@
|
|||
<merge xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
<RelativeLayout
|
||||
<LinearLayout
|
||||
android:id="@+id/chatBarContainer"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
|
|
@ -11,13 +11,9 @@
|
|||
android:paddingRight="@dimen/spacing_medium">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/chatInputContainer"
|
||||
android:layout_width="match_parent"
|
||||
android:orientation="horizontal"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentTop="true"
|
||||
android:layout_alignParentLeft="true"
|
||||
android:layout_alignParentRight="true">
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/emojiButton"
|
||||
|
|
@ -91,13 +87,10 @@
|
|||
android:layout_height="wrap_content"
|
||||
android:text="@string/read_community_guidelines"
|
||||
style="@style/Caption3"
|
||||
android:textColor="@color/brand_300"
|
||||
android:layout_above="@id/spacing"
|
||||
android:layout_below="@id/chatInputContainer"/>
|
||||
android:textColor="@color/brand_300" />
|
||||
<Space
|
||||
android:id="@+id/spacing"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentBottom="true"/>
|
||||
</RelativeLayout>
|
||||
android:layout_height="match_parent" />
|
||||
</LinearLayout>
|
||||
</merge>
|
||||
|
|
@ -1,14 +1,14 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<style name="AppTheme.ActionBar.Transparent">
|
||||
<item name="android:windowTranslucentNavigation">true</item>
|
||||
<item name="android:windowTranslucentNavigation">false</item>
|
||||
<item name="android:windowTranslucentStatus">true</item>
|
||||
<item name="android:windowContentOverlay">@null</item>
|
||||
<item name="windowNoTitle">true</item>
|
||||
<item name="windowActionBarOverlay">true</item>
|
||||
</style>
|
||||
<style name="AppTheme.NoActionBar.Transparent">
|
||||
<item name="android:windowTranslucentNavigation">true</item>
|
||||
<item name="android:windowTranslucentNavigation">false</item>
|
||||
<item name="android:windowTranslucentStatus">true</item>
|
||||
<item name="android:windowContentOverlay">@null</item>
|
||||
<item name="windowNoTitle">true</item>
|
||||
|
|
|
|||
|
|
@ -88,4 +88,24 @@
|
|||
<attr name="hasAdditionalInfo" format="boolean" />
|
||||
<attr name="titleSize" format="dimension" />
|
||||
</declare-styleable>
|
||||
<declare-styleable name="BottomBar">
|
||||
<attr name="bb_tabXmlResource" format="reference" />
|
||||
<attr name="bb_tabletMode" format="boolean" />
|
||||
<attr name="bb_behavior">
|
||||
<flag name="shifting" value="1" />
|
||||
<flag name="shy" value="2" />
|
||||
<flag name="underNavbar" value="4" />
|
||||
<flag name="iconsOnly" value="8" />
|
||||
</attr>
|
||||
<attr name="bb_longPressHintsEnabled" format="boolean" />
|
||||
<attr name="bb_inActiveTabAlpha" format="float" />
|
||||
<attr name="bb_activeTabAlpha" format="float" />
|
||||
<attr name="bb_inActiveTabColor" format="color" />
|
||||
<attr name="bb_activeTabColor" format="color" />
|
||||
<attr name="bb_badgeBackgroundColor" format="color" />
|
||||
<attr name="bb_badgesHideWhenActive" format="boolean" />
|
||||
<attr name="bb_titleTextAppearance" format="reference" />
|
||||
<attr name="bb_titleTypeFace" format="string" />
|
||||
<attr name="bb_showShadow" format="boolean" />
|
||||
</declare-styleable>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -163,4 +163,8 @@
|
|||
<color name="black">#000</color>
|
||||
<color name="setup_background">#efeff4</color>
|
||||
<color name="setup_label_background">#fafaff</color>
|
||||
|
||||
<color name="bb_inActiveBottomBarItemColor">#747474</color>
|
||||
<color name="bb_darkBackgroundColor">#212121</color>
|
||||
<color name="bb_tabletRightBorderDark">#505050</color>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -135,4 +135,7 @@
|
|||
<dimen name="header_border_spacing">16dp</dimen>
|
||||
<dimen name="login_field_width">300dp</dimen>
|
||||
|
||||
<dimen name="bb_height">56dp</dimen>
|
||||
<dimen name="bb_default_elevation">8dp</dimen>
|
||||
<dimen name="bb_fake_shadow_height">4dp</dimen>
|
||||
</resources>
|
||||
|
|
|
|||
5
Habitica/res/values/ids.xml
Normal file
5
Habitica/res/values/ids.xml
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<item name="bb_bottom_bar_color_id" type="id"/>
|
||||
<item name="bb_bottom_bar_appearance_id" type="id"/>
|
||||
</resources>
|
||||
|
|
@ -434,4 +434,36 @@
|
|||
<style name="PurpleTextLabel" parent="TextAppearance.AppCompat">
|
||||
<item name="android:textColor">@color/brand_300</item>
|
||||
</style>
|
||||
|
||||
<style name="BB_BottomBarItem">
|
||||
<item name="android:background">?attr/selectableItemBackgroundBorderless</item>
|
||||
<item name="android:layout_width">wrap_content</item>
|
||||
<!-- layout_height is ignored since the height is set programmatically in BottomBar
|
||||
.updateItems() -->
|
||||
<item name="android:layout_height">@dimen/bb_height</item>
|
||||
</style>
|
||||
|
||||
<style name="BB_BottomBarItem_TitleStyle">
|
||||
<!-- Material spec: "Avoid long text labels as these labels do not truncate or wrap." -->
|
||||
<item name="android:singleLine">true</item>
|
||||
<item name="android:maxLines">1</item>
|
||||
<item name="android:gravity">center_horizontal</item>
|
||||
</style>
|
||||
|
||||
<style name="BB_BottomBarItem_Fixed.TitleAppearance" parent="TextAppearance.AppCompat.Body1">
|
||||
<item name="android:textSize">14sp</item>
|
||||
</style>
|
||||
|
||||
<style name="BB_BottomBarItem_Shifting.TitleAppearance" parent="BB_BottomBarItem_Fixed.TitleAppearance">
|
||||
<item name="android:textColor">#FFFFFF</item>
|
||||
</style>
|
||||
|
||||
<style name="BB_BottomBarItem_Tablet">
|
||||
<item name="android:layout_width">wrap_content</item>
|
||||
<item name="android:layout_height">wrap_content</item>
|
||||
</style>
|
||||
|
||||
<style name="BB_BottomBarBadge_Text" parent="TextAppearance.AppCompat.Body2">
|
||||
<item name="android:textColor">#FFFFFF</item>
|
||||
</style>
|
||||
</resources>
|
||||
|
|
@ -67,7 +67,7 @@ import com.habitrpg.android.habitica.widget.AvatarStatsWidgetProvider
|
|||
import com.habitrpg.android.habitica.widget.DailiesWidgetProvider
|
||||
import com.habitrpg.android.habitica.widget.HabitButtonWidgetProvider
|
||||
import com.habitrpg.android.habitica.widget.TodoListWidgetProvider
|
||||
import com.roughike.bottombar.BottomBar
|
||||
import com.habitrpg.android.habitica.ui.views.bottombar.BottomBar
|
||||
import io.reactivex.Completable
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.functions.Action
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ import com.habitrpg.android.habitica.helpers.RxErrorHandler
|
|||
import com.habitrpg.android.habitica.helpers.SoundManager
|
||||
import com.habitrpg.android.habitica.models.user.User
|
||||
import com.habitrpg.android.habitica.ui.activities.MainActivity
|
||||
import com.roughike.bottombar.BottomBar
|
||||
import com.habitrpg.android.habitica.ui.views.bottombar.BottomBar
|
||||
import io.reactivex.functions.Consumer
|
||||
|
||||
import javax.inject.Inject
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@ package com.habitrpg.android.habitica.ui.fragments
|
|||
|
||||
import android.app.ActionBar
|
||||
import android.content.Intent
|
||||
import android.graphics.Rect
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import androidx.core.content.ContextCompat
|
||||
|
|
@ -12,6 +14,7 @@ import androidx.recyclerview.widget.LinearLayoutManager
|
|||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.Window
|
||||
import com.habitrpg.android.habitica.HabiticaBaseApplication
|
||||
import com.habitrpg.android.habitica.R
|
||||
import com.habitrpg.android.habitica.data.InventoryRepository
|
||||
|
|
@ -47,6 +50,9 @@ import io.reactivex.disposables.CompositeDisposable
|
|||
import io.reactivex.functions.Consumer
|
||||
import kotlinx.android.synthetic.main.drawer_main.*
|
||||
import javax.inject.Inject
|
||||
import android.view.Window.ID_ANDROID_CONTENT
|
||||
import androidx.core.view.ViewCompat
|
||||
|
||||
|
||||
/**
|
||||
* Fragment used for managing interactions for and presentation of a navigation drawer.
|
||||
|
|
@ -166,10 +172,19 @@ class NavigationDrawerFragment : DialogFragment() {
|
|||
}
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
|
||||
savedInstanceState: Bundle?): View? = inflater.inflate(R.layout.drawer_main, container, false) as ViewGroup
|
||||
savedInstanceState: Bundle?): View? = inflater.inflate(R.layout.drawer_main, container, false) as? ViewGroup
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
var statusBarHeight = 0
|
||||
val resourceId = resources.getIdentifier("status_bar_height", "dimen", "android")
|
||||
if (resourceId > 0) {
|
||||
statusBarHeight = resources.getDimensionPixelSize(resourceId)
|
||||
}
|
||||
val params = menuHeaderView.layoutParams as? ViewGroup.MarginLayoutParams
|
||||
params?.topMargin = statusBarHeight
|
||||
|
||||
recyclerView.adapter = adapter
|
||||
recyclerView.layoutManager = androidx.recyclerview.widget.LinearLayoutManager(context)
|
||||
initializeMenuItems()
|
||||
|
|
|
|||
|
|
@ -34,6 +34,15 @@ object NavbarUtils {
|
|||
return size
|
||||
}
|
||||
|
||||
fun shouldDrawBehindNavbar(context: Context): Boolean {
|
||||
return isPortrait(context) && hasSoftKeys(context)
|
||||
}
|
||||
|
||||
private fun isPortrait(context: Context): Boolean {
|
||||
val res = context.resources
|
||||
return res.getBoolean(R.bool.is_portrait_mode)
|
||||
}
|
||||
|
||||
private fun getRealScreenSize(context: Context): Point {
|
||||
val windowManager = context.getSystemService(Context.WINDOW_SERVICE) as? WindowManager
|
||||
val display = windowManager?.defaultDisplay
|
||||
|
|
|
|||
|
|
@ -1,72 +0,0 @@
|
|||
package com.habitrpg.android.habitica.ui.helpers;
|
||||
|
||||
import android.content.Context;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
import android.view.WindowInsets;
|
||||
|
||||
//http://stackoverflow.com/a/27801394/1315039
|
||||
public class RecyclerViewEmptySupport extends RecyclerView {
|
||||
private View emptyView;
|
||||
final private AdapterDataObserver observer = new AdapterDataObserver() {
|
||||
@Override
|
||||
public void onChanged() {
|
||||
checkIfEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onItemRangeInserted(int positionStart, int itemCount) {
|
||||
checkIfEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onItemRangeRemoved(int positionStart, int itemCount) {
|
||||
checkIfEmpty();
|
||||
}
|
||||
};
|
||||
|
||||
public RecyclerViewEmptySupport(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
public RecyclerViewEmptySupport(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
public RecyclerViewEmptySupport(Context context, AttributeSet attrs, int defStyle) {
|
||||
super(context, attrs, defStyle);
|
||||
}
|
||||
|
||||
@Override
|
||||
public WindowInsets onApplyWindowInsets(WindowInsets insets) {
|
||||
return super.onApplyWindowInsets(insets);
|
||||
}
|
||||
|
||||
void checkIfEmpty() {
|
||||
if (emptyView != null && getAdapter() != null) {
|
||||
final boolean emptyViewVisible = getAdapter().getItemCount() == 0;
|
||||
emptyView.setVisibility(emptyViewVisible ? VISIBLE : GONE);
|
||||
setVisibility(emptyViewVisible ? GONE : VISIBLE);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAdapter(Adapter adapter) {
|
||||
final Adapter oldAdapter = getAdapter();
|
||||
if (oldAdapter != null) {
|
||||
oldAdapter.unregisterAdapterDataObserver(observer);
|
||||
}
|
||||
super.setAdapter(adapter);
|
||||
if (adapter != null) {
|
||||
adapter.registerAdapterDataObserver(observer);
|
||||
}
|
||||
|
||||
checkIfEmpty();
|
||||
}
|
||||
|
||||
public void setEmptyView(View emptyView) {
|
||||
this.emptyView = emptyView;
|
||||
checkIfEmpty();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
package com.habitrpg.android.habitica.ui.helpers
|
||||
|
||||
import android.content.Context
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import android.util.AttributeSet
|
||||
import android.view.View
|
||||
import android.view.WindowInsets
|
||||
|
||||
import com.habitrpg.android.habitica.ui.views.PaddedRecylerView
|
||||
|
||||
//http://stackoverflow.com/a/27801394/1315039
|
||||
class RecyclerViewEmptySupport : PaddedRecylerView {
|
||||
private var emptyView: View? = null
|
||||
private val observer = object : RecyclerView.AdapterDataObserver() {
|
||||
override fun onChanged() {
|
||||
checkIfEmpty()
|
||||
}
|
||||
|
||||
override fun onItemRangeInserted(positionStart: Int, itemCount: Int) {
|
||||
checkIfEmpty()
|
||||
}
|
||||
|
||||
override fun onItemRangeRemoved(positionStart: Int, itemCount: Int) {
|
||||
checkIfEmpty()
|
||||
}
|
||||
}
|
||||
|
||||
constructor(context: Context) : super(context)
|
||||
|
||||
constructor(context: Context, attrs: AttributeSet) : super(context, attrs)
|
||||
|
||||
constructor(context: Context, attrs: AttributeSet, defStyle: Int) : super(context, attrs, defStyle)
|
||||
|
||||
internal fun checkIfEmpty() {
|
||||
if (emptyView != null && adapter != null) {
|
||||
val emptyViewVisible = adapter?.itemCount == 0
|
||||
emptyView?.visibility = if (emptyViewVisible) View.VISIBLE else View.GONE
|
||||
visibility = if (emptyViewVisible) View.GONE else View.VISIBLE
|
||||
}
|
||||
}
|
||||
|
||||
override fun setAdapter(adapter: RecyclerView.Adapter<*>?) {
|
||||
val oldAdapter = getAdapter()
|
||||
oldAdapter?.unregisterAdapterDataObserver(observer)
|
||||
super.setAdapter(adapter)
|
||||
adapter?.registerAdapterDataObserver(observer)
|
||||
|
||||
checkIfEmpty()
|
||||
}
|
||||
|
||||
fun setEmptyView(emptyView: View?) {
|
||||
this.emptyView = emptyView
|
||||
checkIfEmpty()
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
package com.habitrpg.android.habitica.ui.views
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
import android.util.AttributeSet
|
||||
import android.view.View
|
||||
import android.widget.LinearLayout
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.habitrpg.android.habitica.ui.helpers.NavbarUtils
|
||||
|
||||
open class PaddedLinearLayout : LinearLayout {
|
||||
private var navBarAccountedHeightCalculated = false
|
||||
|
||||
constructor(context: Context) : super(context)
|
||||
|
||||
constructor(context: Context, attrs: AttributeSet) : super(context, attrs)
|
||||
|
||||
constructor(context: Context, attrs: AttributeSet, defStyle: Int) : super(context, attrs, defStyle)
|
||||
|
||||
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
|
||||
val navbarHeight = NavbarUtils.getNavbarHeight(context)
|
||||
val params = layoutParams as? MarginLayoutParams
|
||||
params?.setMargins(0, 0, 0, navbarHeight)
|
||||
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
package com.habitrpg.android.habitica.ui.views
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
import android.util.AttributeSet
|
||||
import android.view.View
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.habitrpg.android.habitica.ui.helpers.NavbarUtils
|
||||
|
||||
open class PaddedRecylerView : RecyclerView {
|
||||
private var navBarAccountedHeightCalculated = false
|
||||
|
||||
constructor(context: Context) : super(context)
|
||||
|
||||
constructor(context: Context, attrs: AttributeSet) : super(context, attrs)
|
||||
|
||||
constructor(context: Context, attrs: AttributeSet, defStyle: Int) : super(context, attrs, defStyle)
|
||||
|
||||
override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
|
||||
super.onLayout(changed, l, t, r, b)
|
||||
if (changed) {
|
||||
resizeForDrawingUnderNavbar()
|
||||
}
|
||||
}
|
||||
|
||||
//https://github.com/roughike/BottomBar/blob/master/bottom-bar/src/main/java/com/roughike/bottombar/BottomBar.java#L834
|
||||
private fun resizeForDrawingUnderNavbar() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
|
||||
val currentHeight = height
|
||||
|
||||
if (currentHeight != 0 && !navBarAccountedHeightCalculated) {
|
||||
navBarAccountedHeightCalculated = true
|
||||
|
||||
val navbarHeight = NavbarUtils.getNavbarHeight(context)
|
||||
setPadding(0, 0, 0, navbarHeight)
|
||||
(parent as? View)?.invalidate()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
/*
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
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);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,172 @@
|
|||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,730 @@
|
|||
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 = (AppCompatImageView) findViewById(R.id.bb_bottom_bar_icon);
|
||||
iconView.setImageResource(iconResId);
|
||||
|
||||
if (type != Type.TABLET && !isTitleless) {
|
||||
titleView = (TextView) 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,177 @@
|
|||
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 = Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP ? new LollipopBottomNavWithSnackBarImpl() : new PreLollipopBottomNavWithSnackBarImpl();
|
||||
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 PreLollipopBottomNavWithSnackBarImpl 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 = bottomNavHeight + mSnackbarHeight - defaultOffset;
|
||||
|
||||
ViewGroup.MarginLayoutParams layoutParams = (ViewGroup.MarginLayoutParams) dependency.getLayoutParams();
|
||||
layoutParams.bottomMargin = targetPadding;
|
||||
child.bringToFront();
|
||||
child.getParent().requestLayout();
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
|
||||
((View) child.getParent()).invalidate();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,121 @@
|
|||
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;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
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);
|
||||
}
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
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);
|
||||
}
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,210 @@
|
|||
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.
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
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);
|
||||
}
|
||||
|
|
@ -0,0 +1,150 @@
|
|||
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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -133,7 +133,7 @@ class ChatBarView : FrameLayout {
|
|||
navBarAccountedHeightCalculated = true
|
||||
|
||||
val navbarHeight = NavbarUtils.getNavbarHeight(context)
|
||||
spacing.updateLayoutParams<RelativeLayout.LayoutParams> {
|
||||
spacing.updateLayoutParams<LinearLayout.LayoutParams> {
|
||||
height = navbarHeight
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue