From a05ea079d18443fecd8b28461f6253fd2d6b5e21 Mon Sep 17 00:00:00 2001 From: Phillip Thelen Date: Thu, 6 Jun 2019 14:33:29 +0200 Subject: [PATCH] Implement new bottom navigation design --- Habitica/build.gradle | 2 +- .../drawable-hdpi/bottom_navigation_inset.png | Bin 0 -> 2078 bytes Habitica/res/drawable-hdpi/fab_background.png | Bin 0 -> 4434 bytes Habitica/res/drawable-hdpi/fab_plus.png | Bin 0 -> 316 bytes .../drawable-mdpi/bottom_navigation_inset.png | Bin 0 -> 1335 bytes Habitica/res/drawable-mdpi/fab_background.png | Bin 0 -> 3137 bytes Habitica/res/drawable-mdpi/fab_plus.png | Bin 0 -> 202 bytes .../bottom_navigation_inset.png | Bin 0 -> 2922 bytes .../res/drawable-xhdpi/fab_background.png | Bin 0 -> 6142 bytes Habitica/res/drawable-xhdpi/fab_plus.png | Bin 0 -> 344 bytes .../bottom_navigation_inset.png | Bin 0 -> 4899 bytes .../res/drawable-xxhdpi/fab_background.png | Bin 0 -> 9681 bytes Habitica/res/drawable-xxhdpi/fab_plus.png | Bin 0 -> 516 bytes .../bottom_navigation_inset.png | Bin 0 -> 7073 bytes .../res/drawable-xxxhdpi/fab_background.png | Bin 0 -> 13877 bytes Habitica/res/drawable-xxxhdpi/fab_plus.png | Bin 0 -> 688 bytes Habitica/res/drawable/nav_icon_colors.xml | 6 + Habitica/res/fab_background.png | Bin 0 -> 2008 bytes .../res/layout/activity_challenge_detail.xml | 2 +- Habitica/res/layout/activity_main.xml | 38 +- .../res/layout/bottom_navigation_item.xml | 38 + Habitica/res/layout/main_navigation_view.xml | 79 ++ Habitica/res/layout/snackbar_view.xml | 2 +- Habitica/res/menu/main_menu_tasks.xml | 18 + Habitica/res/values/attrs.xml | 7 +- .../habitica/ui/activities/MainActivity.kt | 14 +- .../habitica/ui/fragments/BaseMainFragment.kt | 4 +- .../ui/fragments/skills/SkillsFragment.kt | 4 +- .../ui/fragments/social/ChatFragment.kt | 2 +- .../ui/fragments/social/ChatListFragment.kt | 2 +- .../social/GroupInformationFragment.kt | 2 +- .../fragments/social/GuildDetailFragment.kt | 4 +- .../social/InboxMessageListFragment.kt | 2 +- .../tasks/TaskRecyclerViewFragment.kt | 2 +- .../ui/fragments/tasks/TasksFragment.kt | 57 +- .../helpers/FloatingActionMenuBehavior.java | 15 +- .../habitica/ui/views/HabiticaSnackbar.kt | 15 +- .../ui/views/bottombar/BadgeCircle.java | 58 - .../ui/views/bottombar/BadgeContainer.java | 14 - .../bottombar/BatchTabPropertyApplier.java | 26 - .../ui/views/bottombar/BottomBar.java | 1081 ----------------- .../ui/views/bottombar/BottomBarBadge.java | 172 --- .../ui/views/bottombar/BottomBarTab.java | 730 ----------- .../bottombar/BottomNavigationBehavior.java | 153 --- .../ui/views/bottombar/MiscUtils.java | 121 -- .../bottombar/OnTabReselectListener.java | 30 - .../views/bottombar/OnTabSelectListener.java | 32 - .../ui/views/bottombar/ShySettings.java | 55 - .../ui/views/bottombar/TabParser.java | 210 ---- .../bottombar/TabSelectionInterceptor.java | 33 - .../bottombar/VerticalScrollingBehavior.java | 150 --- .../views/navigation/BottomNavigationItem.kt | 52 + .../HabiticaBottomNavigationView.kt | 66 + 53 files changed, 332 insertions(+), 2966 deletions(-) create mode 100644 Habitica/res/drawable-hdpi/bottom_navigation_inset.png create mode 100644 Habitica/res/drawable-hdpi/fab_background.png create mode 100644 Habitica/res/drawable-hdpi/fab_plus.png create mode 100644 Habitica/res/drawable-mdpi/bottom_navigation_inset.png create mode 100644 Habitica/res/drawable-mdpi/fab_background.png create mode 100644 Habitica/res/drawable-mdpi/fab_plus.png create mode 100644 Habitica/res/drawable-xhdpi/bottom_navigation_inset.png create mode 100644 Habitica/res/drawable-xhdpi/fab_background.png create mode 100644 Habitica/res/drawable-xhdpi/fab_plus.png create mode 100644 Habitica/res/drawable-xxhdpi/bottom_navigation_inset.png create mode 100644 Habitica/res/drawable-xxhdpi/fab_background.png create mode 100644 Habitica/res/drawable-xxhdpi/fab_plus.png create mode 100644 Habitica/res/drawable-xxxhdpi/bottom_navigation_inset.png create mode 100644 Habitica/res/drawable-xxxhdpi/fab_background.png create mode 100644 Habitica/res/drawable-xxxhdpi/fab_plus.png create mode 100644 Habitica/res/drawable/nav_icon_colors.xml create mode 100644 Habitica/res/fab_background.png create mode 100644 Habitica/res/layout/bottom_navigation_item.xml create mode 100644 Habitica/res/layout/main_navigation_view.xml create mode 100644 Habitica/res/menu/main_menu_tasks.xml delete mode 100644 Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/bottombar/BadgeCircle.java delete mode 100644 Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/bottombar/BadgeContainer.java delete mode 100644 Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/bottombar/BatchTabPropertyApplier.java delete mode 100644 Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/bottombar/BottomBar.java delete mode 100644 Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/bottombar/BottomBarBadge.java delete mode 100644 Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/bottombar/BottomBarTab.java delete mode 100644 Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/bottombar/BottomNavigationBehavior.java delete mode 100644 Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/bottombar/MiscUtils.java delete mode 100644 Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/bottombar/OnTabReselectListener.java delete mode 100644 Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/bottombar/OnTabSelectListener.java delete mode 100644 Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/bottombar/ShySettings.java delete mode 100644 Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/bottombar/TabParser.java delete mode 100644 Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/bottombar/TabSelectionInterceptor.java delete mode 100644 Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/bottombar/VerticalScrollingBehavior.java create mode 100644 Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/navigation/BottomNavigationItem.kt create mode 100644 Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/navigation/HabiticaBottomNavigationView.kt diff --git a/Habitica/build.gradle b/Habitica/build.gradle index 9a3f4c9eb..5c6057a92 100644 --- a/Habitica/build.gradle +++ b/Habitica/build.gradle @@ -119,7 +119,7 @@ dependencies { implementation 'com.google.firebase:firebase-core:16.0.9' implementation 'com.google.firebase:firebase-messaging:18.0.0' implementation 'com.google.firebase:firebase-config:17.0.0' - implementation 'com.google.firebase:firebase-perf:17.0.0' + implementation 'com.google.firebase:firebase-perf:17.0.2' implementation 'com.google.android.gms:play-services-auth:16.0.1' implementation 'io.realm:android-adapters:3.1.0' implementation(project(':seeds-sdk')) { diff --git a/Habitica/res/drawable-hdpi/bottom_navigation_inset.png b/Habitica/res/drawable-hdpi/bottom_navigation_inset.png new file mode 100644 index 0000000000000000000000000000000000000000..860db0a48543fbf999218edb82511251ea7304b2 GIT binary patch literal 2078 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 HzH$Er_N|VU literal 0 HcmV?d00001 diff --git a/Habitica/res/drawable-hdpi/fab_plus.png b/Habitica/res/drawable-hdpi/fab_plus.png new file mode 100644 index 0000000000000000000000000000000000000000..994b31f3a31deb104d4937f8df0527c65d77695f GIT binary patch literal 316 zcmV-C0mJ@@P)Px#_DMuRR7ef&R~XdlLmJnUn6+jKN_E{t}ms26018=|$)^n-KN=gLp3~W3;>&%NH#cUf< zjOBau6tiCA)Wl+zI%7fpe5cWHymn)9zYHzo1KuA2KW<3e{iIWWqFfncUIs=ya;|{C zVI97@_<_QSW;F_XwcNPCgJvyw>v67{@#Si4)G{)Gh9ld7djY2ZX6zIU`J0%0HPTBI z=uioSq7(^$G~W|s8JTnq4D$YV8hLt$B9;$vWjSmx^}SK%WLYp&-H3nVXPY5i8Px(@kvBMRCodH+*@cIRTu!^b8_j05Nj+-QM3hbdr68h3YIEHG8^2Ac~Ig7D(Fk` zp-_eFBtgUx)R&4rRv#1<6>AG>p{=c^7h4fU9~3Eqq6Ht+3Mp;f(qw1O?>~frG~F~i z|D2iqXZGY}va{zu=imR!nOkO74&}x`EclT^o&f&1nbN)Tm`JY@jg*gJ8%=ZFo!VI5QBd$o4nbXA4|E) zr>U%1lKT;&Xxm2MWr~ZdSIeW74)LXly<2Y?rzOn=Naj)YfSQe!k z8UTag)hkxPc7j)CS-%3KqaU{)-}&D=2={dT7U9-v1QY_4dchzr4&a{}O#l4d`#AHS zSiDuZceDcfLmR~U09yv$>^%N=^&>owd#rv7+FI>^ei{bxVvNzEv+VckZ@cke<>8Cp ziT)}UKtC;mIEVA(ptIt%s(ylaBhk4o?IAWmf2dYR9NqBR%0K_9?(t8zE-k&DHL(J= zGUD+1?OoHS|Jo}6N_wd3_Yyl`D%6r{Mt3 zynFO9E#6aNfUSV|K;J}BV`LekW1DEF%u(CRfk6?iBtT=+33fuCGPn zfLTN=_fK9egnJ0Du=MMrX{@KF0A>ksQ?asU_CoCl9y)2FQ)>ObWBM$dIcRgHCM{(xe+IYG3w`@9K8WHcmZDO70&VGgayqolsgzJ$7 zV44uiy_Fl?8PA`2>%x^fp4Myt(}0Ml3b)L30(H>EtKE=)EisVYKXQWgD>O{11_Crdk-FVOv=^+z69N~LOEuap>p?u|0 zkgyZ~pVt|_bUY$b4HyNnR2bVV0Y1RXV;wvwXj!Blkb;O$P(1}ozKfT~+G%Cu6O{?T zFo?L%+k&_JzlA61nfUCP_z=N-Sn62}#M=r&y@Bmz5R>+0q%S$(`r+}*7vAo?qU+iH z-#Tag9R24M@K*PiaeudsYtGcXG6^_W40%op_(4eN;pTE+;TPmk3&t%j)kk2LFD7*3 zLCPS`y3039`Ma6g2d3PDl)eEZm5oo30pw#r%ESPY%El+i0P?XQWnutHW#bcM0Qp#u zGBJRpvhfKrfP5@SnHWG)+4uw*Kt2|vObj5YY)jZcsP|PcOto#4~002ovPDHLkV1n5;eYOAq literal 0 HcmV?d00001 diff --git a/Habitica/res/drawable-mdpi/fab_background.png b/Habitica/res/drawable-mdpi/fab_background.png new file mode 100644 index 0000000000000000000000000000000000000000..d13cf484c3515ca111b26a366d61f98e559497a9 GIT binary patch literal 3137 zcmZ`*3p|ti8{dpDx4DGuU@qkxvf0RGXXe@t<(6CgIkMR>?9vQFM>~?sa!H*+NkS5- zqcWBIy+jla6^a~k&63FBbT&HY`1^FWy?fu^^L)R*@AG}$_t|GDE>1s4fmOf&06@y# z&eoNGb`cz6JNVz}vhU0JC*dF~M=JoJB12*gFT!7o)9t*&0RRbk!2twZ$W`WJw8`#X zOfN?VbO4P4#}jD;5}ZY$^U(kRhK1%|Qbui%usVp zFGm-c4UIv9?SmV^4K%S}7z~DC5ChS!wzw~J{>of4n8~E05s0X$D0q}1oW=-3AkECo z5C$j&3Z>6S=!ZvBnRu2yHC$_RlfU<|C4~nt$aE%|MuiFX#S>@|Omj_5fzUs%%{rOn zz<-sf;a_a=4I%^?1QKq5_!^DGBL4?Xkoiv;r-Y|m7(XOO$T3Cdj1s+r~6N|!Z7}_5GwVBxu5&?Kw$zv=s@gs_ZMn!YxkGBFZK9ky3`AcEJ0F)%r*Agi+i7$&6>+O`pPh; z6>3bp6B=7ny^9>st`}^$^+3mP?B|}ku|6xq;|+`-v&>f)WlwyP zT#&p`*5EOYtM*7MFJGN9_B3DpA0CqsyUFMFDpbRK(qBlnU++>yxP5(G6XTCTdxrftQ zkP!Tf%VB7NvE|7hZpxzTX(J7LVoo2A%axlUm}B;JEe;glRMJ5Nq4rDn3fD0;L(Fz0 z@+NQ-!G)~kY~*Tp?eo}qui{@GnL5>rpH==w=BPzUdH`XJ4dtz_t_EvmCb3G1Qw`5; zKw0U(+qU+lWe3Zr3ps0y^1>LuQQx04@y;WwwlH{2phm_j9_ij z$|iZ#7BAr0rrs1~sQ)w814sa{@8mTQl3_W_ve&|avlVN!Wwk&KP(Rn?OhEBYgm}w& zU~jsT3E?1nE?!B8(951nR8k_ivFA8SqGMd(OI1Wj3k4Vn_WLDS3KXg0=bkJDjMVj0 zNcItmMEjK`cZ;~QvPIaJMqW;tyFq_*Y7TrLYAiBqj9IdxDOJGztH#Wgcp5~@K6|a` z9ds?VVxO8bm(40X+SoCRPP*SOPQX4<-^)JSNz<${_3ItO#(O!RoP9s4@6qp9 zz{r=0F9C1KGSox&$}wteJ1I?dF9>@K%c(@jdb%l042Eym@gbEl_FF|zGEmC3hH3ma_^EBo4zs-8n0MhADFM_yt~ zq+L0vKG-&&Zh9X!qW=4o9=-;3hjqMuVc{sJt4_4gZFo~r zl zsb&!P2G^ICCFbv&Ol~+RX%Ny9@^05q;;kqhvlaW29q%Sbxs|Tm8m-}uj^@X>h#;R) zyeOpN`l;HW(U%MMW21jk{)qEezM8}Hq;{MiANvH=>$R#cpN?@?wH!>!G|E%7Jk}eN z%&c$rNSaQpQtn1HHF%)(;tTZmj z&q(8(aLig*?-{M%>PEC~9r`Y{`sBNLrLLL*5NiigI8f66nxeM3XSEu13edEi_CR|i zq4|3k`IMRDq1O(sk31#C>piCoeHQ$odYvCAc3i_EjZ`Ze|29jlb?)f$seo^eW1V~& zG zXXv8GnV~B!Pns^yxv$I~TUnqloZzsm*~PO?+5+MKp literal 0 HcmV?d00001 diff --git a/Habitica/res/drawable-mdpi/fab_plus.png b/Habitica/res/drawable-mdpi/fab_plus.png new file mode 100644 index 0000000000000000000000000000000000000000..69965cefe14f1c0bd7c562e90971d3033de59b28 GIT binary patch literal 202 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 X-`#||HK-mw&%orOg;A}c>!1GsR>n}l literal 0 HcmV?d00001 diff --git a/Habitica/res/drawable-xhdpi/fab_background.png b/Habitica/res/drawable-xhdpi/fab_background.png new file mode 100644 index 0000000000000000000000000000000000000000..913c83f7fb7f64f57d9b8ee3117ed06218e1b2ba GIT binary patch literal 6142 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 Rs^d5EIvV=wWvaH({|8@|ws`;m literal 0 HcmV?d00001 diff --git a/Habitica/res/drawable-xhdpi/fab_plus.png b/Habitica/res/drawable-xhdpi/fab_plus.png new file mode 100644 index 0000000000000000000000000000000000000000..37645eed05d78ca4e99f84c7dc98f1eadd6e82ca GIT binary patch literal 344 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 literal 0 HcmV?d00001 diff --git a/Habitica/res/drawable-xxhdpi/fab_background.png b/Habitica/res/drawable-xxhdpi/fab_background.png new file mode 100644 index 0000000000000000000000000000000000000000..05e7eb474f4c5ce7540a7cf79e254ad27ade0f13 GIT binary patch literal 9681 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 literal 0 HcmV?d00001 diff --git a/Habitica/res/drawable-xxhdpi/fab_plus.png b/Habitica/res/drawable-xxhdpi/fab_plus.png new file mode 100644 index 0000000000000000000000000000000000000000..402a7f2d50cbde35aa890309f3093599b003820e GIT binary patch literal 516 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 JS?83{1ON#KBbNXG literal 0 HcmV?d00001 diff --git a/Habitica/res/drawable/nav_icon_colors.xml b/Habitica/res/drawable/nav_icon_colors.xml new file mode 100644 index 000000000..cd141807d --- /dev/null +++ b/Habitica/res/drawable/nav_icon_colors.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/Habitica/res/fab_background.png b/Habitica/res/fab_background.png new file mode 100644 index 0000000000000000000000000000000000000000..939ff7e207cb2efc517c51c1a36c28373273c13b GIT binary patch literal 2008 zcmV;}2PgQ6P)Px+l1W5CRCod9+|O^*RvZBE=lAT`ZByE=RI92XO>BZqJphM^%MLy5u*0-*;1Aes z5)w?480xB)0s_W%nKZT?*nLBqH0`imk{v5MhGF|ZI`-k?GrVg5g1GekB?9rc(7%XQ1X5hLCSw*ONJ)Pt59o4zf zC>UZrcwp1FhNq7(!})^vWCZucC;pNrizohOAOBdbR^@1MRMz&?1)M4Ei_RcbY~Yrv+^@SYKZsZ6?AHObt0YaQ4u^rDJ(fHPePmym`g}rr>Z~lPlA= zS5AIeWDd9OwV=7`ZmNl*#SNzL(uPY6Eh(@pz$Jo~G+5ejv7_k%OA{_uG~Hln!NrD_ z7FZf^v7n_5rW>whw5)*Xglh>cYhb$Ic8r!)Fdc9^Ld!Z>(s0{GYZtI2;kJd=ZeU5l zHI3G;U`fC=h1Tw1al>ti)+u0d!aWzQ)4<|_dnQ_^g2e&X5Uta}T7`QGO@U!H8xy(P z$r`DhX{Yf{E}hq*bsLy^bGHi@qIE0SbKxFBCci-mM1Zy#3`+%GDa@SMD)X&2*ePQb zabBgXqK@NGDDlFI%&_kd(QpErCaz#v-AJ|Rn1xHB zLWx3MoU!)enh~wc#Fn|yr%gP0;=Stkcd(#t)5~q1(nKG5?9n{=>qWx&hCiYQ_fIHA3#=+(kmJm#2Qh)2~{oTgGL% zaQ=32`WIHM`8_U|P?Z!wVX?F_z>ggv8Wh_3`TT-k9jMB_nkRp&On&A1t21ilP9K-V z!iB%qCa=IrP>?<%;Io=^RvdqEvEPTv&ANf2oGSW-@0vb*sPo;tD+hckel4H+rY4tX z>z>hp4=Mn5b9Ls5OBpw*8PvQ%e=bg;QC=tFF#70&G*uTvsUfl-(7n!z}$)Hs4+$^BMfffL?s9_k_v zu6R8D=dlT$q6r`P%yy5sC>S?o&?G&(&qFYEWh45l7}bwOjp`yzEvSpQT1^h_ALMZQ zqgEHSjC;IaUgC|cZbVb%yPQ8&8c8vLD3 zlh?y=-&z?OUb$`M1*6Z6>k6;}!z2iCsEb*n=p|~MV0e5@T~CWb>}0x?3z=oPc>c!9 zbdZ-+X*QJv!E5@u!L*^_h8n@i5y>z;=s>#hK=0{ydd$%aSRWRw57j$s;=%OI%DJC? zC>4N8PS1qgPTF8v(Vl`+c~xFAp$q~tU5;tX5qn?nk=Ljc#@*s~_e*BM>j5x>7y>ij znAm|ryD-hxOcDgQ8~324c}91-F5=eVxWSpElC2mL{*}*Xj^M$5Rhj8KQ21p?Ll}dG zz|40udCBPZl9dPY9uEFQ4m)*dre1#;$^u?sF5a+2 zV0E=GNa8w9foA~>k0}*2+^~X!$)HLJYbRh@EU`g8xU_LYEL?^;$+RO3_ZtW8ecF@F zZro!Zht=h(rWFgO$;A5dYlNuUFl>%EH(K(vzLxe;>S%bx2!~@;nvc`bK@}b1JdDuj qvNWbMfu;^z;i<>baTc)D{`?Qq3$kJN@ASw30000 - - + - + - - + android:layout_height="wrap_content" + android:layout_alignParentBottom="true" + android:layout_alignParentStart="true" + android:layout_alignParentEnd="true" /> + + + + + + \ No newline at end of file diff --git a/Habitica/res/layout/main_navigation_view.xml b/Habitica/res/layout/main_navigation_view.xml new file mode 100644 index 000000000..be983d27b --- /dev/null +++ b/Habitica/res/layout/main_navigation_view.xml @@ -0,0 +1,79 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Habitica/res/layout/snackbar_view.xml b/Habitica/res/layout/snackbar_view.xml index 4d67e26b2..4bfadd158 100644 --- a/Habitica/res/layout/snackbar_view.xml +++ b/Habitica/res/layout/snackbar_view.xml @@ -13,7 +13,7 @@ android:paddingStart="6dp" android:gravity="center" tools:background="@drawable/snackbar_background_green" - android:layout_marginBottom="16dp" + android:layout_marginBottom="32dp" android:layout_gravity="center_horizontal" android:elevation="24dp"> + + + + + + + \ No newline at end of file diff --git a/Habitica/res/values/attrs.xml b/Habitica/res/values/attrs.xml index d67ca7bad..aba07135f 100644 --- a/Habitica/res/values/attrs.xml +++ b/Habitica/res/values/attrs.xml @@ -12,6 +12,7 @@ + @@ -46,7 +47,7 @@ - + @@ -119,4 +120,8 @@ + + + + diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/MainActivity.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/MainActivity.kt index 44565adc7..84f6f9e0e 100755 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/MainActivity.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/MainActivity.kt @@ -68,8 +68,8 @@ import com.habitrpg.android.habitica.ui.views.HabiticaIconsHelper import com.habitrpg.android.habitica.ui.views.HabiticaSnackbar import com.habitrpg.android.habitica.ui.views.HabiticaSnackbar.SnackbarDisplayType import com.habitrpg.android.habitica.ui.views.ValueBar -import com.habitrpg.android.habitica.ui.views.bottombar.BottomBar import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaAlertDialog +import com.habitrpg.android.habitica.ui.views.navigation.HabiticaBottomNavigationView import com.habitrpg.android.habitica.ui.views.yesterdailies.YesterdailyDialog import com.habitrpg.android.habitica.userpicture.BitmapUtils import com.habitrpg.android.habitica.widget.AvatarStatsWidgetProvider @@ -121,8 +121,8 @@ open class MainActivity : BaseActivity(), TutorialView.OnTutorialReaction { @Inject internal lateinit var appConfigManager: AppConfigManager - val floatingMenuWrapper: ViewGroup by bindView(R.id.floating_menu_wrapper) - internal val bottomNavigation: BottomBar by bindView(R.id.bottom_navigation) + val snackbarContainer: ViewGroup by bindView(R.id.snackbar_container) + internal val bottomNavigation: HabiticaBottomNavigationView by bindView(R.id.bottom_navigation) private val appBar: AppBarLayout by bindView(R.id.appbar) internal val toolbar: Toolbar by bindView(R.id.toolbar) @@ -467,7 +467,7 @@ open class MainActivity : BaseActivity(), TutorialView.OnTutorialReaction { val pet = event.usingPet compositeSubscription.add(this.inventoryRepository.feedPet(event.usingPet, event.usingFood) .subscribe(Consumer { feedResponse -> - HabiticaSnackbar.showSnackbar(floatingMenuWrapper, getString(R.string.notification_pet_fed, pet.text), SnackbarDisplayType.NORMAL) + HabiticaSnackbar.showSnackbar(snackbarContainer, getString(R.string.notification_pet_fed, pet.text), SnackbarDisplayType.NORMAL) if (feedResponse.value == -1) { val mountWrapper = View.inflate(this, R.layout.pet_imageview, null) as? FrameLayout val mountImageView = mountWrapper?.findViewById(R.id.pet_imageview) as? SimpleDraweeView @@ -499,12 +499,12 @@ open class MainActivity : BaseActivity(), TutorialView.OnTutorialReaction { internal fun displayTaskScoringResponse(data: TaskScoringResult?) { if (user != null && data != null) { - compositeSubscription.add(notifyUserUseCase.observable(NotifyUserUseCase.RequestValues(this, floatingMenuWrapper, + compositeSubscription.add(notifyUserUseCase.observable(NotifyUserUseCase.RequestValues(this, snackbarContainer, user, data.experienceDelta, data.healthDelta, data.goldDelta, data.manaDelta, if (userIsOnQuest) data.questDamage else 0.0, data.hasLeveledUp)) .subscribe(Consumer { }, RxErrorHandler.handleEmptyError())) } - compositeSubscription.add(displayItemDropUseCase.observable(DisplayItemDropUseCase.RequestValues(data, this, floatingMenuWrapper)) + compositeSubscription.add(displayItemDropUseCase.observable(DisplayItemDropUseCase.RequestValues(data, this, snackbarContainer)) .subscribe(Consumer { }, RxErrorHandler.handleEmptyError())) } @@ -689,7 +689,7 @@ open class MainActivity : BaseActivity(), TutorialView.OnTutorialReaction { @Subscribe fun showSnackBarEvent(event: ShowSnackbarEvent) { - HabiticaSnackbar.showSnackbar(floatingMenuWrapper, event.leftImage, event.title, event.text, event.specialView, event.rightIcon, event.rightTextColor, event.rightText, event.type) + HabiticaSnackbar.showSnackbar(snackbarContainer, event.leftImage, event.title, event.text, event.specialView, event.rightIcon, event.rightTextColor, event.rightText, event.type) } @Subscribe diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/BaseMainFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/BaseMainFragment.kt index 9dcc8f904..f74fc97d7 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/BaseMainFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/BaseMainFragment.kt @@ -30,7 +30,7 @@ abstract class BaseMainFragment : BaseFragment() { val collapsingToolbar get() = activity?.toolbar val toolbarAccessoryContainer get() = activity?.toolbarAccessoryContainer val bottomNavigation get() = activity?.bottomNavigation - val floatingMenuWrapper get() = activity?.floatingMenuWrapper + val floatingMenuWrapper get() = activity?.snackbarContainer var usesTabLayout: Boolean = false var hidesToolbar: Boolean = false var usesBottomNavigation = false @@ -56,8 +56,6 @@ abstract class BaseMainFragment : BaseFragment() { } if (this.usesBottomNavigation) { - bottomNavigation?.removeOnTabSelectListener() - bottomNavigation?.removeOnTabReselectListener() bottomNavigation?.visibility = View.VISIBLE } else { bottomNavigation?.visibility = View.GONE diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/skills/SkillsFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/skills/SkillsFragment.kt index 7e8a98d75..98066e3f7 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/skills/SkillsFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/skills/SkillsFragment.kt @@ -110,10 +110,10 @@ class SkillsFragment : BaseMainFragment() { adapter?.mana = response.user.stats?.mp ?: 0.0 val activity = activity ?: return if ("special" == usedSkill?.habitClass) { - showSnackbar(activity.floatingMenuWrapper, context?.getString(R.string.used_skill_without_mana, usedSkill.text), HabiticaSnackbar.SnackbarDisplayType.BLUE) + showSnackbar(activity.snackbarContainer, context?.getString(R.string.used_skill_without_mana, usedSkill.text), HabiticaSnackbar.SnackbarDisplayType.BLUE) } else { context.notNull { - showSnackbar(activity.floatingMenuWrapper, null, + showSnackbar(activity.snackbarContainer, null, context?.getString(R.string.used_skill_without_mana, usedSkill?.text), BitmapDrawable(resources, HabiticaIconsHelper.imageOfMagic()), ContextCompat.getColor(it, R.color.blue_10), "-" + usedSkill?.mana, diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/ChatFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/ChatFragment.kt index ed091da5f..edc3fa18d 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/ChatFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/ChatFragment.kt @@ -195,7 +195,7 @@ class ChatFragment : BaseFragment(), SwipeRefreshLayout.OnRefreshListener { clipMan?.primaryClip = messageText val activity = activity as? MainActivity if (activity != null) { - showSnackbar(activity.floatingMenuWrapper, getString(R.string.chat_message_copied), SnackbarDisplayType.NORMAL) + showSnackbar(activity.snackbarContainer, getString(R.string.chat_message_copied), SnackbarDisplayType.NORMAL) } } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/ChatListFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/ChatListFragment.kt index b40f4f433..15a7e29b4 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/ChatListFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/ChatListFragment.kt @@ -250,7 +250,7 @@ class ChatListFragment : BaseFragment(), SwipeRefreshLayout.OnRefreshListener { clipMan?.primaryClip = messageText val activity = activity as? MainActivity if (activity != null) { - showSnackbar(activity.floatingMenuWrapper, getString(R.string.chat_message_copied), SnackbarDisplayType.NORMAL) + showSnackbar(activity.snackbarContainer, getString(R.string.chat_message_copied), SnackbarDisplayType.NORMAL) } } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/GroupInformationFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/GroupInformationFragment.kt index 77b46605d..8a444042b 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/GroupInformationFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/GroupInformationFragment.kt @@ -102,7 +102,7 @@ class GroupInformationFragment : BaseFragment() { clipboard?.primaryClip = clip val activity = activity as? MainActivity if (activity != null) { - HabiticaSnackbar.showSnackbar(activity.floatingMenuWrapper, getString(R.string.username_copied), HabiticaSnackbar.SnackbarDisplayType.NORMAL) + HabiticaSnackbar.showSnackbar(activity.snackbarContainer, getString(R.string.username_copied), HabiticaSnackbar.SnackbarDisplayType.NORMAL) } } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/GuildDetailFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/GuildDetailFragment.kt index 466199147..9eb5c8658 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/GuildDetailFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/GuildDetailFragment.kt @@ -80,7 +80,7 @@ class GuildDetailFragment : BaseFragment() { viewModel?.leaveGroup { val activity = activity as? MainActivity if (activity != null) { - HabiticaSnackbar.showSnackbar(activity.floatingMenuWrapper, getString(R.string.left_guild), HabiticaSnackbar.SnackbarDisplayType.NORMAL) + HabiticaSnackbar.showSnackbar(activity.snackbarContainer, getString(R.string.left_guild), HabiticaSnackbar.SnackbarDisplayType.NORMAL) } } } @@ -88,7 +88,7 @@ class GuildDetailFragment : BaseFragment() { viewModel?.joinGroup { val activity = activity as? MainActivity if (activity != null) { - HabiticaSnackbar.showSnackbar(activity.floatingMenuWrapper, getString(R.string.joined_guild), HabiticaSnackbar.SnackbarDisplayType.NORMAL) + HabiticaSnackbar.showSnackbar(activity.snackbarContainer, getString(R.string.joined_guild), HabiticaSnackbar.SnackbarDisplayType.NORMAL) } } } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/InboxMessageListFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/InboxMessageListFragment.kt index 1afbe70ad..d05dc7a5c 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/InboxMessageListFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/InboxMessageListFragment.kt @@ -143,7 +143,7 @@ class InboxMessageListFragment : BaseMainFragment(), androidx.swiperefreshlayout clipMan?.primaryClip = messageText val activity = getActivity() as? MainActivity if (activity != null) { - showSnackbar(activity.floatingMenuWrapper, getString(R.string.chat_message_copied), HabiticaSnackbar.SnackbarDisplayType.NORMAL) + showSnackbar(activity.snackbarContainer, getString(R.string.chat_message_copied), HabiticaSnackbar.SnackbarDisplayType.NORMAL) } } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/tasks/TaskRecyclerViewFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/tasks/TaskRecyclerViewFragment.kt index 28bbd6056..f1d8dc51d 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/tasks/TaskRecyclerViewFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/tasks/TaskRecyclerViewFragment.kt @@ -99,7 +99,7 @@ open class TaskRecyclerViewFragment : BaseFragment(), androidx.swiperefreshlayou .doOnNext { soundManager.loadAndPlayAudio(SoundManager.SoundTodo) } .flatMap { taskRepository.taskChecked(user, it.first, it.second == TaskDirection.UP, false) { _ -> (activity as? MainActivity)?.let { activity -> - HabiticaSnackbar.showSnackbar(activity.floatingMenuWrapper, null, getString(R.string.notification_purchase_reward), + HabiticaSnackbar.showSnackbar(activity.snackbarContainer, null, getString(R.string.notification_purchase_reward), BitmapDrawable(resources, HabiticaIconsHelper.imageOfGold()), ContextCompat.getColor(activity, R.color.yellow_10), "-" + it.first.value.toInt(), diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/tasks/TasksFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/tasks/TasksFragment.kt index 4dc250856..6a216a776 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/tasks/TasksFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/tasks/TasksFragment.kt @@ -5,7 +5,6 @@ import android.content.Intent import android.os.Bundle import android.view.* import androidx.fragment.app.FragmentPagerAdapter -import com.github.clans.fab.FloatingActionButton import com.github.clans.fab.FloatingActionMenu import com.habitrpg.android.habitica.HabiticaBaseApplication import com.habitrpg.android.habitica.R @@ -44,32 +43,13 @@ class TasksFragment : BaseMainFragment() { override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { - this.usesTabLayout = false this.usesBottomNavigation = true this.displayingTaskForm = false super.onCreateView(inflater, container, savedInstanceState) val v = inflater.inflate(R.layout.fragment_viewpager, container, false) - viewPager = v.findViewById(R.id.viewPager) - val view = inflater.inflate(R.layout.floating_menu_tasks, floatingMenuWrapper, true) - floatingMenu = if (FloatingActionMenu::class.java == view.javaClass) { - view as? FloatingActionMenu - } else { - val frame = view as? ViewGroup - frame?.findViewById(R.id.fab_menu) - } - val habitFab = floatingMenu?.findViewById(R.id.fab_new_habit) - habitFab?.setOnClickListener { openNewTaskActivity(Task.TYPE_HABIT) } - val dailyFab = floatingMenu?.findViewById(R.id.fab_new_daily) - dailyFab?.setOnClickListener { openNewTaskActivity(Task.TYPE_DAILY) } - val todoFab = floatingMenu?.findViewById(R.id.fab_new_todo) - todoFab?.setOnClickListener { openNewTaskActivity(Task.TYPE_TODO) } - val rewardFab = floatingMenu?.findViewById(R.id.fab_new_reward) - rewardFab?.setOnClickListener { openNewTaskActivity(Task.TYPE_REWARD) } - floatingMenu?.setOnMenuButtonLongClickListener { this.onFloatingMenuLongClicked() } - loadTaskLists() return v @@ -78,33 +58,25 @@ class TasksFragment : BaseMainFragment() { override fun onResume() { super.onResume() - bottomNavigation?.setBadgesHideWhenActive(true) - bottomNavigation?.setOnTabSelectListener { tabId -> - when (tabId) { - R.id.tab_habits -> viewPager?.currentItem = 0 - R.id.tab_dailies -> viewPager?.currentItem = 1 - R.id.tab_todos -> viewPager?.currentItem = 2 - R.id.tab_rewards -> viewPager?.currentItem = 3 + bottomNavigation?.onTabSelectedListener = { + when (it) { + Task.TYPE_HABIT -> viewPager?.currentItem = 0 + Task.TYPE_DAILY -> viewPager?.currentItem = 1 + Task.TYPE_TODO -> viewPager?.currentItem = 2 + Task.TYPE_REWARD -> viewPager?.currentItem = 3 } updateBottomBarBadges() } + bottomNavigation?.onAddListener = { + openNewTaskActivity(it) + } } override fun onDestroy() { tagRepository.close() - bottomNavigation?.removeOnTabSelectListener() super.onDestroy() } - private fun onFloatingMenuLongClicked(): Boolean { - val currentFragment = activeFragment - if (currentFragment != null) { - val className = currentFragment.className - openNewTaskActivity(className) - } - return true - } - override fun injectFragment(component: UserComponent) { component.inject(this) } @@ -145,7 +117,6 @@ class TasksFragment : BaseMainFragment() { } } dialog.setListener(object : TaskFilterDialog.OnFilterCompletedListener { - override fun onFilterCompleted(activeTaskFilter: String?, activeTags: MutableList) { if (viewFragmentsDictionary == null) { return @@ -204,7 +175,7 @@ class TasksFragment : BaseMainFragment() { } override fun onPageSelected(position: Int) { - bottomNavigation?.selectTabAtPosition(position) + bottomNavigation?.selectedPosition = position updateFilterIcon() } @@ -233,7 +204,7 @@ class TasksFragment : BaseMainFragment() { if (bottomNavigation == null) { return } - tutorialRepository.getTutorialSteps(Arrays.asList("habits", "dailies", "todos", "rewards")).subscribe(Consumer { tutorialSteps -> + compositeSubscription.add(tutorialRepository.getTutorialSteps(Arrays.asList("habits", "dailies", "todos", "rewards")).subscribe(Consumer { tutorialSteps -> val activeTutorialFragments = ArrayList() for (step in tutorialSteps) { var id = -1 @@ -256,13 +227,13 @@ class TasksFragment : BaseMainFragment() { } else -> "" } - val tab = bottomNavigation?.getTabWithId(id) + /*val tab = bottomNavigation?.id(id) if (step.shouldDisplay()) { tab?.setBadgeCount(1) activeTutorialFragments.add(taskType) } else { tab?.removeBadge() - } + }*/ } if (activeTutorialFragments.size == 1) { val fragment = viewFragmentsDictionary?.get(indexForTaskType(activeTutorialFragments[0])) @@ -273,7 +244,7 @@ class TasksFragment : BaseMainFragment() { } } } - }, RxErrorHandler.handleEmptyError()) + }, RxErrorHandler.handleEmptyError())) } // endregion diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/helpers/FloatingActionMenuBehavior.java b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/helpers/FloatingActionMenuBehavior.java index d33c2202b..97998db3d 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/helpers/FloatingActionMenuBehavior.java +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/helpers/FloatingActionMenuBehavior.java @@ -3,15 +3,16 @@ package com.habitrpg.android.habitica.ui.helpers; // https://gist.github.com/lodlock/e3cd12130bad70a098db import android.content.Context; -import androidx.annotation.Nullable; -import androidx.coordinatorlayout.widget.CoordinatorLayout; -import com.google.android.material.snackbar.Snackbar; -import androidx.core.view.ViewCompat; import android.util.AttributeSet; import android.view.View; import android.view.ViewGroup; +import androidx.annotation.Nullable; +import androidx.coordinatorlayout.widget.CoordinatorLayout; +import androidx.core.view.ViewCompat; + import com.github.clans.fab.FloatingActionMenu; +import com.google.android.material.snackbar.Snackbar; import com.habitrpg.android.habitica.R; import java.util.List; @@ -59,7 +60,7 @@ public class FloatingActionMenuBehavior extends CoordinatorLayout.Behavior { .translationY(translationY) .setListener(null); } else { - ViewCompat.setTranslationY(child, translationY); + child.setTranslationY(translationY); } this.mTranslationY = translationY; @@ -75,7 +76,7 @@ public class FloatingActionMenuBehavior extends CoordinatorLayout.Behavior { for (int z = dependencies.size(); i < z; ++i) { View view = (View) dependencies.get(i); if (view instanceof Snackbar.SnackbarLayout && parent.doViewsOverlap(child, view)) { - minOffset = Math.min(minOffset, ViewCompat.getTranslationY(view) - (float) view.getHeight()); + minOffset = Math.min(minOffset, view.getTranslationY() - (float) view.getHeight()); } } @@ -84,7 +85,7 @@ public class FloatingActionMenuBehavior extends CoordinatorLayout.Behavior { @Override public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout, View child, View directTargetChild, View target, int nestedScrollAxes) { - ViewGroup fabContainer = child.findViewById(R.id.floating_menu_wrapper); + ViewGroup fabContainer = child.findViewById(R.id.snackbar_container); if (fabContainer.getChildCount() > 0) { if (fabContainer.getChildAt(0).getClass().equals(FloatingActionMenu.class)) { fab = (FloatingActionMenu) fabContainer.getChildAt(0); diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/HabiticaSnackbar.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/HabiticaSnackbar.kt index 1e9d2d8f2..9a5016afb 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/HabiticaSnackbar.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/HabiticaSnackbar.kt @@ -1,18 +1,17 @@ package com.habitrpg.android.habitica.ui.views import android.graphics.drawable.Drawable -import androidx.annotation.ColorInt -import com.google.android.material.snackbar.BaseTransientBottomBar -import com.google.android.material.snackbar.Snackbar -import androidx.core.content.ContextCompat -import androidx.core.view.ViewCompat import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.ImageView import android.widget.LinearLayout import android.widget.TextView - +import androidx.annotation.ColorInt +import androidx.core.content.ContextCompat +import androidx.core.view.ViewCompat +import com.google.android.material.snackbar.BaseTransientBottomBar +import com.google.android.material.snackbar.Snackbar import com.habitrpg.android.habitica.R import com.habitrpg.android.habitica.ui.helpers.NavbarUtils @@ -87,13 +86,17 @@ private constructor(parent: ViewGroup, content: View, callback: ContentViewCallb override fun animateContentIn(delay: Int, duration: Int) { content.scaleY = 0f + content.scaleX = 0f ViewCompat.animate(content).scaleY(1f).setDuration(duration.toLong()).startDelay = delay.toLong() + ViewCompat.animate(content).scaleX(1f).setDuration(duration.toLong()).startDelay = delay.toLong() ViewCompat.animate(content).alpha(1f).setDuration(duration.toLong()).startDelay = delay.toLong() } override fun animateContentOut(delay: Int, duration: Int) { content.scaleY = 1f + content.scaleX = 1f ViewCompat.animate(content).scaleY(0f).setDuration(duration.toLong()).startDelay = delay.toLong() + ViewCompat.animate(content).scaleX(0f).setDuration(duration.toLong()).startDelay = delay.toLong() ViewCompat.animate(content).alpha(0f).setDuration(duration.toLong()).startDelay = delay.toLong() } } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/bottombar/BadgeCircle.java b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/bottombar/BadgeCircle.java deleted file mode 100644 index 095efa2a0..000000000 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/bottombar/BadgeCircle.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * BottomBar library for Android - * Copyright (c) 2016 Iiro Krankka (http://github.com/roughike). - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.habitrpg.android.habitica.ui.views.bottombar; - -import android.graphics.drawable.ShapeDrawable; -import android.graphics.drawable.shapes.OvalShape; -import androidx.annotation.ColorInt; -import androidx.annotation.IntRange; -import androidx.annotation.NonNull; - -/* - * BottomBar library for Android - * Copyright (c) 2016 Iiro Krankka (http://github.com/roughike). - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -class BadgeCircle { - /** - * Creates a new circle for the Badge background. - * - * @param size the width and height for the circle - * @param color the activeIconColor for the circle - * @return a nice and adorable circle. - */ - @NonNull - static ShapeDrawable make(@IntRange(from = 0) int size, @ColorInt int color) { - ShapeDrawable indicator = new ShapeDrawable(new OvalShape()); - indicator.setIntrinsicWidth(size); - indicator.setIntrinsicHeight(size); - indicator.getPaint().setColor(color); - return indicator; - } -} diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/bottombar/BadgeContainer.java b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/bottombar/BadgeContainer.java deleted file mode 100644 index e7551c4be..000000000 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/bottombar/BadgeContainer.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.habitrpg.android.habitica.ui.views.bottombar; - -import android.content.Context; -import androidx.annotation.NonNull; -import android.widget.FrameLayout; - -/** - * Created by iiro on 29.8.2016. - */ -public class BadgeContainer extends FrameLayout { - public BadgeContainer(@NonNull Context context) { - super(context); - } -} diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/bottombar/BatchTabPropertyApplier.java b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/bottombar/BatchTabPropertyApplier.java deleted file mode 100644 index 3a3849ebe..000000000 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/bottombar/BatchTabPropertyApplier.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.habitrpg.android.habitica.ui.views.bottombar; - -import androidx.annotation.NonNull; - -class BatchTabPropertyApplier { - private final BottomBar bottomBar; - - interface TabPropertyUpdater { - void update(BottomBarTab tab); - } - - BatchTabPropertyApplier(@NonNull BottomBar bottomBar) { - this.bottomBar = bottomBar; - } - - void applyToAllTabs(@NonNull TabPropertyUpdater propertyUpdater) { - int tabCount = bottomBar.getTabCount(); - - if (tabCount > 0) { - for (int i = 0; i < tabCount; i++) { - BottomBarTab tab = bottomBar.getTabAtPosition(i); - propertyUpdater.update(tab); - } - } - } -} diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/bottombar/BottomBar.java b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/bottombar/BottomBar.java deleted file mode 100644 index bedf4f764..000000000 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/bottombar/BottomBar.java +++ /dev/null @@ -1,1081 +0,0 @@ -package com.habitrpg.android.habitica.ui.views.bottombar; - -import android.animation.Animator; -import android.animation.AnimatorListenerAdapter; -import android.annotation.TargetApi; -import android.content.Context; -import android.content.res.TypedArray; -import android.graphics.Color; -import android.graphics.Typeface; -import android.graphics.drawable.ColorDrawable; -import android.graphics.drawable.Drawable; -import android.os.Build; -import android.os.Bundle; -import android.os.Parcelable; -import androidx.annotation.ColorInt; -import androidx.annotation.IdRes; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.annotation.RequiresApi; -import androidx.annotation.VisibleForTesting; -import androidx.annotation.XmlRes; -import androidx.coordinatorlayout.widget.CoordinatorLayout; -import androidx.core.content.ContextCompat; -import androidx.core.view.ViewCompat; -import androidx.core.view.ViewPropertyAnimatorListenerAdapter; - -import android.util.AttributeSet; -import android.util.Log; -import android.view.View; -import android.view.ViewAnimationUtils; -import android.view.ViewGroup; -import android.view.ViewOutlineProvider; -import android.view.ViewParent; -import android.widget.LinearLayout; -import android.widget.TextView; -import android.widget.Toast; - -import com.habitrpg.android.habitica.R; -import com.habitrpg.android.habitica.ui.helpers.NavbarUtils; - -import java.util.List; - -/* - * 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 BottomBar extends LinearLayout implements View.OnClickListener, View.OnLongClickListener { - private static final String STATE_CURRENT_SELECTED_TAB = "STATE_CURRENT_SELECTED_TAB"; - private static final float DEFAULT_INACTIVE_SHIFTING_TAB_ALPHA = 0.6f; - // Behaviors - private static final int BEHAVIOR_NONE = 0; - private static final int BEHAVIOR_SHIFTING = 1; - private static final int BEHAVIOR_SHY = 2; - private static final int BEHAVIOR_DRAW_UNDER_NAV = 4; - private static final int BEHAVIOR_ICONS_ONLY = 8; - - private BatchTabPropertyApplier batchPropertyApplier; - private int primaryColor; - private int screenWidth; - private int tenDp; - private int maxFixedItemWidth; - - // XML Attributes - private int tabXmlResource; - private boolean isTabletMode; - private int behaviors; - private float inActiveTabAlpha; - private float activeTabAlpha; - private int inActiveTabColor; - private int activeTabColor; - private int badgeBackgroundColor; - private boolean hideBadgeWhenActive; - private boolean longPressHintsEnabled; - private int titleTextAppearance; - private Typeface titleTypeFace; - private boolean showShadow; - private float shadowElevation; - private View shadowView; - - private View backgroundOverlay; - private ViewGroup outerContainer; - private ViewGroup tabContainer; - - private int defaultBackgroundColor = Color.WHITE; - private int currentBackgroundColor; - private int currentTabPosition; - - private int inActiveShiftingItemWidth; - private int activeShiftingItemWidth; - - @Nullable - private TabSelectionInterceptor tabSelectionInterceptor; - - @Nullable - private OnTabSelectListener onTabSelectListener; - - @Nullable - private OnTabReselectListener onTabReselectListener; - - private boolean isComingFromRestoredState; - private boolean ignoreTabReselectionListener; - - private ShySettings shySettings; - private boolean shyHeightAlreadyCalculated; - private boolean navBarAccountedHeightCalculated; - - private BottomBarTab[] currentTabs; - - public BottomBar(Context context) { - this(context, null); - } - - public BottomBar(Context context, AttributeSet attrs) { - this(context, attrs, 0); - } - - public BottomBar(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - init(context, attrs, defStyleAttr, 0); - } - - @RequiresApi(Build.VERSION_CODES.LOLLIPOP) - public BottomBar(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { - super(context, attrs, defStyleAttr, defStyleRes); - init(context, attrs, defStyleAttr, defStyleRes); - } - - private void init(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { - batchPropertyApplier = new BatchTabPropertyApplier(this); - - populateAttributes(context, attrs, defStyleAttr, defStyleRes); - initializeViews(); - determineInitialBackgroundColor(); - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - init21(context); - } - - if (tabXmlResource != 0) { - setItems(tabXmlResource); - } - } - - @Override - protected void onAttachedToWindow() { - super.onAttachedToWindow(); - - // This is so that in Pre-Lollipop devices there is a shadow BUT without pushing the content - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP && showShadow && shadowView != null) { - shadowView.setVisibility(VISIBLE); - ViewGroup.LayoutParams params = getLayoutParams(); - if (params instanceof MarginLayoutParams) { - MarginLayoutParams layoutParams = (MarginLayoutParams) params; - final int shadowHeight = getResources().getDimensionPixelSize(R.dimen.bb_fake_shadow_height); - - layoutParams.setMargins(layoutParams.leftMargin, - layoutParams.topMargin - shadowHeight, - layoutParams.rightMargin, - layoutParams.bottomMargin); - setLayoutParams(params); - } - } - } - - @RequiresApi(Build.VERSION_CODES.LOLLIPOP) - private void init21(Context context) { - if (showShadow) { - shadowElevation = getElevation(); - shadowElevation = shadowElevation > 0 - ? shadowElevation - : getResources().getDimensionPixelSize(R.dimen.bb_default_elevation); - setElevation(MiscUtils.dpToPixel(context, shadowElevation)); - setOutlineProvider(ViewOutlineProvider.BOUNDS); - } - } - - private void populateAttributes(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { - primaryColor = MiscUtils.getColor(getContext(), R.attr.colorPrimary); - screenWidth = MiscUtils.getScreenWidth(getContext()); - tenDp = MiscUtils.dpToPixel(getContext(), 10); - maxFixedItemWidth = MiscUtils.dpToPixel(getContext(), 168); - - TypedArray ta = context.getTheme() - .obtainStyledAttributes(attrs, R.styleable.BottomBar, defStyleAttr, defStyleRes); - - try { - tabXmlResource = ta.getResourceId(R.styleable.BottomBar_bb_tabXmlResource, 0); - isTabletMode = ta.getBoolean(R.styleable.BottomBar_bb_tabletMode, false); - behaviors = ta.getInteger(R.styleable.BottomBar_bb_behavior, BEHAVIOR_NONE); - inActiveTabAlpha = ta.getFloat(R.styleable.BottomBar_bb_inActiveTabAlpha, - isShiftingMode() ? DEFAULT_INACTIVE_SHIFTING_TAB_ALPHA : 1); - activeTabAlpha = ta.getFloat(R.styleable.BottomBar_bb_activeTabAlpha, 1); - - @ColorInt - int defaultInActiveColor = isShiftingMode() ? - Color.WHITE : ContextCompat.getColor(context, R.color.bb_inActiveBottomBarItemColor); - int defaultActiveColor = isShiftingMode() ? Color.WHITE : primaryColor; - - longPressHintsEnabled = ta.getBoolean(R.styleable.BottomBar_bb_longPressHintsEnabled, true); - inActiveTabColor = ta.getColor(R.styleable.BottomBar_bb_inActiveTabColor, defaultInActiveColor); - activeTabColor = ta.getColor(R.styleable.BottomBar_bb_activeTabColor, defaultActiveColor); - badgeBackgroundColor = ta.getColor(R.styleable.BottomBar_bb_badgeBackgroundColor, Color.RED); - hideBadgeWhenActive = ta.getBoolean(R.styleable.BottomBar_bb_badgesHideWhenActive, true); - titleTextAppearance = ta.getResourceId(R.styleable.BottomBar_bb_titleTextAppearance, 0); - titleTypeFace = getTypeFaceFromAsset(ta.getString(R.styleable.BottomBar_bb_titleTypeFace)); - showShadow = ta.getBoolean(R.styleable.BottomBar_bb_showShadow, true); - } finally { - ta.recycle(); - } - } - - private boolean isShiftingMode() { - return !isTabletMode && hasBehavior(BEHAVIOR_SHIFTING); - } - - private boolean drawUnderNav() { - return !isTabletMode - && hasBehavior(BEHAVIOR_DRAW_UNDER_NAV) - && NavbarUtils.INSTANCE.shouldDrawBehindNavbar(getContext()); - } - - boolean isShy() { - return !isTabletMode && hasBehavior(BEHAVIOR_SHY); - } - - boolean isShyHeightAlreadyCalculated() { - return shyHeightAlreadyCalculated; - } - - private boolean isIconsOnlyMode() { - return !isTabletMode && hasBehavior(BEHAVIOR_ICONS_ONLY); - } - - private boolean hasBehavior(int behavior) { - return (behaviors | behavior) == behaviors; - } - - private Typeface getTypeFaceFromAsset(String fontPath) { - if (fontPath != null) { - return Typeface.createFromAsset( - getContext().getAssets(), fontPath); - } - - return null; - } - - private void initializeViews() { - int width = isTabletMode ? LayoutParams.WRAP_CONTENT : LayoutParams.MATCH_PARENT; - int height = isTabletMode ? LayoutParams.MATCH_PARENT : LayoutParams.WRAP_CONTENT; - LayoutParams params = new LayoutParams(width, height); - - setLayoutParams(params); - setOrientation(isTabletMode ? HORIZONTAL : VERTICAL); - - View rootView = inflate(getContext(), - isTabletMode ? R.layout.bb_bottom_bar_item_container_tablet : R.layout.bb_bottom_bar_item_container, this); - rootView.setLayoutParams(params); - - backgroundOverlay = rootView.findViewById(R.id.bb_bottom_bar_background_overlay); - outerContainer = rootView.findViewById(R.id.bb_bottom_bar_outer_container); - tabContainer = rootView.findViewById(R.id.bb_bottom_bar_item_container); - shadowView = findViewById(R.id.bb_bottom_bar_shadow); - } - - private void determineInitialBackgroundColor() { - if (isShiftingMode()) { - defaultBackgroundColor = primaryColor; - } - - Drawable userDefinedBackground = getBackground(); - - boolean userHasDefinedBackgroundColor = userDefinedBackground != null - && userDefinedBackground instanceof ColorDrawable; - - if (userHasDefinedBackgroundColor) { - defaultBackgroundColor = ((ColorDrawable) userDefinedBackground).getColor(); - setBackgroundColor(Color.TRANSPARENT); - } - } - - /** - * Set the items for the BottomBar from XML Resource. - */ - public void setItems(@XmlRes int xmlRes) { - setItems(xmlRes, null); - } - - /** - * Set the item for the BottomBar from XML Resource with a default configuration - * for each tab. - */ - public void setItems(@XmlRes int xmlRes, BottomBarTab.Config defaultTabConfig) { - if (xmlRes == 0) { - throw new RuntimeException("No items specified for the BottomBar!"); - } - - if (defaultTabConfig == null) { - defaultTabConfig = getTabConfig(); - } - - TabParser parser = new TabParser(getContext(), defaultTabConfig, xmlRes); - updateItems(parser.parseTabs()); - } - - private BottomBarTab.Config getTabConfig() { - return new BottomBarTab.Config.Builder() - .inActiveTabAlpha(inActiveTabAlpha) - .activeTabAlpha(activeTabAlpha) - .inActiveTabColor(inActiveTabColor) - .activeTabColor(activeTabColor) - .barColorWhenSelected(defaultBackgroundColor) - .badgeBackgroundColor(badgeBackgroundColor) - .hideBadgeWhenSelected(hideBadgeWhenActive) - .titleTextAppearance(titleTextAppearance) - .titleTypeFace(titleTypeFace) - .build(); - } - - private void updateItems(final List bottomBarItems) { - tabContainer.removeAllViews(); - - int index = 0; - int biggestWidth = 0; - - BottomBarTab[] viewsToAdd = new BottomBarTab[bottomBarItems.size()]; - - for (BottomBarTab bottomBarTab : bottomBarItems) { - BottomBarTab.Type type; - - if (isShiftingMode()) { - type = BottomBarTab.Type.SHIFTING; - } else if (isTabletMode) { - type = BottomBarTab.Type.TABLET; - } else { - type = BottomBarTab.Type.FIXED; - } - - if (isIconsOnlyMode()) { - bottomBarTab.setIsTitleless(true); - } - - bottomBarTab.setType(type); - bottomBarTab.prepareLayout(); - - if (index == currentTabPosition) { - bottomBarTab.select(false); - - handleBackgroundColorChange(bottomBarTab, false); - } else { - bottomBarTab.deselect(false); - } - - if (!isTabletMode) { - if (bottomBarTab.getWidth() > biggestWidth) { - biggestWidth = bottomBarTab.getWidth(); - } - - viewsToAdd[index] = bottomBarTab; - } else { - tabContainer.addView(bottomBarTab); - } - - bottomBarTab.setOnClickListener(this); - bottomBarTab.setOnLongClickListener(this); - index++; - } - - currentTabs = viewsToAdd; - - if (!isTabletMode) { - resizeTabsToCorrectSizes(viewsToAdd); - } - } - - private void resizeTabsToCorrectSizes(BottomBarTab[] tabsToAdd) { - int viewWidth = MiscUtils.pixelToDp(getContext(), getWidth()); - - if (viewWidth <= 0 || viewWidth > screenWidth) { - viewWidth = screenWidth; - } - - int proposedItemWidth = Math.min( - MiscUtils.dpToPixel(getContext(), viewWidth / tabsToAdd.length), - maxFixedItemWidth - ); - - inActiveShiftingItemWidth = (int) (proposedItemWidth * 0.9); - activeShiftingItemWidth = (int) (proposedItemWidth + (proposedItemWidth * ((tabsToAdd.length - 1) * 0.1))); - int height = Math.round(getContext().getResources() - .getDimension(R.dimen.bb_height)); - - for (BottomBarTab tabView : tabsToAdd) { - ViewGroup.LayoutParams params = tabView.getLayoutParams(); - params.height = height; - - if (isShiftingMode()) { - if (tabView.isActive()) { - params.width = activeShiftingItemWidth; - } else { - params.width = inActiveShiftingItemWidth; - } - } else { - params.width = proposedItemWidth; - } - - if (tabView.getParent() == null) { - tabContainer.addView(tabView); - } - - tabView.setLayoutParams(params); - } - } - - /** - * Returns the settings specific for a shy BottomBar. - * - * @throws UnsupportedOperationException, if this BottomBar is not shy. - */ - public ShySettings getShySettings() { - if (!isShy()) { - Log.e("BottomBar", "Tried to get shy settings for a BottomBar " + - "that is not shy."); - } - - if (shySettings == null) { - shySettings = new ShySettings(this); - } - - return shySettings; - } - - /** - * Set a listener that gets fired when the selected {@link BottomBarTab} is about to change. - * - * @param interceptor a listener for potentially interrupting changes in tab selection. - */ - public void setTabSelectionInterceptor(@NonNull TabSelectionInterceptor interceptor) { - tabSelectionInterceptor = interceptor; - } - - /** - * Removes the current {@link TabSelectionInterceptor} listener - */ - public void removeOverrideTabSelectionListener() { - tabSelectionInterceptor = null; - } - - /** - * Set a listener that gets fired when the selected {@link BottomBarTab} changes. - *

- * Note: Will be immediately called for the currently selected tab - * once when set. - * - * @param listener a listener for monitoring changes in tab selection. - */ - public void setOnTabSelectListener(@NonNull OnTabSelectListener listener) { - setOnTabSelectListener(listener, true); - } - - /** - * Set a listener that gets fired when the selected {@link BottomBarTab} changes. - *

- * If {@code shouldFireInitially} is set to false, this listener isn't fired straight away - * it's set, but you'll get all events normally for consecutive tab selection changes. - * - * @param listener a listener for monitoring changes in tab selection. - * @param shouldFireInitially whether the listener should be fired the first time it's set. - */ - public void setOnTabSelectListener(@NonNull OnTabSelectListener listener, boolean shouldFireInitially) { - onTabSelectListener = listener; - - if (shouldFireInitially && getTabCount() > 0) { - listener.onTabSelected(getCurrentTabId()); - } - } - - /** - * Removes the current {@link OnTabSelectListener} listener - */ - public void removeOnTabSelectListener() { - onTabSelectListener = null; - } - - /** - * Set a listener that gets fired when a currently selected {@link BottomBarTab} is clicked. - * - * @param listener a listener for handling tab reselections. - */ - public void setOnTabReselectListener(@NonNull OnTabReselectListener listener) { - onTabReselectListener = listener; - } - - /** - * Removes the current {@link OnTabReselectListener} listener - */ - public void removeOnTabReselectListener() { - onTabReselectListener = null; - } - - /** - * Set the default selected to be the tab with the corresponding tab id. - * By default, the first tab in the container is the default tab. - */ - public void setDefaultTab(@IdRes int defaultTabId) { - int defaultTabPosition = findPositionForTabWithId(defaultTabId); - setDefaultTabPosition(defaultTabPosition); - } - - /** - * Sets the default tab for this BottomBar that is shown until the user changes - * the selection. - * - * @param defaultTabPosition the default tab position. - */ - public void setDefaultTabPosition(int defaultTabPosition) { - if (isComingFromRestoredState) return; - - selectTabAtPosition(defaultTabPosition); - } - - /** - * Select the tab with the corresponding id. - */ - public void selectTabWithId(@IdRes int tabResId) { - int tabPosition = findPositionForTabWithId(tabResId); - selectTabAtPosition(tabPosition); - } - - /** - * Select a tab at the specified position. - * - * @param position the position to select. - */ - public void selectTabAtPosition(int position) { - selectTabAtPosition(position, false); - } - - /** - * Select a tab at the specified position. - * - * @param position the position to select. - * @param animate should the tab change be animated or not. - */ - public void selectTabAtPosition(int position, boolean animate) { - if (position > getTabCount() - 1 || position < 0) { - throw new IndexOutOfBoundsException("Can't select tab at position " + - position + ". This BottomBar has no items at that position."); - } - - BottomBarTab oldTab = getCurrentTab(); - BottomBarTab newTab = getTabAtPosition(position); - - oldTab.deselect(animate); - newTab.select(animate); - - updateSelectedTab(position); - shiftingMagic(oldTab, newTab, animate); - handleBackgroundColorChange(newTab, animate); - } - - public int getTabCount() { - return tabContainer.getChildCount(); - } - - /** - * Get the currently selected tab. - */ - public BottomBarTab getCurrentTab() { - return getTabAtPosition(getCurrentTabPosition()); - } - - /** - * Get the tab at the specified position. - */ - public BottomBarTab getTabAtPosition(int position) { - View child = tabContainer.getChildAt(position); - - if (child instanceof BadgeContainer) { - return findTabInLayout((BadgeContainer) child); - } - - return (BottomBarTab) child; - } - - /** - * Get the resource id for the currently selected tab. - */ - @IdRes - public int getCurrentTabId() { - return getCurrentTab().getId(); - } - - /** - * Get the currently selected tab position. - */ - public int getCurrentTabPosition() { - return currentTabPosition; - } - - /** - * Find the tabs' position in the container by id. - */ - public int findPositionForTabWithId(@IdRes int tabId) { - return getTabWithId(tabId).getIndexInTabContainer(); - } - - /** - * Find a BottomBarTab with the corresponding id. - */ - public BottomBarTab getTabWithId(@IdRes int tabId) { - return (BottomBarTab) tabContainer.findViewById(tabId); - } - - /** - * Controls whether the long pressed tab title should be displayed in - * a helpful Toast if the title is not currently visible. - * - * @param enabled true if toasts should be shown to indicate the title - * of a long pressed tab, false otherwise. - */ - public void setLongPressHintsEnabled(boolean enabled) { - longPressHintsEnabled = enabled; - } - - /** - * Set alpha value used for inactive BottomBarTabs. - */ - public void setInActiveTabAlpha(float alpha) { - inActiveTabAlpha = alpha; - - batchPropertyApplier.applyToAllTabs(new BatchTabPropertyApplier.TabPropertyUpdater() { - @Override - public void update(BottomBarTab tab) { - tab.setInActiveAlpha(inActiveTabAlpha); - } - }); - } - - /** - * Set alpha value used for active BottomBarTabs. - */ - public void setActiveTabAlpha(float alpha) { - activeTabAlpha = alpha; - - batchPropertyApplier.applyToAllTabs(new BatchTabPropertyApplier.TabPropertyUpdater() { - @Override - public void update(BottomBarTab tab) { - tab.setActiveAlpha(activeTabAlpha); - } - }); - } - - public void setInActiveTabColor(@ColorInt int color) { - inActiveTabColor = color; - - batchPropertyApplier.applyToAllTabs(new BatchTabPropertyApplier.TabPropertyUpdater() { - @Override - public void update(BottomBarTab tab) { - tab.setInActiveColor(inActiveTabColor); - } - }); - } - - /** - * Set active color used for selected BottomBarTabs. - */ - public void setActiveTabColor(@ColorInt int color) { - activeTabColor = color; - - batchPropertyApplier.applyToAllTabs(new BatchTabPropertyApplier.TabPropertyUpdater() { - @Override - public void update(BottomBarTab tab) { - tab.setActiveColor(activeTabColor); - } - }); - } - - /** - * Set background color for the badge. - */ - public void setBadgeBackgroundColor(@ColorInt int color) { - badgeBackgroundColor = color; - - batchPropertyApplier.applyToAllTabs(new BatchTabPropertyApplier.TabPropertyUpdater() { - @Override - public void update(BottomBarTab tab) { - tab.setBadgeBackgroundColor(badgeBackgroundColor); - } - }); - } - - /** - * Controls whether the badge (if any) for active tabs - * should be hidden or not. - */ - public void setBadgesHideWhenActive(final boolean hideWhenSelected) { - hideBadgeWhenActive = hideWhenSelected; - batchPropertyApplier.applyToAllTabs(new BatchTabPropertyApplier.TabPropertyUpdater() { - @Override - public void update(BottomBarTab tab) { - tab.setBadgeHidesWhenActive(hideWhenSelected); - } - }); - } - - /** - * Set custom text apperance for all BottomBarTabs. - */ - public void setTabTitleTextAppearance(int textAppearance) { - titleTextAppearance = textAppearance; - - batchPropertyApplier.applyToAllTabs(new BatchTabPropertyApplier.TabPropertyUpdater() { - @Override - public void update(BottomBarTab tab) { - tab.setTitleTextAppearance(titleTextAppearance); - } - }); - } - - /** - * Set a custom typeface for all tab's titles. - * - * @param fontPath path for your custom font file, such as fonts/MySuperDuperFont.ttf. - * In that case your font path would look like src/main/assets/fonts/MySuperDuperFont.ttf, - * but you only need to provide fonts/MySuperDuperFont.ttf, as the asset folder - * will be auto-filled for you. - */ - public void setTabTitleTypeface(String fontPath) { - Typeface actualTypeface = getTypeFaceFromAsset(fontPath); - setTabTitleTypeface(actualTypeface); - } - - /** - * Set a custom typeface for all tab's titles. - */ - public void setTabTitleTypeface(Typeface typeface) { - titleTypeFace = typeface; - - batchPropertyApplier.applyToAllTabs(new BatchTabPropertyApplier.TabPropertyUpdater() { - @Override - public void update(BottomBarTab tab) { - tab.setTitleTypeface(titleTypeFace); - } - }); - } - - @Override - protected void onLayout(boolean changed, int left, int top, int right, int bottom) { - super.onLayout(changed, left, top, right, bottom); - - if (changed) { - if (!isTabletMode) { - resizeTabsToCorrectSizes(currentTabs); - } - - updateTitleBottomPadding(); - - if (isShy()) { - initializeShyBehavior(); - } - - if (drawUnderNav()) { - resizeForDrawingUnderNavbar(); - } - } - } - - private void updateTitleBottomPadding() { - if (isIconsOnlyMode()) { - return; - } - - int tabCount = getTabCount(); - - if (tabContainer == null || tabCount == 0 || !isShiftingMode()) { - return; - } - - for (int i = 0; i < tabCount; i++) { - BottomBarTab tab = getTabAtPosition(i); - TextView title = tab.getTitleView(); - - if (title == null) { - continue; - } - - int baseline = title.getBaseline(); - int height = title.getHeight(); - int paddingInsideTitle = height - baseline; - int missingPadding = tenDp - paddingInsideTitle; - - if (missingPadding > 0) { - title.setPadding(title.getPaddingLeft(), title.getPaddingTop(), - title.getPaddingRight(), missingPadding + title.getPaddingBottom()); - } - } - } - - private void initializeShyBehavior() { - ViewParent parent = getParent(); - - boolean hasAbusiveParent = parent != null - && parent instanceof CoordinatorLayout; - - if (!hasAbusiveParent) { - throw new RuntimeException("In order to have shy behavior, the " + - "BottomBar must be a direct child of a CoordinatorLayout."); - } - - if (!shyHeightAlreadyCalculated) { - int height = getHeight(); - - if (height != 0) { - updateShyHeight(height); - getShySettings().shyHeightCalculated(); - shyHeightAlreadyCalculated = true; - } - } - } - - private void updateShyHeight(int height) { - ((CoordinatorLayout.LayoutParams) getLayoutParams()) - .setBehavior(new BottomNavigationBehavior(height, 0, false)); - } - - private void resizeForDrawingUnderNavbar() { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { - int currentHeight = getHeight(); - - if (currentHeight != 0 && !navBarAccountedHeightCalculated) { - navBarAccountedHeightCalculated = true; - tabContainer.getLayoutParams().height = currentHeight; - - int navbarHeight = NavbarUtils.INSTANCE.getNavbarHeight(getContext()); - int finalHeight = currentHeight + navbarHeight; - getLayoutParams().height = finalHeight; - - if (isShy()) { - updateShyHeight(finalHeight); - } - } - } - } - - @Override - public Parcelable onSaveInstanceState() { - Bundle bundle = saveState(); - bundle.putParcelable("superstate", super.onSaveInstanceState()); - return bundle; - } - - @VisibleForTesting - Bundle saveState() { - Bundle outState = new Bundle(); - outState.putInt(STATE_CURRENT_SELECTED_TAB, currentTabPosition); - - 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) { - if (savedInstanceState != null) { - isComingFromRestoredState = true; - ignoreTabReselectionListener = true; - - int restoredPosition = savedInstanceState.getInt(STATE_CURRENT_SELECTED_TAB, currentTabPosition); - selectTabAtPosition(restoredPosition, false); - } - } - - @Override - public void onClick(View target) { - if (!(target instanceof BottomBarTab)) return; - handleClick((BottomBarTab) target); - } - - @Override - public boolean onLongClick(View target) { - return !(target instanceof BottomBarTab) || handleLongClick((BottomBarTab) target); - } - - private BottomBarTab findTabInLayout(ViewGroup child) { - for (int i = 0; i < child.getChildCount(); i++) { - View candidate = child.getChildAt(i); - - if (candidate instanceof BottomBarTab) { - return (BottomBarTab) candidate; - } - } - - return null; - } - - private void handleClick(BottomBarTab newTab) { - BottomBarTab oldTab = getCurrentTab(); - - if (tabSelectionInterceptor != null - && tabSelectionInterceptor.shouldInterceptTabSelection(oldTab.getId(), newTab.getId())) { - return; - } - - oldTab.deselect(true); - newTab.select(true); - - shiftingMagic(oldTab, newTab, true); - handleBackgroundColorChange(newTab, true); - updateSelectedTab(newTab.getIndexInTabContainer()); - } - - private boolean handleLongClick(BottomBarTab longClickedTab) { - boolean areInactiveTitlesHidden = isShiftingMode() || isTabletMode; - boolean isClickedTitleHidden = !longClickedTab.isActive(); - boolean shouldShowHint = areInactiveTitlesHidden - && isClickedTitleHidden - && longPressHintsEnabled; - - if (shouldShowHint) { - Toast.makeText(getContext(), longClickedTab.getTitle(), Toast.LENGTH_SHORT) - .show(); - } - - return true; - } - - private void updateSelectedTab(int newPosition) { - int newTabId = getTabAtPosition(newPosition).getId(); - - if (newPosition != currentTabPosition) { - if (onTabSelectListener != null) { - onTabSelectListener.onTabSelected(newTabId); - } - } else if (onTabReselectListener != null && !ignoreTabReselectionListener) { - onTabReselectListener.onTabReSelected(newTabId); - } - - currentTabPosition = newPosition; - - if (ignoreTabReselectionListener) { - ignoreTabReselectionListener = false; - } - } - - private void shiftingMagic(BottomBarTab oldTab, BottomBarTab newTab, boolean animate) { - if (isShiftingMode()) { - oldTab.updateWidth(inActiveShiftingItemWidth, animate); - newTab.updateWidth(activeShiftingItemWidth, animate); - } - } - - private void handleBackgroundColorChange(BottomBarTab tab, boolean animate) { - int newColor = tab.getBarColorWhenSelected(); - - if (currentBackgroundColor == newColor) { - return; - } - - if (!animate) { - outerContainer.setBackgroundColor(newColor); - return; - } - - View clickedView = tab; - - if (tab.hasActiveBadge()) { - clickedView = tab.getOuterView(); - } - - animateBGColorChange(clickedView, newColor); - currentBackgroundColor = newColor; - } - - private void animateBGColorChange(View clickedView, final int newColor) { - prepareForBackgroundColorAnimation(newColor); - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - if (!outerContainer.isAttachedToWindow()) { - return; - } - - backgroundCircularRevealAnimation(clickedView, newColor); - } else { - backgroundCrossfadeAnimation(newColor); - } - } - - private void prepareForBackgroundColorAnimation(int newColor) { - outerContainer.clearAnimation(); - backgroundOverlay.clearAnimation(); - - backgroundOverlay.setBackgroundColor(newColor); - backgroundOverlay.setVisibility(View.VISIBLE); - } - - @TargetApi(Build.VERSION_CODES.LOLLIPOP) - private void backgroundCircularRevealAnimation(View clickedView, final int newColor) { - int centerX = (int) (ViewCompat.getX(clickedView) + (clickedView.getMeasuredWidth() / 2)); - int yOffset = isTabletMode ? (int) ViewCompat.getY(clickedView) : 0; - int centerY = yOffset + clickedView.getMeasuredHeight() / 2; - int startRadius = 0; - int finalRadius = isTabletMode ? outerContainer.getHeight() : outerContainer.getWidth(); - - Animator animator = ViewAnimationUtils.createCircularReveal( - backgroundOverlay, - centerX, - centerY, - startRadius, - finalRadius - ); - - if (isTabletMode) { - animator.setDuration(500); - } - - animator.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - onEnd(); - } - - @Override - public void onAnimationCancel(Animator animation) { - onEnd(); - } - - private void onEnd() { - outerContainer.setBackgroundColor(newColor); - backgroundOverlay.setVisibility(View.INVISIBLE); - ViewCompat.setAlpha(backgroundOverlay, 1); - } - }); - - animator.start(); - } - - private void backgroundCrossfadeAnimation(final int newColor) { - ViewCompat.setAlpha(backgroundOverlay, 0); - ViewCompat.animate(backgroundOverlay) - .alpha(1) - .setListener(new ViewPropertyAnimatorListenerAdapter() { - @Override - public void onAnimationEnd(View view) { - onEnd(); - } - - @Override - public void onAnimationCancel(View view) { - onEnd(); - } - - private void onEnd() { - outerContainer.setBackgroundColor(newColor); - backgroundOverlay.setVisibility(View.INVISIBLE); - ViewCompat.setAlpha(backgroundOverlay, 1); - } - }) - .start(); - } -} diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/bottombar/BottomBarBadge.java b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/bottombar/BottomBarBadge.java deleted file mode 100644 index d7aa57294..000000000 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/bottombar/BottomBarBadge.java +++ /dev/null @@ -1,172 +0,0 @@ -package com.habitrpg.android.habitica.ui.views.bottombar; - -import android.content.Context; -import android.graphics.drawable.Drawable; -import android.graphics.drawable.ShapeDrawable; -import android.os.Build; -import android.view.Gravity; -import android.view.ViewGroup; -import android.view.ViewTreeObserver; -import android.widget.TextView; - -import com.habitrpg.android.habitica.R; - -import androidx.appcompat.widget.AppCompatImageView; -import androidx.appcompat.widget.AppCompatTextView; -import androidx.core.view.ViewCompat; - -/* - * BottomBar library for Android - * Copyright (c) 2016 Iiro Krankka (http://github.com/roughike). - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -class BottomBarBadge extends AppCompatTextView { - private int count; - private boolean isVisible = false; - - BottomBarBadge(Context context) { - super(context); - } - - /** - * Set the unread / new item / whatever count for this Badge. - * - * @param count the value this Badge should show. - */ - void setCount(int count) { - this.count = count; - setText(String.valueOf(count)); - } - - /** - * Get the currently showing count for this Badge. - * - * @return current count for the Badge. - */ - int getCount() { - return count; - } - - /** - * Shows the badge with a neat little scale animation. - */ - void show() { - isVisible = true; - ViewCompat.animate(this) - .setDuration(150) - .alpha(1) - .scaleX(1) - .scaleY(1) - .start(); - } - - /** - * Hides the badge with a neat little scale animation. - */ - void hide() { - isVisible = false; - ViewCompat.animate(this) - .setDuration(150) - .alpha(0) - .scaleX(0) - .scaleY(0) - .start(); - } - - /** - * Is this badge currently visible? - * - * @return true is this badge is visible, otherwise false. - */ - boolean isVisible() { - return isVisible; - } - - void attachToTab(BottomBarTab tab, int backgroundColor) { - ViewGroup.LayoutParams params = new ViewGroup.LayoutParams( - ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); - - setLayoutParams(params); - setGravity(Gravity.CENTER); - MiscUtils.setTextAppearance(this, R.style.BB_BottomBarBadge_Text); - - setColoredCircleBackground(backgroundColor); - wrapTabAndBadgeInSameContainer(tab); - } - - void setColoredCircleBackground(int circleColor) { - int innerPadding = MiscUtils.dpToPixel(getContext(), 1); - ShapeDrawable backgroundCircle = BadgeCircle.make(innerPadding * 3, circleColor); - setPadding(innerPadding, innerPadding, innerPadding, innerPadding); - setBackgroundCompat(backgroundCircle); - } - - private void wrapTabAndBadgeInSameContainer(final BottomBarTab tab) { - ViewGroup tabContainer = (ViewGroup) tab.getParent(); - tabContainer.removeView(tab); - - final BadgeContainer badgeContainer = new BadgeContainer(getContext()); - badgeContainer.setLayoutParams(new ViewGroup.LayoutParams( - ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)); - - badgeContainer.addView(tab); - badgeContainer.addView(this); - - tabContainer.addView(badgeContainer, tab.getIndexInTabContainer()); - - badgeContainer.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { - @SuppressWarnings("deprecation") - @Override - public void onGlobalLayout() { - badgeContainer.getViewTreeObserver().removeGlobalOnLayoutListener(this); - adjustPositionAndSize(tab); - } - }); - } - - void removeFromTab(BottomBarTab tab) { - BadgeContainer badgeAndTabContainer = (BadgeContainer) getParent(); - ViewGroup originalTabContainer = (ViewGroup) badgeAndTabContainer.getParent(); - - badgeAndTabContainer.removeView(tab); - originalTabContainer.removeView(badgeAndTabContainer); - originalTabContainer.addView(tab, tab.getIndexInTabContainer()); - } - - void adjustPositionAndSize(BottomBarTab tab) { - AppCompatImageView iconView = tab.getIconView(); - ViewGroup.LayoutParams params = getLayoutParams(); - - int size = Math.max(getWidth(), getHeight()); - float xOffset = (float) (iconView.getWidth() / 1.25); - - setX(iconView.getX() + xOffset); - setTranslationY(10); - - if (params.width != size || params.height != size) { - params.width = size; - params.height = size; - setLayoutParams(params); - } - } - - @SuppressWarnings("deprecation") - private void setBackgroundCompat(Drawable background) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { - setBackground(background); - } else { - setBackgroundDrawable(background); - } - } -} diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/bottombar/BottomBarTab.java b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/bottombar/BottomBarTab.java deleted file mode 100644 index 88e9d8eaa..000000000 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/bottombar/BottomBarTab.java +++ /dev/null @@ -1,730 +0,0 @@ -package com.habitrpg.android.habitica.ui.views.bottombar; - -import android.animation.ArgbEvaluator; -import android.animation.ValueAnimator; -import android.content.Context; -import android.graphics.Typeface; -import android.os.Build; -import android.os.Bundle; -import android.os.Parcelable; -import androidx.annotation.ColorInt; -import androidx.annotation.NonNull; -import androidx.annotation.VisibleForTesting; -import androidx.appcompat.widget.AppCompatImageView; -import androidx.core.view.ViewCompat; -import androidx.core.view.ViewPropertyAnimatorCompat; - -import android.view.Gravity; -import android.view.ViewGroup; -import android.widget.LinearLayout; -import android.widget.TextView; - -import com.habitrpg.android.habitica.R; - -/* - * BottomBar library for Android - * Copyright (c) 2016 Iiro Krankka (http://github.com/roughike). - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -public class BottomBarTab extends LinearLayout { - @VisibleForTesting - static final String STATE_BADGE_COUNT = "STATE_BADGE_COUNT_FOR_TAB_"; - - private static final long ANIMATION_DURATION = 150; - private static final float ACTIVE_TITLE_SCALE = 1; - private static final float INACTIVE_FIXED_TITLE_SCALE = 0.86f; - private static final float ACTIVE_SHIFTING_TITLELESS_ICON_SCALE = 1.24f; - private static final float INACTIVE_SHIFTING_TITLELESS_ICON_SCALE = 1f; - - private final int sixDps; - private final int eightDps; - private final int sixteenDps; - - @VisibleForTesting - BottomBarBadge badge; - - private Type type = Type.FIXED; - private boolean isTitleless; - private int iconResId; - private String title; - private float inActiveAlpha; - private float activeAlpha; - private int inActiveColor; - private int activeColor; - private int barColorWhenSelected; - private int badgeBackgroundColor; - private boolean badgeHidesWhenActive; - private AppCompatImageView iconView; - private TextView titleView; - private boolean isActive; - private int indexInContainer; - private int titleTextAppearanceResId; - private Typeface titleTypeFace; - - BottomBarTab(Context context) { - super(context); - - sixDps = MiscUtils.dpToPixel(context, 6); - eightDps = MiscUtils.dpToPixel(context, 8); - sixteenDps = MiscUtils.dpToPixel(context, 16); - } - - void setConfig(@NonNull Config config) { - setInActiveAlpha(config.inActiveTabAlpha); - setActiveAlpha(config.activeTabAlpha); - setInActiveColor(config.inActiveTabColor); - setActiveColor(config.activeTabColor); - setBarColorWhenSelected(config.barColorWhenSelected); - setBadgeBackgroundColor(config.badgeBackgroundColor); - setBadgeHidesWhenActive(config.badgeHidesWhenSelected); - setTitleTextAppearance(config.titleTextAppearance); - setTitleTypeface(config.titleTypeFace); - } - - void prepareLayout() { - inflate(getContext(), getLayoutResource(), this); - setOrientation(VERTICAL); - setGravity(isTitleless? Gravity.CENTER : Gravity.CENTER_HORIZONTAL); - setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT)); - setBackgroundResource(MiscUtils.getDrawableRes(getContext(), R.attr.selectableItemBackgroundBorderless)); - - iconView = findViewById(R.id.bb_bottom_bar_icon); - iconView.setImageResource(iconResId); - - if (type != Type.TABLET && !isTitleless) { - titleView = findViewById(R.id.bb_bottom_bar_title); - titleView.setVisibility(VISIBLE); - - if (type == Type.SHIFTING) { - findViewById(R.id.spacer).setVisibility(VISIBLE); - } - - updateTitle(); - } - - updateCustomTextAppearance(); - updateCustomTypeface(); - } - - @VisibleForTesting - int getLayoutResource() { - int layoutResource; - switch (type) { - case FIXED: - layoutResource = R.layout.bb_bottom_bar_item_fixed; - break; - case SHIFTING: - layoutResource = R.layout.bb_bottom_bar_item_shifting; - break; - case TABLET: - layoutResource = R.layout.bb_bottom_bar_item_fixed_tablet; - break; - default: - // should never happen - throw new RuntimeException("Unknown BottomBarTab type."); - } - return layoutResource; - } - - private void updateTitle() { - if (titleView != null) { - titleView.setText(title); - } - } - - @SuppressWarnings("deprecation") - private void updateCustomTextAppearance() { - if (titleView == null || titleTextAppearanceResId == 0) { - return; - } - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - titleView.setTextAppearance(titleTextAppearanceResId); - } else { - titleView.setTextAppearance(getContext(), titleTextAppearanceResId); - } - - titleView.setTag(R.id.bb_bottom_bar_appearance_id, titleTextAppearanceResId); - } - - private void updateCustomTypeface() { - if (titleTypeFace != null && titleView != null) { - titleView.setTypeface(titleTypeFace); - } - } - - Type getType() { - return type; - } - - void setType(Type type) { - this.type = type; - } - - boolean isTitleless() { - return isTitleless; - } - - void setIsTitleless(boolean isTitleless) { - if (isTitleless && getIconResId() == 0) { - throw new IllegalStateException("This tab is supposed to be " + - "icon only, yet it has no icon specified. Index in " + - "container: " + getIndexInTabContainer()); - } - - this.isTitleless = isTitleless; - } - - public ViewGroup getOuterView() { - return (ViewGroup) getParent(); - } - - AppCompatImageView getIconView() { - return iconView; - } - - int getIconResId() { - return iconResId; - } - - void setIconResId(int iconResId) { - this.iconResId = iconResId; - } - - TextView getTitleView() { - return titleView; - } - - public String getTitle() { - return title; - } - - public void setTitle(String title) { - this.title = title; - updateTitle(); - } - - public float getInActiveAlpha() { - return inActiveAlpha; - } - - public void setInActiveAlpha(float inActiveAlpha) { - this.inActiveAlpha = inActiveAlpha; - - if (!isActive) { - setAlphas(inActiveAlpha); - } - } - - public float getActiveAlpha() { - return activeAlpha; - } - - public void setActiveAlpha(float activeAlpha) { - this.activeAlpha = activeAlpha; - - if (isActive) { - setAlphas(activeAlpha); - } - } - - public int getInActiveColor() { - return inActiveColor; - } - - public void setInActiveColor(int inActiveColor) { - this.inActiveColor = inActiveColor; - - if (!isActive) { - setColors(inActiveColor); - } - } - - public int getActiveColor() { - return activeColor; - } - - public void setActiveColor(int activeIconColor) { - this.activeColor = activeIconColor; - - if (isActive) { - setColors(activeColor); - } - } - - public int getBarColorWhenSelected() { - return barColorWhenSelected; - } - - public void setBarColorWhenSelected(int barColorWhenSelected) { - this.barColorWhenSelected = barColorWhenSelected; - } - - public int getBadgeBackgroundColor() { - return badgeBackgroundColor; - } - - public void setBadgeBackgroundColor(int badgeBackgroundColor) { - this.badgeBackgroundColor = badgeBackgroundColor; - - if (badge != null) { - badge.setColoredCircleBackground(badgeBackgroundColor); - } - } - - public boolean getBadgeHidesWhenActive() { - return badgeHidesWhenActive; - } - - public void setBadgeHidesWhenActive(boolean hideWhenActive) { - this.badgeHidesWhenActive = hideWhenActive; - } - - int getCurrentDisplayedIconColor() { - Object tag = iconView.getTag(R.id.bb_bottom_bar_color_id); - - if (tag instanceof Integer) { - return (int) tag; - } - - return 0; - } - - int getCurrentDisplayedTitleColor() { - if (titleView != null) { - return titleView.getCurrentTextColor(); - } - - return 0; - } - - int getCurrentDisplayedTextAppearance() { - Object tag = titleView.getTag(R.id.bb_bottom_bar_appearance_id); - - if (titleView != null && tag instanceof Integer) { - return (int) tag; - } - - return 0; - } - - public void setBadgeCount(int count) { - if (count <= 0) { - if (badge != null) { - badge.removeFromTab(this); - badge = null; - } - - return; - } - - if (badge == null) { - badge = new BottomBarBadge(getContext()); - badge.attachToTab(this, badgeBackgroundColor); - } - - badge.setCount(count); - - if (isActive && badgeHidesWhenActive) { - badge.hide(); - } - } - - public void removeBadge() { - setBadgeCount(0); - } - - boolean isActive() { - return isActive; - } - - boolean hasActiveBadge() { - return badge != null; - } - - int getIndexInTabContainer() { - return indexInContainer; - } - - void setIndexInContainer(int indexInContainer) { - this.indexInContainer = indexInContainer; - } - - void setIconTint(int tint) { - iconView.setColorFilter(tint); - } - - public int getTitleTextAppearance() { - return titleTextAppearanceResId; - } - - @SuppressWarnings("deprecation") - void setTitleTextAppearance(int resId) { - this.titleTextAppearanceResId = resId; - updateCustomTextAppearance(); - } - - public void setTitleTypeface(Typeface typeface) { - this.titleTypeFace = typeface; - updateCustomTypeface(); - } - - public Typeface getTitleTypeFace() { - return titleTypeFace; - } - - void select(boolean animate) { - isActive = true; - - if (animate) { - animateIcon(activeAlpha, ACTIVE_SHIFTING_TITLELESS_ICON_SCALE); - animateTitle(sixDps, ACTIVE_TITLE_SCALE, activeAlpha); - animateColors(inActiveColor, activeColor); - } else { - setTitleScale(ACTIVE_TITLE_SCALE); - setTopPadding(sixDps); - setIconScale(ACTIVE_SHIFTING_TITLELESS_ICON_SCALE); - setColors(activeColor); - setAlphas(activeAlpha); - } - - setSelected(true); - - if (badge != null && badgeHidesWhenActive) { - badge.hide(); - } - } - - void deselect(boolean animate) { - isActive = false; - - boolean isShifting = type == Type.SHIFTING; - - float titleScale = isShifting ? 0 : INACTIVE_FIXED_TITLE_SCALE; - int iconPaddingTop = isShifting ? sixteenDps : eightDps; - - if (animate) { - animateTitle(iconPaddingTop, titleScale, inActiveAlpha); - animateIcon(inActiveAlpha, INACTIVE_SHIFTING_TITLELESS_ICON_SCALE); - animateColors(activeColor, inActiveColor); - } else { - setTitleScale(titleScale); - setTopPadding(iconPaddingTop); - setIconScale(INACTIVE_SHIFTING_TITLELESS_ICON_SCALE); - setColors(inActiveColor); - setAlphas(inActiveAlpha); - } - - setSelected(false); - - if (!isShifting && badge != null && !badge.isVisible()) { - badge.show(); - } - } - - private void animateColors(int previousColor, int color) { - ValueAnimator anim = new ValueAnimator(); - anim.setIntValues(previousColor, color); - anim.setEvaluator(new ArgbEvaluator()); - anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { - @Override - public void onAnimationUpdate(ValueAnimator valueAnimator) { - setColors((Integer) valueAnimator.getAnimatedValue()); - } - }); - - anim.setDuration(150); - anim.start(); - } - - private void setColors(int color) { - if (iconView != null) { - iconView.setColorFilter(color); - iconView.setTag(R.id.bb_bottom_bar_color_id, color); - } - - if (titleView != null) { - titleView.setTextColor(color); - } - } - - private void setAlphas(float alpha) { - if (iconView != null) { - ViewCompat.setAlpha(iconView, alpha); - } - - if (titleView != null) { - ViewCompat.setAlpha(titleView, alpha); - } - } - - void updateWidth(float endWidth, boolean animated) { - if (!animated) { - getLayoutParams().width = (int) endWidth; - - if (!isActive && badge != null) { - badge.adjustPositionAndSize(this); - badge.show(); - } - return; - } - - float start = getWidth(); - - ValueAnimator animator = ValueAnimator.ofFloat(start, endWidth); - animator.setDuration(150); - animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { - @Override - public void onAnimationUpdate(ValueAnimator animator) { - ViewGroup.LayoutParams params = getLayoutParams(); - if (params == null) return; - - params.width = Math.round((float) animator.getAnimatedValue()); - setLayoutParams(params); - } - }); - - // Workaround to avoid using faulty onAnimationEnd() listener - postDelayed(new Runnable() { - @Override - public void run() { - if (!isActive && badge != null) { - clearAnimation(); - badge.adjustPositionAndSize(BottomBarTab.this); - badge.show(); - } - } - }, animator.getDuration()); - - animator.start(); - } - - private void updateBadgePosition() { - if (badge != null) { - badge.adjustPositionAndSize(this); - } - } - - private void setTopPaddingAnimated(int start, int end) { - if (type == Type.TABLET || isTitleless) { - return; - } - - ValueAnimator paddingAnimator = ValueAnimator.ofInt(start, end); - paddingAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { - @Override - public void onAnimationUpdate(ValueAnimator animation) { - iconView.setPadding( - iconView.getPaddingLeft(), - (Integer) animation.getAnimatedValue(), - iconView.getPaddingRight(), - iconView.getPaddingBottom() - ); - } - }); - - paddingAnimator.setDuration(ANIMATION_DURATION); - paddingAnimator.start(); - } - - private void animateTitle(int padding, float scale, float alpha) { - if (type == Type.TABLET && isTitleless) { - return; - } - - setTopPaddingAnimated(iconView.getPaddingTop(), padding); - - ViewPropertyAnimatorCompat titleAnimator = ViewCompat.animate(titleView) - .setDuration(ANIMATION_DURATION) - .scaleX(scale) - .scaleY(scale); - titleAnimator.alpha(alpha); - titleAnimator.start(); - } - - private void animateIconScale(float scale) { - ViewCompat.animate(iconView) - .setDuration(ANIMATION_DURATION) - .scaleX(scale) - .scaleY(scale) - .start(); - } - - private void animateIcon(float alpha, float scale) { - ViewCompat.animate(iconView) - .setDuration(ANIMATION_DURATION) - .alpha(alpha) - .start(); - - if (isTitleless && type == Type.SHIFTING) { - animateIconScale(scale); - } - } - - private void setTopPadding(int topPadding) { - if (type == Type.TABLET || isTitleless) { - return; - } - - iconView.setPadding( - iconView.getPaddingLeft(), - topPadding, - iconView.getPaddingRight(), - iconView.getPaddingBottom() - ); - } - - private void setTitleScale(float scale) { - if (type == Type.TABLET || isTitleless) { - return; - } - - ViewCompat.setScaleX(titleView, scale); - ViewCompat.setScaleY(titleView, scale); - } - - private void setIconScale(float scale) { - if (isTitleless && type == Type.SHIFTING) { - ViewCompat.setScaleX(iconView, scale); - ViewCompat.setScaleY(iconView, scale); - } - } - - @Override - public Parcelable onSaveInstanceState() { - if (badge != null) { - Bundle bundle = saveState(); - bundle.putParcelable("superstate", super.onSaveInstanceState()); - - return bundle; - } - - return super.onSaveInstanceState(); - } - - @VisibleForTesting - Bundle saveState() { - Bundle outState = new Bundle(); - outState.putInt(STATE_BADGE_COUNT + getIndexInTabContainer(), badge.getCount()); - - return outState; - } - - @Override - public void onRestoreInstanceState(Parcelable state) { - if (state instanceof Bundle) { - Bundle bundle = (Bundle) state; - restoreState(bundle); - - state = bundle.getParcelable("superstate"); - } - - super.onRestoreInstanceState(state); - } - - @VisibleForTesting - void restoreState(Bundle savedInstanceState) { - int previousBadgeCount = savedInstanceState.getInt(STATE_BADGE_COUNT + getIndexInTabContainer()); - setBadgeCount(previousBadgeCount); - } - - enum Type { - FIXED, SHIFTING, TABLET - } - - public static class Config { - private final float inActiveTabAlpha; - private final float activeTabAlpha; - private final int inActiveTabColor; - private final int activeTabColor; - private final int barColorWhenSelected; - private final int badgeBackgroundColor; - private final int titleTextAppearance; - private final Typeface titleTypeFace; - private boolean badgeHidesWhenSelected = true; - - private Config(Builder builder) { - this.inActiveTabAlpha = builder.inActiveTabAlpha; - this.activeTabAlpha = builder.activeTabAlpha; - this.inActiveTabColor = builder.inActiveTabColor; - this.activeTabColor = builder.activeTabColor; - this.barColorWhenSelected = builder.barColorWhenSelected; - this.badgeBackgroundColor = builder.badgeBackgroundColor; - this.badgeHidesWhenSelected = builder.hidesBadgeWhenSelected; - this.titleTextAppearance = builder.titleTextAppearance; - this.titleTypeFace = builder.titleTypeFace; - } - - public static class Builder { - private float inActiveTabAlpha; - private float activeTabAlpha; - private int inActiveTabColor; - private int activeTabColor; - private int barColorWhenSelected; - private int badgeBackgroundColor; - private boolean hidesBadgeWhenSelected = true; - private int titleTextAppearance; - private Typeface titleTypeFace; - - public Builder inActiveTabAlpha(float alpha) { - this.inActiveTabAlpha = alpha; - return this; - } - - public Builder activeTabAlpha(float alpha) { - this.activeTabAlpha = alpha; - return this; - } - - public Builder inActiveTabColor(@ColorInt int color) { - this.inActiveTabColor = color; - return this; - } - - public Builder activeTabColor(@ColorInt int color) { - this.activeTabColor = color; - return this; - } - - public Builder barColorWhenSelected(@ColorInt int color) { - this.barColorWhenSelected = color; - return this; - } - - public Builder badgeBackgroundColor(@ColorInt int color) { - this.badgeBackgroundColor = color; - return this; - } - - public Builder hideBadgeWhenSelected(boolean hide) { - this.hidesBadgeWhenSelected = hide; - return this; - } - - public Builder titleTextAppearance(int titleTextAppearance) { - this.titleTextAppearance = titleTextAppearance; - return this; - } - - public Builder titleTypeFace(Typeface titleTypeFace) { - this.titleTypeFace = titleTypeFace; - return this; - } - - public Config build() { - return new Config(this); - } - } - } -} diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/bottombar/BottomNavigationBehavior.java b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/bottombar/BottomNavigationBehavior.java deleted file mode 100644 index e4e2c554f..000000000 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/bottombar/BottomNavigationBehavior.java +++ /dev/null @@ -1,153 +0,0 @@ -package com.habitrpg.android.habitica.ui.views.bottombar; - -import android.os.Build; -import androidx.annotation.NonNull; -import androidx.coordinatorlayout.widget.CoordinatorLayout; -import androidx.core.view.ViewCompat; -import androidx.core.view.ViewPropertyAnimatorCompat; -import androidx.interpolator.view.animation.LinearOutSlowInInterpolator; - -import android.view.View; -import android.view.ViewGroup; -import android.view.animation.Interpolator; - -import com.google.android.material.snackbar.Snackbar; - -/** - * Created by Nikola D. on 3/15/2016. - * - * Credit goes to Nikola Despotoski: - * https://github.com/NikolaDespotoski - */ -class BottomNavigationBehavior extends VerticalScrollingBehavior { - private static final Interpolator INTERPOLATOR = new LinearOutSlowInInterpolator(); - private final int bottomNavHeight; - private final int defaultOffset; - private boolean isTablet = false; - - private ViewPropertyAnimatorCompat mTranslationAnimator; - private boolean hidden = false; - private int mSnackbarHeight = -1; - private final BottomNavigationWithSnackbar mWithSnackBarImpl = new LollipopBottomNavWithSnackBarImpl(); - private boolean mScrollingEnabled = true; - - BottomNavigationBehavior(int bottomNavHeight, int defaultOffset, boolean tablet) { - this.bottomNavHeight = bottomNavHeight; - this.defaultOffset = defaultOffset; - isTablet = tablet; - } - - @Override - public boolean layoutDependsOn(CoordinatorLayout parent, V child, View dependency) { - mWithSnackBarImpl.updateSnackbar(parent, dependency, child); - return dependency instanceof Snackbar.SnackbarLayout; - } - - @Override - public void onNestedVerticalOverScroll(CoordinatorLayout coordinatorLayout, V child, @ScrollDirection int direction, int currentOverScroll, int totalOverScroll) { - } - - @Override - public void onDependentViewRemoved(CoordinatorLayout parent, V child, View dependency) { - updateScrollingForSnackbar(dependency, true); - super.onDependentViewRemoved(parent, child, dependency); - } - - private void updateScrollingForSnackbar(View dependency, boolean enabled) { - if (!isTablet && dependency instanceof Snackbar.SnackbarLayout) { - mScrollingEnabled = enabled; - } - } - - @Override - public boolean onDependentViewChanged(CoordinatorLayout parent, V child, View dependency) { - updateScrollingForSnackbar(dependency, false); - return super.onDependentViewChanged(parent, child, dependency); - } - - @Override - public void onDirectionNestedPreScroll(CoordinatorLayout coordinatorLayout, V child, View target, int dx, int dy, int[] consumed, @ScrollDirection int scrollDirection) { - handleDirection(child, scrollDirection); - } - - private void handleDirection(V child, int scrollDirection) { - if (!mScrollingEnabled) return; - if (scrollDirection == ScrollDirection.SCROLL_DIRECTION_DOWN && hidden) { - hidden = false; - animateOffset(child, defaultOffset); - } else if (scrollDirection == ScrollDirection.SCROLL_DIRECTION_UP && !hidden) { - hidden = true; - animateOffset(child, bottomNavHeight + defaultOffset); - } - } - - @Override - protected boolean onNestedDirectionFling(CoordinatorLayout coordinatorLayout, V child, View target, float velocityX, float velocityY, @ScrollDirection int scrollDirection) { - handleDirection(child, scrollDirection); - return true; - } - - private void animateOffset(final V child, final int offset) { - ensureOrCancelAnimator(child); - mTranslationAnimator.translationY(offset).start(); - } - - private void ensureOrCancelAnimator(V child) { - if (mTranslationAnimator == null) { - mTranslationAnimator = ViewCompat.animate(child); - mTranslationAnimator.setDuration(300); - mTranslationAnimator.setInterpolator(INTERPOLATOR); - } else { - mTranslationAnimator.cancel(); - } - } - - - void setHidden(@NonNull V view, boolean bottomLayoutHidden) { - if (!bottomLayoutHidden && hidden) { - animateOffset(view, defaultOffset); - } else if (bottomLayoutHidden && !hidden) { - animateOffset(view, bottomNavHeight + defaultOffset); - } - hidden = bottomLayoutHidden; - } - - - static BottomNavigationBehavior 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) behavior; - } - - throw new IllegalArgumentException("The view is not associated with BottomNavigationBehavior"); - } - - private interface BottomNavigationWithSnackbar { - void updateSnackbar(CoordinatorLayout parent, View dependency, View child); - } - - private class LollipopBottomNavWithSnackBarImpl implements BottomNavigationWithSnackbar { - @Override - public void updateSnackbar(CoordinatorLayout parent, View dependency, View child) { - if (!isTablet && dependency instanceof Snackbar.SnackbarLayout) { - if (mSnackbarHeight == -1) { - mSnackbarHeight = dependency.getHeight(); - } - if (ViewCompat.getTranslationY(child) != 0) return; - int targetPadding = (mSnackbarHeight + bottomNavHeight - defaultOffset); - dependency.setPadding(dependency.getPaddingLeft(), - dependency.getPaddingTop(), dependency.getPaddingRight(), targetPadding - ); - } - } - } -} diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/bottombar/MiscUtils.java b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/bottombar/MiscUtils.java deleted file mode 100644 index cdc743907..000000000 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/bottombar/MiscUtils.java +++ /dev/null @@ -1,121 +0,0 @@ -package com.habitrpg.android.habitica.ui.views.bottombar; - -import android.content.Context; -import android.content.res.Configuration; -import android.content.res.Resources; -import android.os.Build; -import androidx.annotation.AttrRes; -import androidx.annotation.ColorInt; -import androidx.annotation.Dimension; -import androidx.annotation.DrawableRes; -import androidx.annotation.NonNull; -import androidx.annotation.Px; -import androidx.annotation.StyleRes; -import android.util.DisplayMetrics; -import android.util.TypedValue; -import android.widget.TextView; - -import static androidx.annotation.Dimension.DP; - -/* - * BottomBar library for Android - * Copyright (c) 2016 Iiro Krankka (http://github.com/roughike). - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -class MiscUtils { - - @NonNull protected static TypedValue getTypedValue(@NonNull Context context, @AttrRes int resId) { - TypedValue tv = new TypedValue(); - context.getTheme().resolveAttribute(resId, tv, true); - return tv; - } - - @ColorInt - protected static int getColor(@NonNull Context context, @AttrRes int color) { - return getTypedValue(context, color).data; - } - - @DrawableRes - protected static int getDrawableRes(@NonNull Context context, @AttrRes int drawable) { - return getTypedValue(context, drawable).resourceId; - } - - /** - * Converts dps to pixels nicely. - * - * @param context the Context for getting the resources - * @param dp dimension in dps - * @return dimension in pixels - */ - protected static int dpToPixel(@NonNull Context context, @Dimension(unit = DP) float dp) { - Resources resources = context.getResources(); - DisplayMetrics metrics = resources.getDisplayMetrics(); - - try { - return (int) (dp * metrics.density); - } catch (NoSuchFieldError ignored) { - return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, metrics); - } - } - - /** - * Converts pixels to dps just as well. - * - * @param context the Context for getting the resources - * @param px dimension in pixels - * @return dimension in dps - */ - protected static int pixelToDp(@NonNull Context context, @Px int px) { - DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics(); - return Math.round(px / displayMetrics.density); - } - - /** - * Returns screen width. - * - * @param context Context to get resources and device specific display metrics - * @return screen width - */ - protected static int getScreenWidth(@NonNull Context context) { - DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics(); - return (int) (displayMetrics.widthPixels / displayMetrics.density); - } - - /** - * A convenience method for setting text appearance. - * - * @param textView a TextView which textAppearance to modify. - * @param resId a style resource for the text appearance. - */ - @SuppressWarnings("deprecation") - protected static void setTextAppearance(@NonNull TextView textView, @StyleRes int resId) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - textView.setTextAppearance(resId); - } else { - textView.setTextAppearance(textView.getContext(), resId); - } - } - - /** - * Determine if the current UI Mode is Night Mode. - * - * @param context Context to get the configuration. - * @return true if the night mode is enabled, otherwise false. - */ - protected static boolean isNightMode(@NonNull Context context) { - int currentNightMode = context.getResources().getConfiguration().uiMode - & Configuration.UI_MODE_NIGHT_MASK; - return currentNightMode == Configuration.UI_MODE_NIGHT_YES; - } -} diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/bottombar/OnTabReselectListener.java b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/bottombar/OnTabReselectListener.java deleted file mode 100644 index e8e5febd6..000000000 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/bottombar/OnTabReselectListener.java +++ /dev/null @@ -1,30 +0,0 @@ -package com.habitrpg.android.habitica.ui.views.bottombar; - -import androidx.annotation.IdRes; - -/* - * BottomBar library for Android - * Copyright (c) 2016 Iiro Krankka (http://github.com/roughike). - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -public interface OnTabReselectListener { - /** - * The method being called when currently visible {@link BottomBarTab} is - * reselected. Use this method for scrolling to the top of your content, - * as recommended by the Material Design spec - * - * @param tabId the {@link BottomBarTab} that was reselected. - */ - void onTabReSelected(@IdRes int tabId); -} diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/bottombar/OnTabSelectListener.java b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/bottombar/OnTabSelectListener.java deleted file mode 100644 index 5d7f5374f..000000000 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/bottombar/OnTabSelectListener.java +++ /dev/null @@ -1,32 +0,0 @@ -package com.habitrpg.android.habitica.ui.views.bottombar; - -import androidx.annotation.IdRes; - -/* - * BottomBar library for Android - * Copyright (c) 2016 Iiro Krankka (http://github.com/roughike). - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -public interface OnTabSelectListener { - /** - * The method being called when currently visible {@link BottomBarTab} changes. - * - * This listener is fired for the first time after the items have been set and - * also after a configuration change, such as when screen orientation changes - * from portrait to landscape. - * - * @param tabId the new visible {@link BottomBarTab} - */ - void onTabSelected(@IdRes int tabId); -} diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/bottombar/ShySettings.java b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/bottombar/ShySettings.java deleted file mode 100644 index 02e3e4fe1..000000000 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/bottombar/ShySettings.java +++ /dev/null @@ -1,55 +0,0 @@ -package com.habitrpg.android.habitica.ui.views.bottombar; - -/** - * Settings specific for a shy BottomBar. - */ -public class ShySettings { - private BottomBar bottomBar; - private Boolean pendingIsVisibleInShyMode; - - ShySettings(BottomBar bottomBar) { - this.bottomBar = bottomBar; - } - - void shyHeightCalculated() { - updatePendingShyVisibility(); - } - - /** - * Shows the BottomBar if it was hidden, with a translate animation. - */ - public void showBar() { - toggleIsVisibleInShyMode(true); - } - - /** - * Hides the BottomBar in if it was visible, with a translate animation. - */ - public void hideBar() { - toggleIsVisibleInShyMode(false); - } - - private void toggleIsVisibleInShyMode(boolean visible) { - if (!bottomBar.isShy()) { - return; - } - - if (bottomBar.isShyHeightAlreadyCalculated()) { - BottomNavigationBehavior 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; - } - } -} diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/bottombar/TabParser.java b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/bottombar/TabParser.java deleted file mode 100644 index e4a6c9b26..000000000 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/bottombar/TabParser.java +++ /dev/null @@ -1,210 +0,0 @@ -package com.habitrpg.android.habitica.ui.views.bottombar; - -import android.content.Context; -import android.content.res.XmlResourceParser; -import android.graphics.Color; -import androidx.annotation.CheckResult; -import androidx.annotation.ColorInt; -import androidx.annotation.IntRange; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.annotation.StringDef; -import androidx.annotation.XmlRes; -import androidx.core.content.ContextCompat; - -import org.xmlpull.v1.XmlPullParserException; - -import java.io.IOException; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.util.ArrayList; -import java.util.List; - -import static com.habitrpg.android.habitica.ui.views.bottombar.TabParser.TabAttribute.ACTIVE_COLOR; -import static com.habitrpg.android.habitica.ui.views.bottombar.TabParser.TabAttribute.BADGE_BACKGROUND_COLOR; -import static com.habitrpg.android.habitica.ui.views.bottombar.TabParser.TabAttribute.BADGE_HIDES_WHEN_ACTIVE; -import static com.habitrpg.android.habitica.ui.views.bottombar.TabParser.TabAttribute.BAR_COLOR_WHEN_SELECTED; -import static com.habitrpg.android.habitica.ui.views.bottombar.TabParser.TabAttribute.ICON; -import static com.habitrpg.android.habitica.ui.views.bottombar.TabParser.TabAttribute.ID; -import static com.habitrpg.android.habitica.ui.views.bottombar.TabParser.TabAttribute.INACTIVE_COLOR; -import static com.habitrpg.android.habitica.ui.views.bottombar.TabParser.TabAttribute.IS_TITLELESS; -import static com.habitrpg.android.habitica.ui.views.bottombar.TabParser.TabAttribute.TITLE; - -/** - * Created by iiro on 21.7.2016. - * - * BottomBar library for Android - * Copyright (c) 2016 Iiro Krankka (http://github.com/roughike). - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -class TabParser { - private static final String TAB_TAG = "tab"; - private static final int AVG_NUMBER_OF_TABS = 5; - private static final int COLOR_NOT_SET = -1; - private static final int RESOURCE_NOT_FOUND = 0; - - @NonNull - private final Context context; - - @NonNull - private final BottomBarTab.Config defaultTabConfig; - - @NonNull - private final XmlResourceParser parser; - - @Nullable - private List 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 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. - } -} diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/bottombar/TabSelectionInterceptor.java b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/bottombar/TabSelectionInterceptor.java deleted file mode 100644 index d510f6f8c..000000000 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/bottombar/TabSelectionInterceptor.java +++ /dev/null @@ -1,33 +0,0 @@ -package com.habitrpg.android.habitica.ui.views.bottombar; - -import androidx.annotation.IdRes; - -/* - * BottomBar library for Android - * Copyright (c) 2016 Iiro Krankka (http://github.com/roughike). - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -public interface TabSelectionInterceptor { - /** - * The method being called when currently visible {@link BottomBarTab} is about to change. - *

- * 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); -} diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/bottombar/VerticalScrollingBehavior.java b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/bottombar/VerticalScrollingBehavior.java deleted file mode 100644 index ab1e5bea8..000000000 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/bottombar/VerticalScrollingBehavior.java +++ /dev/null @@ -1,150 +0,0 @@ -package com.habitrpg.android.habitica.ui.views.bottombar; - -import android.content.Context; -import android.os.Parcelable; -import androidx.annotation.IntDef; -import androidx.annotation.NonNull; -import androidx.coordinatorlayout.widget.CoordinatorLayout; -import androidx.core.view.WindowInsetsCompat; - -import android.util.AttributeSet; -import android.view.View; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; - -/** - * Created by Nikola D. on 11/22/2015. - * - * Credit goes to Nikola Despotoski: - * https://github.com/NikolaDespotoski - */ -abstract class VerticalScrollingBehavior extends CoordinatorLayout.Behavior { - - 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); - } - -} \ No newline at end of file diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/navigation/BottomNavigationItem.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/navigation/BottomNavigationItem.kt new file mode 100644 index 000000000..d5393bf8d --- /dev/null +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/navigation/BottomNavigationItem.kt @@ -0,0 +1,52 @@ +package com.habitrpg.android.habitica.ui.views.navigation + +import android.content.Context +import android.graphics.PorterDuff +import android.util.AttributeSet +import android.view.View +import android.widget.ImageView +import android.widget.LinearLayout +import android.widget.TextView +import androidx.core.content.ContextCompat +import com.habitrpg.android.habitica.R +import com.habitrpg.android.habitica.extensions.inflate +import com.habitrpg.android.habitica.ui.helpers.bindView + +class BottomNavigationItem @JvmOverloads constructor( + context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 +) : LinearLayout(context, attrs, defStyleAttr) { + + private val iconView: ImageView by bindView(R.id.icon_view) + private val selectedTitleView: TextView by bindView(R.id.selected_title_view) + private val titleView: TextView by bindView(R.id.title_view) + + var isActive = false + set(value) { + field = value + if (isActive) { + selectedTitleView.visibility = View.VISIBLE + titleView.visibility = View.GONE + iconView.drawable.setColorFilter(ContextCompat.getColor(context, R.color.white), PorterDuff.Mode.MULTIPLY ) + } else { + selectedTitleView.visibility = View.GONE + titleView.visibility = View.VISIBLE + iconView.drawable.setColorFilter(ContextCompat.getColor(context, R.color.brand_500), PorterDuff.Mode.MULTIPLY ) + } + } + + init { + inflate(R.layout.bottom_navigation_item, true) + + val attributes = context.theme?.obtainStyledAttributes( + attrs, + R.styleable.BottomNavigationItem, + 0, 0) + if (attributes != null) { + iconView.setImageDrawable(attributes.getDrawable(R.styleable.BottomNavigationItem_iconDrawable)) + titleView.text = attributes.getString(R.styleable.BottomNavigationItem_title) + selectedTitleView.text = attributes.getString(R.styleable.BottomNavigationItem_title) + } + orientation = VERTICAL + } + +} \ No newline at end of file diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/navigation/HabiticaBottomNavigationView.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/navigation/HabiticaBottomNavigationView.kt new file mode 100644 index 000000000..f3f6abab0 --- /dev/null +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/navigation/HabiticaBottomNavigationView.kt @@ -0,0 +1,66 @@ +package com.habitrpg.android.habitica.ui.views.navigation + +import android.content.Context +import android.util.AttributeSet +import android.widget.ImageButton +import android.widget.RelativeLayout +import com.habitrpg.android.habitica.R +import com.habitrpg.android.habitica.extensions.inflate +import com.habitrpg.android.habitica.models.tasks.Task +import com.habitrpg.android.habitica.ui.helpers.bindView + +class HabiticaBottomNavigationView @JvmOverloads constructor( + context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 +) : RelativeLayout(context, attrs, defStyleAttr) { + + var selectedPosition: Int + get() { + return when (activeTaskType) { + Task.TYPE_DAILY -> 1 + Task.TYPE_REWARD -> 2 + Task.TYPE_TODO -> 3 + else -> 0 + } + } + set(value) { + activeTaskType = when (value) { + 1 -> Task.TYPE_DAILY + 2 -> Task.TYPE_TODO + 3 -> Task.TYPE_REWARD + else -> Task.TYPE_HABIT + } + } + var onTabSelectedListener: ((String) -> Unit)? = null + var onAddListener: ((String) -> Unit)? = null + var activeTaskType: String = Task.TYPE_HABIT + set(value) { + field = value + updateItemSelection() + onTabSelectedListener?.invoke(value) + } + + private val habitsTab: BottomNavigationItem by bindView(R.id.tab_habits) + private val dailiesTab: BottomNavigationItem by bindView(R.id.tab_dailies) + private val todosTab: BottomNavigationItem by bindView(R.id.tab_todos) + private val rewardsTab: BottomNavigationItem by bindView(R.id.tab_rewards) + private val addButton: ImageButton by bindView(R.id.add) + + init { + inflate(R.layout.main_navigation_view, true) + habitsTab.setOnClickListener { activeTaskType = Task.TYPE_HABIT } + dailiesTab.setOnClickListener { activeTaskType = Task.TYPE_DAILY } + todosTab.setOnClickListener { activeTaskType = Task.TYPE_TODO } + rewardsTab.setOnClickListener { activeTaskType = Task.TYPE_REWARD } + addButton.setOnClickListener { + onAddListener?.invoke(activeTaskType) + } + updateItemSelection() + } + + private fun updateItemSelection() { + habitsTab.isActive = activeTaskType == Task.TYPE_HABIT + dailiesTab.isActive = activeTaskType == Task.TYPE_DAILY + todosTab.isActive = activeTaskType == Task.TYPE_TODO + rewardsTab.isActive = activeTaskType == Task.TYPE_REWARD + } +} \ No newline at end of file