From a8ba41a7ad25344861bc89c4bb4fbbbf6c90129e Mon Sep 17 00:00:00 2001 From: sadorowo Date: Tue, 18 Jun 2024 19:12:08 +0200 Subject: [PATCH] add NavLib files/deps into project --- app/build.gradle | 3 +- navlib-font/build.gradle | 44 ++ navlib-font/consumer-proguard-rules.pro | 1 + navlib-font/gradle.properties | 18 + navlib-font/src/main/AndroidManifest.xml | 18 + .../typeface/library/navlibfont/NavLibFont.kt | 72 ++ .../main/res/font/navlibfont_font_v1_0.ttf | Bin 0 -> 6104 bytes .../src/main/res/values/font_addon.xml | 20 + .../src/main/res/values/font_description.xml | 31 + navlib/build.gradle | 55 ++ navlib/consumer-rules.pro | 1 + navlib/proguard-rules.pro | 23 + navlib/src/main/AndroidManifest.xml | 2 + .../main/java/pl/szczodrzynski/navlib/Anim.kt | 170 ++++ .../szczodrzynski/navlib/BadgeDrawable.java | 109 +++ .../szczodrzynski/navlib/BezelGifImageView.kt | 278 +++++++ .../szczodrzynski/navlib/DrawerExtensions.kt | 85 ++ .../pl/szczodrzynski/navlib/ImageHolder.kt | 121 +++ .../pl/szczodrzynski/navlib/NavBottomBar.kt | 211 +++++ .../pl/szczodrzynski/navlib/NavToolbar.kt | 55 ++ .../java/pl/szczodrzynski/navlib/NavView.kt | 213 +++++ .../szczodrzynski/navlib/NavigationLoader.kt | 5 + .../pl/szczodrzynski/navlib/SystemBarsUtil.kt | 376 +++++++++ .../java/pl/szczodrzynski/navlib/Utils.kt | 164 ++++ .../navlib/bottomsheet/BottomSheetAdapter.kt | 38 + .../navlib/bottomsheet/NavBottomSheet.kt | 438 +++++++++++ .../navlib/bottomsheet/ViewHolderProvider.kt | 21 + .../items/BottomSheetPrimaryItem.kt | 124 +++ .../items/BottomSheetSeparatorItem.kt | 28 + .../bottomsheet/items/IBottomSheetItem.kt | 19 + .../navlib/drawer/IDrawerProfile.kt | 17 + .../navlib/drawer/IUnreadCounter.kt | 8 + .../szczodrzynski/navlib/drawer/NavDrawer.kt | 730 ++++++++++++++++++ .../navlib/drawer/items/DrawerPrimaryItem.kt | 18 + .../color/mtrl_filled_background_color.xml | 21 + .../color/text_input_layout_background.xml | 22 + .../res/drawable-v21/bs_item_background.xml | 10 + .../res/drawable/bottom_sheet_background.xml | 9 + .../res/drawable/bottom_sheet_control_bar.xml | 9 + .../main/res/drawable/bs_item_background.xml | 10 + .../res/drawable/bs_item_background_base.xml | 11 + navlib/src/main/res/drawable/header.png | Bin 0 -> 92843 bytes navlib/src/main/res/drawable/ic_android.xml | 9 + navlib/src/main/res/drawable/ic_light.xml | 9 + .../src/main/res/drawable/ic_menu_badge.xml | 10 + navlib/src/main/res/drawable/ic_night.xml | 9 + navlib/src/main/res/drawable/placeholder.xml | 3 + navlib/src/main/res/drawable/profile.xml | 15 + .../src/main/res/drawable/shadow_bottom.xml | 8 + navlib/src/main/res/drawable/shadow_right.xml | 8 + .../src/main/res/layout/material_drawer.xml | 6 + .../res/layout/material_drawer_slider.xml | 25 + .../src/main/res/layout/nav_bottom_sheet.xml | 109 +++ .../main/res/layout/nav_bs_item_primary.xml | 49 ++ .../main/res/layout/nav_bs_item_separator.xml | 12 + navlib/src/main/res/layout/nav_view.xml | 211 +++++ navlib/src/main/res/values-w600dp/styles.xml | 6 + navlib/src/main/res/values/attrs.xml | 8 + navlib/src/main/res/values/attrs_nav_view.xml | 8 + navlib/src/main/res/values/colors.xml | 19 + navlib/src/main/res/values/dimens.xml | 4 + navlib/src/main/res/values/strings.xml | 4 + navlib/src/main/res/values/styles.xml | 159 ++++ settings.gradle | 2 +- 64 files changed, 4298 insertions(+), 3 deletions(-) create mode 100644 navlib-font/build.gradle create mode 100644 navlib-font/consumer-proguard-rules.pro create mode 100644 navlib-font/gradle.properties create mode 100644 navlib-font/src/main/AndroidManifest.xml create mode 100644 navlib-font/src/main/java/com/mikepenz/iconics/typeface/library/navlibfont/NavLibFont.kt create mode 100644 navlib-font/src/main/res/font/navlibfont_font_v1_0.ttf create mode 100644 navlib-font/src/main/res/values/font_addon.xml create mode 100644 navlib-font/src/main/res/values/font_description.xml create mode 100644 navlib/build.gradle create mode 100644 navlib/consumer-rules.pro create mode 100644 navlib/proguard-rules.pro create mode 100644 navlib/src/main/AndroidManifest.xml create mode 100644 navlib/src/main/java/pl/szczodrzynski/navlib/Anim.kt create mode 100644 navlib/src/main/java/pl/szczodrzynski/navlib/BadgeDrawable.java create mode 100644 navlib/src/main/java/pl/szczodrzynski/navlib/BezelGifImageView.kt create mode 100644 navlib/src/main/java/pl/szczodrzynski/navlib/DrawerExtensions.kt create mode 100644 navlib/src/main/java/pl/szczodrzynski/navlib/ImageHolder.kt create mode 100644 navlib/src/main/java/pl/szczodrzynski/navlib/NavBottomBar.kt create mode 100644 navlib/src/main/java/pl/szczodrzynski/navlib/NavToolbar.kt create mode 100644 navlib/src/main/java/pl/szczodrzynski/navlib/NavView.kt create mode 100644 navlib/src/main/java/pl/szczodrzynski/navlib/NavigationLoader.kt create mode 100644 navlib/src/main/java/pl/szczodrzynski/navlib/SystemBarsUtil.kt create mode 100644 navlib/src/main/java/pl/szczodrzynski/navlib/Utils.kt create mode 100644 navlib/src/main/java/pl/szczodrzynski/navlib/bottomsheet/BottomSheetAdapter.kt create mode 100644 navlib/src/main/java/pl/szczodrzynski/navlib/bottomsheet/NavBottomSheet.kt create mode 100644 navlib/src/main/java/pl/szczodrzynski/navlib/bottomsheet/ViewHolderProvider.kt create mode 100644 navlib/src/main/java/pl/szczodrzynski/navlib/bottomsheet/items/BottomSheetPrimaryItem.kt create mode 100644 navlib/src/main/java/pl/szczodrzynski/navlib/bottomsheet/items/BottomSheetSeparatorItem.kt create mode 100644 navlib/src/main/java/pl/szczodrzynski/navlib/bottomsheet/items/IBottomSheetItem.kt create mode 100644 navlib/src/main/java/pl/szczodrzynski/navlib/drawer/IDrawerProfile.kt create mode 100644 navlib/src/main/java/pl/szczodrzynski/navlib/drawer/IUnreadCounter.kt create mode 100644 navlib/src/main/java/pl/szczodrzynski/navlib/drawer/NavDrawer.kt create mode 100644 navlib/src/main/java/pl/szczodrzynski/navlib/drawer/items/DrawerPrimaryItem.kt create mode 100644 navlib/src/main/res/color/mtrl_filled_background_color.xml create mode 100644 navlib/src/main/res/color/text_input_layout_background.xml create mode 100644 navlib/src/main/res/drawable-v21/bs_item_background.xml create mode 100644 navlib/src/main/res/drawable/bottom_sheet_background.xml create mode 100644 navlib/src/main/res/drawable/bottom_sheet_control_bar.xml create mode 100644 navlib/src/main/res/drawable/bs_item_background.xml create mode 100644 navlib/src/main/res/drawable/bs_item_background_base.xml create mode 100644 navlib/src/main/res/drawable/header.png create mode 100644 navlib/src/main/res/drawable/ic_android.xml create mode 100644 navlib/src/main/res/drawable/ic_light.xml create mode 100644 navlib/src/main/res/drawable/ic_menu_badge.xml create mode 100644 navlib/src/main/res/drawable/ic_night.xml create mode 100644 navlib/src/main/res/drawable/placeholder.xml create mode 100644 navlib/src/main/res/drawable/profile.xml create mode 100644 navlib/src/main/res/drawable/shadow_bottom.xml create mode 100644 navlib/src/main/res/drawable/shadow_right.xml create mode 100644 navlib/src/main/res/layout/material_drawer.xml create mode 100644 navlib/src/main/res/layout/material_drawer_slider.xml create mode 100644 navlib/src/main/res/layout/nav_bottom_sheet.xml create mode 100644 navlib/src/main/res/layout/nav_bs_item_primary.xml create mode 100644 navlib/src/main/res/layout/nav_bs_item_separator.xml create mode 100644 navlib/src/main/res/layout/nav_view.xml create mode 100644 navlib/src/main/res/values-w600dp/styles.xml create mode 100644 navlib/src/main/res/values/attrs.xml create mode 100644 navlib/src/main/res/values/attrs_nav_view.xml create mode 100644 navlib/src/main/res/values/colors.xml create mode 100644 navlib/src/main/res/values/dimens.xml create mode 100644 navlib/src/main/res/values/strings.xml create mode 100644 navlib/src/main/res/values/styles.xml diff --git a/app/build.gradle b/app/build.gradle index 2a16d640..822f0a02 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -210,7 +210,7 @@ dependencies { implementation "pl.droidsonroids.retrofit2:converter-jspoon:1.3.2" // Szkolny.eu libraries/forks - //implementation "com.github.santoni0:NavLib:323288f" + implementation project(":navlib") implementation "eu.szkolny:android-snowfall:1ca9ea2da3" implementation "eu.szkolny:agendacalendarview:1.0.4" implementation "eu.szkolny:cafebar:5bf0c618de" @@ -221,7 +221,6 @@ dependencies { implementation "eu.szkolny.selective-dao:annotation:6a337f9" officialImplementation "eu.szkolny:ssl-provider:1.0.0" unofficialImplementation "eu.szkolny:ssl-provider:1.0.0" - implementation "pl.szczodrzynski:numberslidingpicker:2921225f76" implementation "pl.szczodrzynski:recyclertablayout:700f980584" implementation "pl.szczodrzynski:tachyon:551943a6b5" diff --git a/navlib-font/build.gradle b/navlib-font/build.gradle new file mode 100644 index 00000000..7f254181 --- /dev/null +++ b/navlib-font/build.gradle @@ -0,0 +1,44 @@ +/* + * Copyright 2019 Mike Penz + * + * 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. + */ + +apply plugin: 'com.android.library' + +apply plugin: 'kotlin-android' + +android { + compileSdkVersion setup.compileSdk + + defaultConfig { + minSdkVersion setup.minSdk + targetSdkVersion setup.targetSdk + consumerProguardFiles 'consumer-proguard-rules.pro' + versionCode 10 + versionName "1.0" + + resValue "string", "NavLibFont_version", "${versionName}" + } + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } +} + +dependencies { + implementation "com.mikepenz:iconics-typeface-api:5.3.0-b01" + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" +} diff --git a/navlib-font/consumer-proguard-rules.pro b/navlib-font/consumer-proguard-rules.pro new file mode 100644 index 00000000..ef3e42a0 --- /dev/null +++ b/navlib-font/consumer-proguard-rules.pro @@ -0,0 +1 @@ +-keep class com.mikepenz.iconics.typeface.library.navlibfont.NavLibFont { *; } diff --git a/navlib-font/gradle.properties b/navlib-font/gradle.properties new file mode 100644 index 00000000..163aa9f2 --- /dev/null +++ b/navlib-font/gradle.properties @@ -0,0 +1,18 @@ +# +# Copyright 2019 Mike Penz +# +# 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. +# +POM_NAME=Android-Iconics NavLibFont Typeface Library +POM_ARTIFACT_ID=navlibfont-typeface +POM_PACKAGING=aar diff --git a/navlib-font/src/main/AndroidManifest.xml b/navlib-font/src/main/AndroidManifest.xml new file mode 100644 index 00000000..edcc9806 --- /dev/null +++ b/navlib-font/src/main/AndroidManifest.xml @@ -0,0 +1,18 @@ + + + + \ No newline at end of file diff --git a/navlib-font/src/main/java/com/mikepenz/iconics/typeface/library/navlibfont/NavLibFont.kt b/navlib-font/src/main/java/com/mikepenz/iconics/typeface/library/navlibfont/NavLibFont.kt new file mode 100644 index 00000000..822fde52 --- /dev/null +++ b/navlib-font/src/main/java/com/mikepenz/iconics/typeface/library/navlibfont/NavLibFont.kt @@ -0,0 +1,72 @@ +/* + * Copyright 2019 Mike Penz + * + * 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.mikepenz.iconics.typeface.library.navlibfont + +import com.mikepenz.iconics.typeface.IIcon +import com.mikepenz.iconics.typeface.ITypeface +import java.util.LinkedList + +@Suppress("EnumEntryName") +object NavLibFont : ITypeface { + + override val fontRes: Int + get() = R.font.navlibfont_font_v1_0 + + override val characters: Map by lazy { + Icon.values().associate { it.name to it.character } + } + + override val mappingPrefix: String + get() = "nav" + + override val fontName: String + get() = "NavLibFont" + + override val version: String + get() = "1.0" + + override val iconCount: Int + get() = characters.size + + override val icons: List + get() = characters.keys.toCollection(LinkedList()) + + override val author: String + get() = "Kuba SzczodrzyƄski" + + override val url: String + get() = "https://github.com/kuba2k2/NavLib" + + override val description: String + get() = "" + + override val license: String + get() = "" + + override val licenseUrl: String + get() = "" + + override fun getIcon(key: String): IIcon = Icon.valueOf(key) + + enum class Icon constructor(override val character: Char) : IIcon { + nav_dots_vertical('\ue801'), + nav_menu('\ue800'), + nav_sort_ascending('\ue803'), + nav_sort_descending('\ue802'); + + override val typeface: ITypeface by lazy { NavLibFont } + } +} \ No newline at end of file diff --git a/navlib-font/src/main/res/font/navlibfont_font_v1_0.ttf b/navlib-font/src/main/res/font/navlibfont_font_v1_0.ttf new file mode 100644 index 0000000000000000000000000000000000000000..f0da0117dbb035090ea3b552fdacb6d7af55719e GIT binary patch literal 6104 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 literal 0 HcmV?d00001 diff --git a/navlib-font/src/main/res/values/font_addon.xml b/navlib-font/src/main/res/values/font_addon.xml new file mode 100644 index 00000000..1d094be3 --- /dev/null +++ b/navlib-font/src/main/res/values/font_addon.xml @@ -0,0 +1,20 @@ + + + + + com.mikepenz.iconics.typeface.library.navlibfont.NavLibFont + diff --git a/navlib-font/src/main/res/values/font_description.xml b/navlib-font/src/main/res/values/font_description.xml new file mode 100644 index 00000000..1421308b --- /dev/null +++ b/navlib-font/src/main/res/values/font_description.xml @@ -0,0 +1,31 @@ + + + + + year;author;libraryName;libraryWebsite + Kuba SzczodrzyƄski + https://github.com/kuba2k2/NavLib + NavLibFont + + https://github.com/kuba2k2/NavLib + @string/NavLibFont_version + + true + https://github.com/kuba2k2/NavLib + + 2018 + diff --git a/navlib/build.gradle b/navlib/build.gradle new file mode 100644 index 00000000..b935281b --- /dev/null +++ b/navlib/build.gradle @@ -0,0 +1,55 @@ +apply plugin: 'com.android.library' +apply plugin: 'kotlin-android' +apply plugin: 'kotlin-kapt' +apply plugin: 'kotlin-android-extensions' + +android { + compileSdkVersion setup.compileSdk + + defaultConfig { + minSdkVersion setup.minSdk + targetSdkVersion setup.targetSdk + versionCode release.versionCode + versionName release.versionName + + consumerProguardFiles 'consumer-rules.pro' + + vectorDrawables.useSupportLibrary = true + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + + buildFeatures { + dataBinding = true + } + + packagingOptions { + exclude 'META-INF/library-core_release.kotlin_module' + } +} + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar']) + + implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" + implementation "androidx.appcompat:appcompat:1.2.0" + implementation "androidx.core:core-ktx:1.3.2" + implementation "androidx.recyclerview:recyclerview:1.1.0" + implementation "com.google.android.material:material:1.3.0" + + api "com.mikepenz:materialize:1.2.1" + api "com.mikepenz:materialdrawer:8.3.3" + api "com.mikepenz:iconics-core:5.3.0-b01" + api "com.mikepenz:itemanimators:1.1.0" + + compileOnly "pl.droidsonroids.gif:android-gif-drawable:1.2.15" + + implementation "com.balysv:material-ripple:1.0.2" + + implementation project(":navlib-font") +} diff --git a/navlib/consumer-rules.pro b/navlib/consumer-rules.pro new file mode 100644 index 00000000..619be6dd --- /dev/null +++ b/navlib/consumer-rules.pro @@ -0,0 +1 @@ +-keep class androidx.drawerlayout.widget.DrawerLayout { *; } diff --git a/navlib/proguard-rules.pro b/navlib/proguard-rules.pro new file mode 100644 index 00000000..06b02c6a --- /dev/null +++ b/navlib/proguard-rules.pro @@ -0,0 +1,23 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in init.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile + +-keep class androidx.drawerlayout.widget.DrawerLayout { *; } diff --git a/navlib/src/main/AndroidManifest.xml b/navlib/src/main/AndroidManifest.xml new file mode 100644 index 00000000..7b521725 --- /dev/null +++ b/navlib/src/main/AndroidManifest.xml @@ -0,0 +1,2 @@ + diff --git a/navlib/src/main/java/pl/szczodrzynski/navlib/Anim.kt b/navlib/src/main/java/pl/szczodrzynski/navlib/Anim.kt new file mode 100644 index 00000000..5b57e48f --- /dev/null +++ b/navlib/src/main/java/pl/szczodrzynski/navlib/Anim.kt @@ -0,0 +1,170 @@ +package pl.szczodrzynski.navlib + +import android.view.View +import android.view.ViewGroup +import android.view.animation.AccelerateInterpolator +import android.view.animation.AlphaAnimation +import android.view.animation.Animation +import android.view.animation.DecelerateInterpolator +import android.view.animation.ScaleAnimation +import android.view.animation.Transformation +import android.widget.LinearLayout + +object Anim { + fun expand(v: View, duration: Int?, animationListener: Animation.AnimationListener?) { + v.measure(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT) + val targetHeight = v.measuredHeight + //Log.d("Anim", "targetHeight="+targetHeight); + v.visibility = View.VISIBLE + v.layoutParams.height = 0 + val a = object : Animation() { + override fun applyTransformation(interpolatedTime: Float, t: Transformation) { + v.layoutParams.height = if (interpolatedTime == 1.0f) + LinearLayout.LayoutParams.WRAP_CONTENT//(int)(targetHeight * interpolatedTime) + else + (targetHeight * interpolatedTime).toInt() + v.requestLayout() + } + + override fun willChangeBounds(): Boolean { + return true + } + } + if (duration == null) { + a.duration = (targetHeight.toFloat() / v.context.resources.displayMetrics.density).toInt().toLong() + } else { + a.duration = duration as Long + } + if (animationListener != null) { + a.setAnimationListener(animationListener) + } + v.startAnimation(a) + } + + fun collapse(v: View, duration: Int?, animationListener: Animation.AnimationListener?) { + val initialHeight = v.measuredHeight + val a = object : Animation() { + override fun applyTransformation(interpolatedTime: Float, t: Transformation) { + if (interpolatedTime == 1.0f) { + v.visibility = View.GONE + return + } + v.layoutParams.height = initialHeight - (initialHeight.toFloat() * interpolatedTime).toInt() + v.requestLayout() + } + + override fun willChangeBounds(): Boolean { + return true + } + } + if (duration == null) { + a.duration = (initialHeight.toFloat() / v.context.resources.displayMetrics.density).toInt().toLong() + } else { + a.duration = duration as Long + } + if (animationListener != null) { + a.setAnimationListener(animationListener) + } + v.startAnimation(a) + } + + fun fadeIn(v: View, duration: Int?, animationListener: Animation.AnimationListener?) { + val fadeIn = AlphaAnimation(0f, 1f) + fadeIn.interpolator = DecelerateInterpolator() //add this + fadeIn.duration = duration!!.toLong() + fadeIn.setAnimationListener(object : Animation.AnimationListener { + override fun onAnimationStart(animation: Animation) { + v.visibility = View.VISIBLE + animationListener?.onAnimationStart(animation) + } + + override fun onAnimationEnd(animation: Animation) { + animationListener?.onAnimationEnd(animation) + } + + override fun onAnimationRepeat(animation: Animation) { + animationListener?.onAnimationRepeat(animation) + } + }) + v.startAnimation(fadeIn) + } + + fun fadeOut(v: View, duration: Int?, animationListener: Animation.AnimationListener?) { + val fadeOut = AlphaAnimation(1f, 0f) + fadeOut.interpolator = AccelerateInterpolator() //and this + fadeOut.duration = duration!!.toLong() + fadeOut.setAnimationListener(object : Animation.AnimationListener { + override fun onAnimationStart(animation: Animation) { + animationListener?.onAnimationStart(animation) + } + + override fun onAnimationEnd(animation: Animation) { + v.visibility = View.INVISIBLE + animationListener?.onAnimationEnd(animation) + } + + override fun onAnimationRepeat(animation: Animation) { + animationListener?.onAnimationRepeat(animation) + } + }) + v.startAnimation(fadeOut) + } + + fun scaleView( + v: View, + duration: Int?, + animationListener: Animation.AnimationListener?, + startScale: Float, + endScale: Float + ) { + val anim = ScaleAnimation( + 1f, 1f, // Start and end values for the X axis scaling + startScale, endScale, // Start and end values for the Y axis scaling + Animation.RELATIVE_TO_SELF, 0f, // Pivot point of X scaling + Animation.RELATIVE_TO_SELF, 0f + ) // Pivot point of Y scaling + anim.fillAfter = true // Needed to keep the result of the animation + anim.duration = duration!!.toLong() + anim.setAnimationListener(object : Animation.AnimationListener { + override fun onAnimationStart(animation: Animation) { + animationListener?.onAnimationStart(animation) + } + + override fun onAnimationEnd(animation: Animation) { + animationListener?.onAnimationEnd(animation) + } + + override fun onAnimationRepeat(animation: Animation) { + animationListener?.onAnimationRepeat(animation) + } + }) + v.startAnimation(anim) + } + + class ResizeAnimation( + private val mView: View, + private val mFromWidth: Float, + private val mFromHeight: Float, + private val mToWidth: Float, + private val mToHeight: Float + ) : Animation() { + + private val width: Float + private val height: Float + + init { + width = mView.width.toFloat() + height = mView.height.toFloat() + duration = 300 + } + + override fun applyTransformation(interpolatedTime: Float, t: Transformation) { + val height = (mToHeight - mFromHeight) * interpolatedTime + mFromHeight + val width = (mToWidth - mFromWidth) * interpolatedTime + mFromWidth + val p = mView.layoutParams + p.width = (width * this.width).toInt() + p.height = (height * this.height).toInt() + mView.requestLayout() + } + } +} diff --git a/navlib/src/main/java/pl/szczodrzynski/navlib/BadgeDrawable.java b/navlib/src/main/java/pl/szczodrzynski/navlib/BadgeDrawable.java new file mode 100644 index 00000000..c47c3ff2 --- /dev/null +++ b/navlib/src/main/java/pl/szczodrzynski/navlib/BadgeDrawable.java @@ -0,0 +1,109 @@ +package pl.szczodrzynski.navlib; + +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.ColorFilter; +import android.graphics.Paint; +import android.graphics.PixelFormat; +import android.graphics.Rect; +import android.graphics.Typeface; +import android.graphics.drawable.Drawable; + +import androidx.core.content.ContextCompat; + +public class BadgeDrawable extends Drawable { + + private Paint mBadgePaint; + private Paint mBadgePaint1; + private Paint mTextPaint; + private Rect mTxtRect = new Rect(); + + private String mCount = ""; + private boolean mWillDraw = false; + + public BadgeDrawable(Context context) { + float mTextSize = context.getResources().getDimension(R.dimen.badge_text_size); + + mBadgePaint = new Paint(); + mBadgePaint.setColor(0xffff3d00); + mBadgePaint.setAntiAlias(true); + mBadgePaint.setStyle(Paint.Style.FILL); + /*mBadgePaint1 = new Paint(); + mBadgePaint1.setColor(ContextCompat.getColor(context.getApplicationContext(), R.color.grey_ivory5)); + mBadgePaint1.setAntiAlias(true); + mBadgePaint1.setStyle(Paint.Style.FILL);*/ + + mTextPaint = new Paint(); + mTextPaint.setColor(Color.WHITE); + mTextPaint.setTypeface(Typeface.DEFAULT); + mTextPaint.setTextSize(mTextSize); + mTextPaint.setAntiAlias(true); + mTextPaint.setTextAlign(Paint.Align.CENTER); + } + + @Override + public void draw(Canvas canvas) { + + + + if (!mWillDraw) { + return; + } + Rect bounds = getBounds(); + float width = bounds.right - bounds.left; + float height = bounds.bottom - bounds.top; + + // Position the badge in the top-right quadrant of the icon. + + /*Using Math.max rather than Math.min */ + + float radius = ((Math.max(width, height) / 2)) / 2; + float centerX = (width - radius - 1) +5; + float centerY = radius -5; + if(mCount.length() <= 2){ + // Draw badge circle. + //canvas.drawCircle(centerX, centerY, (int)(radius+7.5), mBadgePaint1); + canvas.drawCircle(centerX, centerY, (int)(radius+5.5), mBadgePaint); + } + else{ + //canvas.drawCircle(centerX, centerY, (int)(radius+8.5), mBadgePaint1); + canvas.drawCircle(centerX, centerY, (int)(radius+6.5), mBadgePaint); +// canvas.drawRoundRect(radius, radius, radius, radius, 10, 10, mBadgePaint); + } + // Draw badge count text inside the circle. + mTextPaint.getTextBounds(mCount, 0, mCount.length(), mTxtRect); + float textHeight = mTxtRect.bottom - mTxtRect.top; + float textY = centerY + (textHeight / 2f); + if(mCount.length() > 2) + canvas.drawText("99+", centerX, textY, mTextPaint); + else + canvas.drawText(mCount, centerX, textY, mTextPaint); + } + + /* + Sets the count (i.e notifications) to display. + */ + public void setCount(String count) { + mCount = count; + + // Only draw a badge if there are notifications. + mWillDraw = !count.equalsIgnoreCase("0"); + invalidateSelf(); + } + + @Override + public void setAlpha(int alpha) { + // do nothing + } + + @Override + public void setColorFilter(ColorFilter cf) { + // do nothing + } + + @Override + public int getOpacity() { + return PixelFormat.UNKNOWN; + } +} diff --git a/navlib/src/main/java/pl/szczodrzynski/navlib/BezelGifImageView.kt b/navlib/src/main/java/pl/szczodrzynski/navlib/BezelGifImageView.kt new file mode 100644 index 00000000..716ee1be --- /dev/null +++ b/navlib/src/main/java/pl/szczodrzynski/navlib/BezelGifImageView.kt @@ -0,0 +1,278 @@ +package pl.szczodrzynski.navlib + +import android.annotation.TargetApi +import android.content.Context +import android.graphics.* +import android.graphics.drawable.Drawable +import android.net.Uri +import android.os.Build +import android.util.AttributeSet +import android.view.MotionEvent +import android.view.View +import android.view.ViewOutlineProvider +import androidx.core.view.ViewCompat +import com.mikepenz.materialdrawer.R +import com.mikepenz.materialdrawer.util.DrawerImageLoader +import pl.droidsonroids.gif.GifImageView + + +/** + * An [android.widget.ImageView] that draws its contents inside a mask and draws a border + * drawable on top. This is useful for applying a beveled look to image contents, but is also + * flexible enough for use with other desired aesthetics. + */ +open class BezelImageView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyle: Int = 0) : GifImageView(context, attrs, defStyle) { + private val mBlackPaint: Paint + private val mMaskedPaint: Paint + + private var mBounds: Rect? = null + private var mBoundsF: RectF? = null + + private val mMaskDrawable: Drawable? + private var mDrawCircularShadow = true + + private var mDesaturateColorFilter: ColorMatrixColorFilter? = null + + private val mSelectorAlpha = 150 + private var mSelectorColor: Int = 0 + private var mSelectorFilter: ColorFilter? = null + + private var mCacheValid = false + private var mCacheBitmap: Bitmap + private var mCachedWidth: Int = 0 + private var mCachedHeight: Int = 0 + + private var mIsPressed = false + private var mIsSelected: Boolean = false + + private var mTempDesaturateColorFilter: ColorMatrixColorFilter? = null + private var mTempSelectorFilter: ColorFilter? = null + + init { + + // Attribute initialization + val a = context.obtainStyledAttributes(attrs, R.styleable.BezelImageView, defStyle, R.style.BezelImageView) + + mMaskDrawable = a.getDrawable(R.styleable.BezelImageView_materialDrawerMaskDrawable) + if (mMaskDrawable != null) { + mMaskDrawable.callback = this + } + + mDrawCircularShadow = a.getBoolean(R.styleable.BezelImageView_materialDrawerDrawCircularShadow, true) + + mSelectorColor = a.getColor(R.styleable.BezelImageView_materialDrawerSelectorOnPress, 0) + + a.recycle() + + // Other initialization + mBlackPaint = Paint() + mBlackPaint.color = -0x1000000 + + mMaskedPaint = Paint() + mMaskedPaint.xfermode = PorterDuffXfermode(PorterDuff.Mode.SRC_IN) + + // Always want a cache allocated. + mCacheBitmap = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888) + + // Create a desaturate color filter for pressed state. + val cm = ColorMatrix() + cm.setSaturation(0f) + mDesaturateColorFilter = ColorMatrixColorFilter(cm) + + //create a selectorFilter if we already have a color + if (mSelectorColor != 0) { + this.mSelectorFilter = PorterDuffColorFilter(Color.argb(mSelectorAlpha, Color.red(mSelectorColor), Color.green(mSelectorColor), Color.blue(mSelectorColor)), PorterDuff.Mode.SRC_ATOP) + } + } + + override fun onSizeChanged(w: Int, h: Int, old_w: Int, old_h: Int) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + if (mDrawCircularShadow) { + outlineProvider = CustomOutline(w, h) + } + } + } + + @TargetApi(21) + private inner class CustomOutline internal constructor(internal var width: Int, internal var height: Int) : ViewOutlineProvider() { + + override fun getOutline(view: View, outline: Outline) { + outline.setOval(0, 0, width, height) + } + } + + override fun setFrame(l: Int, t: Int, r: Int, b: Int): Boolean { + val changed = super.setFrame(l, t, r, b) + mBounds = Rect(0, 0, r - l, b - t).also { + mBoundsF = RectF(it) + + if (mMaskDrawable != null) { + mMaskDrawable.bounds = it + } + } + + if (changed) { + mCacheValid = false + } + + return changed + } + + override fun onDraw(canvas: Canvas) { + val bounds = mBounds ?: return + + val width = bounds.width() + val height = bounds.height() + + if (width == 0 || height == 0) { + return + } + + if (!mCacheValid || width != mCachedWidth || height != mCachedHeight || mIsSelected != mIsPressed) { + // Need to redraw the cache + if (width == mCachedWidth && height == mCachedHeight) { + // Have a correct-sized bitmap cache already allocated. Just erase it. + mCacheBitmap.eraseColor(0) + } else { + // Allocate a new bitmap with the correct dimensions. + mCacheBitmap.recycle() + + mCacheBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888) + mCachedWidth = width + mCachedHeight = height + } + + val cacheCanvas = Canvas(mCacheBitmap) + + when { + mMaskDrawable != null -> { + val sc = cacheCanvas.save() + mMaskDrawable.draw(cacheCanvas) + if (mIsSelected) { + if (mSelectorFilter != null) { + mMaskedPaint.colorFilter = mSelectorFilter + } else { + mMaskedPaint.colorFilter = mDesaturateColorFilter + + } + } else { + mMaskedPaint.colorFilter = null + } + cacheCanvas.saveLayer(mBoundsF, mMaskedPaint, Canvas.ALL_SAVE_FLAG) + super.onDraw(cacheCanvas) + cacheCanvas.restoreToCount(sc) + } + mIsSelected -> { + val sc = cacheCanvas.save() + cacheCanvas.drawRect(0f, 0f, mCachedWidth.toFloat(), mCachedHeight.toFloat(), mBlackPaint) + if (mSelectorFilter != null) { + mMaskedPaint.colorFilter = mSelectorFilter + } else { + mMaskedPaint.colorFilter = mDesaturateColorFilter + } + cacheCanvas.saveLayer(mBoundsF, mMaskedPaint, Canvas.ALL_SAVE_FLAG) + super.onDraw(cacheCanvas) + cacheCanvas.restoreToCount(sc) + } + else -> super.onDraw(cacheCanvas) + } + } + + // Draw from cache + canvas.drawBitmap(mCacheBitmap, bounds.left.toFloat(), bounds.top.toFloat(), null) + + //remember the previous press state + mIsPressed = isPressed + } + + + override fun dispatchTouchEvent(event: MotionEvent): Boolean { + // Check for clickable state and do nothing if disabled + if (!this.isClickable) { + this.mIsSelected = false + return super.onTouchEvent(event) + } + + // Set selected state based on Motion Event + when (event.action) { + MotionEvent.ACTION_DOWN -> this.mIsSelected = true + MotionEvent.ACTION_UP, MotionEvent.ACTION_SCROLL, MotionEvent.ACTION_OUTSIDE, MotionEvent.ACTION_CANCEL -> this.mIsSelected = false + } + + // Redraw image and return super type + this.invalidate() + return super.dispatchTouchEvent(event) + } + + override fun drawableStateChanged() { + super.drawableStateChanged() + if (mMaskDrawable != null && mMaskDrawable.isStateful) { + mMaskDrawable.state = drawableState + } + if (isDuplicateParentStateEnabled) { + ViewCompat.postInvalidateOnAnimation(this) + } + } + + override fun invalidateDrawable(who: Drawable) { + if (who === mMaskDrawable) { + invalidate() + } else { + super.invalidateDrawable(who) + } + } + + override fun verifyDrawable(who: Drawable): Boolean { + return who === mMaskDrawable || super.verifyDrawable(who) + } + + + /** + * Sets the color of the selector to be draw over the + * CircularImageView. Be sure to provide some opacity. + * + * @param selectorColor The color (including alpha) to set for the selector overlay. + */ + fun setSelectorColor(selectorColor: Int) { + this.mSelectorColor = selectorColor + this.mSelectorFilter = PorterDuffColorFilter(Color.argb(mSelectorAlpha, Color.red(mSelectorColor), Color.green(mSelectorColor), Color.blue(mSelectorColor)), PorterDuff.Mode.SRC_ATOP) + this.invalidate() + } + + + override fun setImageDrawable(drawable: Drawable?) { + super.setImageDrawable(drawable) + } + + override fun setImageResource(resId: Int) { + super.setImageResource(resId) + } + + override fun setImageBitmap(bm: Bitmap) { + super.setImageBitmap(bm) + } + + override fun setImageURI(uri: Uri?) { + if ("http" == uri?.scheme || "https" == uri?.scheme) { + DrawerImageLoader.instance.setImage(this, uri, null) + } else { + super.setImageURI(uri) + } + } + + fun disableTouchFeedback(disable: Boolean) { + if (disable) { + mTempDesaturateColorFilter = this.mDesaturateColorFilter + mTempSelectorFilter = this.mSelectorFilter + this.mSelectorFilter = null + this.mDesaturateColorFilter = null + } else { + if (mTempDesaturateColorFilter != null) { + this.mDesaturateColorFilter = mTempDesaturateColorFilter + } + if (mTempSelectorFilter != null) { + this.mSelectorFilter = mTempSelectorFilter + } + } + } +} diff --git a/navlib/src/main/java/pl/szczodrzynski/navlib/DrawerExtensions.kt b/navlib/src/main/java/pl/szczodrzynski/navlib/DrawerExtensions.kt new file mode 100644 index 00000000..2ec9084e --- /dev/null +++ b/navlib/src/main/java/pl/szczodrzynski/navlib/DrawerExtensions.kt @@ -0,0 +1,85 @@ +package pl.szczodrzynski.navlib + +import android.view.View +import androidx.recyclerview.widget.RecyclerView +import com.mikepenz.iconics.typeface.IIcon +import com.mikepenz.materialdrawer.* +import com.mikepenz.materialdrawer.holder.StringHolder +import com.mikepenz.materialdrawer.model.AbstractDrawerItem +import com.mikepenz.materialdrawer.model.BaseDrawerItem +import com.mikepenz.materialdrawer.model.interfaces.* +import com.mikepenz.materialdrawer.util.getDrawerItem +import com.mikepenz.materialdrawer.util.updateItem +import com.mikepenz.materialdrawer.widget.MaterialDrawerSliderView + +/*inline fun DrawerBuilder.withOnDrawerItemClickListener(crossinline listener: (view: View?, position: Int, drawerItem: IDrawerItem<*>) -> Boolean): DrawerBuilder { + return this.withOnDrawerItemClickListener(object : Drawer.OnDrawerItemClickListener { + override fun onItemClick(view: View?, position: Int, drawerItem: IDrawerItem<*>): Boolean { + return listener(view, position, drawerItem) + } + }) +} + +inline fun DrawerBuilder.withOnDrawerItemLongClickListener(crossinline listener: (view: View, position: Int, drawerItem: IDrawerItem<*>) -> Boolean): DrawerBuilder { + return this.withOnDrawerItemLongClickListener(object : Drawer.OnDrawerItemLongClickListener { + override fun onItemLongClick(view: View, position: Int, drawerItem: IDrawerItem<*>): Boolean { + return listener(view, position, drawerItem) + } + }) +} + +inline fun AccountHeaderBuilder.withOnAccountHeaderListener(crossinline listener: (view: View?, profile: IProfile<*>, current: Boolean) -> Boolean): AccountHeaderBuilder { + return this.withOnAccountHeaderListener(object : AccountHeader.OnAccountHeaderListener { + override fun onProfileChanged(view: View?, profile: IProfile<*>, current: Boolean): Boolean { + return listener(view, profile, current) + } + }) +} + +inline fun AccountHeaderBuilder.withOnAccountHeaderItemLongClickListener(crossinline listener: (view: View, profile: IProfile<*>, current: Boolean) -> Boolean): AccountHeaderBuilder { + return this.withOnAccountHeaderItemLongClickListener(object : AccountHeader.OnAccountHeaderItemLongClickListener { + override fun onProfileLongClick(view: View, profile: IProfile<*>, current: Boolean): Boolean { + return listener(view, profile, current) + } + }) +} + +inline fun AccountHeaderBuilder.withOnAccountHeaderProfileImageListener( + crossinline onClick: ( + view: View, + profile: IProfile<*>, + current: Boolean + ) -> Boolean, + crossinline onLongClick: ( + view: View, + profile: IProfile<*>, + current: Boolean + ) -> Boolean +): AccountHeaderBuilder { + return this.withOnAccountHeaderProfileImageListener(object : AccountHeader.OnAccountHeaderProfileImageListener { + override fun onProfileImageClick(view: View, profile: IProfile<*>, current: Boolean): Boolean { + return onClick(view, profile, current) + } + override fun onProfileImageLongClick(view: View, profile: IProfile<*>, current: Boolean): Boolean { + return onLongClick(view, profile, current) + } + }) +} + +inline fun MiniDrawer.withOnMiniDrawerItemClickListener(crossinline listener: (view: View?, position: Int, drawerItem: IDrawerItem<*>, type: Int) -> Boolean): MiniDrawer { + return this.withOnMiniDrawerItemClickListener(object : MiniDrawer.OnMiniDrawerItemClickListener { + override fun onItemClick(view: View?, position: Int, drawerItem: IDrawerItem<*>, type: Int): Boolean { + return listener(view, position, drawerItem, type) + } + }) +}*/ + +fun MaterialDrawerSliderView.updateBadge(identifier: Long, badge: StringHolder?) { + val drawerItem = getDrawerItem(identifier) + if (drawerItem is Badgeable) { + drawerItem.withBadge(badge) + updateItem(drawerItem) + } +} + +fun T.withIcon(icon: IIcon) = withIcon(pl.szczodrzynski.navlib.ImageHolder(icon)) diff --git a/navlib/src/main/java/pl/szczodrzynski/navlib/ImageHolder.kt b/navlib/src/main/java/pl/szczodrzynski/navlib/ImageHolder.kt new file mode 100644 index 00000000..a2075667 --- /dev/null +++ b/navlib/src/main/java/pl/szczodrzynski/navlib/ImageHolder.kt @@ -0,0 +1,121 @@ +package pl.szczodrzynski.navlib + +import android.content.Context +import android.content.res.ColorStateList +import android.graphics.Bitmap +import android.graphics.PorterDuff +import android.graphics.PorterDuffColorFilter +import android.graphics.drawable.Drawable +import android.net.Uri +import android.widget.ImageView +import androidx.annotation.ColorInt +import androidx.annotation.DrawableRes +import androidx.appcompat.content.res.AppCompatResources +import com.mikepenz.iconics.IconicsDrawable +import com.mikepenz.iconics.typeface.IIcon +import com.mikepenz.iconics.utils.actionBar +import com.mikepenz.iconics.utils.sizeDp +import com.mikepenz.materialdrawer.util.DrawerImageLoader +import pl.droidsonroids.gif.GifDrawable +import java.io.FileNotFoundException + +/** + * Created by mikepenz on 13.07.15. + */ + +open class ImageHolder : com.mikepenz.materialdrawer.holder.ImageHolder { + + constructor(@DrawableRes iconRes: Int, colorFilter: Int?) : super(iconRes) { + this.colorFilter = colorFilter + } + constructor(iicon: IIcon) : super(null as Drawable?) { + this.iicon = iicon + } + constructor() : super() + constructor(url: String) : super(url) + constructor(uri: Uri) : super(uri) + constructor(icon: Drawable?) : super(icon) + constructor(bitmap: Bitmap) : super(bitmap) + constructor(iconRes: Int) : super(iconRes) + + var iicon: IIcon? = null + @ColorInt + var colorFilter: Int? = null + var colorFilterMode: PorterDuff.Mode = PorterDuff.Mode.DST_OVER + + + /** + * sets an existing image to the imageView + * + * @param imageView + * @param tag used to identify imageViews and define different placeholders + * @return true if an image was set + */ + override fun applyTo(imageView: ImageView, tag: String?): Boolean { + val ii = iicon + + if (uri != null) { + if (uri.toString().endsWith(".gif", true)) { + imageView.setImageDrawable(GifDrawable(uri.toString())) + } + else { + val consumed = DrawerImageLoader.instance.setImage(imageView, uri!!, tag) + if (!consumed) { + imageView.setImageURI(uri) + } + } + } else if (icon != null) { + imageView.setImageDrawable(icon) + } else if (bitmap != null) { + imageView.setImageBitmap(bitmap) + } else if (iconRes != -1) { + imageView.setImageResource(iconRes) + } else if (ii != null) { + imageView.setImageDrawable(IconicsDrawable(imageView.context, ii).actionBar()) + } else { + imageView.setImageBitmap(null) + return false + } + + if (colorFilter != null) { + imageView.colorFilter = PorterDuffColorFilter(colorFilter!!, colorFilterMode) + } + + return true + } + + /** + * this only handles Drawables + * + * @param ctx + * @param iconColor + * @param tint + * @return + */ + override fun decideIcon(ctx: Context, iconColor: ColorStateList, tint: Boolean, paddingDp: Int): Drawable? { + var icon: Drawable? = icon + val ii = iicon + val uri = uri + + when { + ii != null -> icon = IconicsDrawable(ctx).apply { + this.icon = ii + colorList = iconColor + sizeDp = 24 + } + iconRes != -1 -> icon = AppCompatResources.getDrawable(ctx, iconRes) + uri != null -> try { + val inputStream = ctx.contentResolver.openInputStream(uri) + icon = Drawable.createFromStream(inputStream, uri.toString()) + } catch (e: FileNotFoundException) { + //no need to handle this + } + } + //if we got an icon AND we have auto tinting enabled AND it is no IIcon, tint it ;) + if (icon != null && tint && iicon == null) { + icon = icon.mutate() + icon.setColorFilter(iconColor.defaultColor, PorterDuff.Mode.SRC_IN) + } + return icon + } +} diff --git a/navlib/src/main/java/pl/szczodrzynski/navlib/NavBottomBar.kt b/navlib/src/main/java/pl/szczodrzynski/navlib/NavBottomBar.kt new file mode 100644 index 00000000..6bc40157 --- /dev/null +++ b/navlib/src/main/java/pl/szczodrzynski/navlib/NavBottomBar.kt @@ -0,0 +1,211 @@ +package pl.szczodrzynski.navlib + +import android.annotation.SuppressLint +import android.content.Context +import android.graphics.drawable.LayerDrawable +import android.util.AttributeSet +import android.view.Gravity +import android.view.MenuItem +import android.view.View +import androidx.coordinatorlayout.widget.CoordinatorLayout +import androidx.core.content.ContextCompat +import com.google.android.material.bottomappbar.BottomAppBar +import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton +import com.google.android.material.floatingactionbutton.FloatingActionButton +import com.mikepenz.iconics.IconicsDrawable +import com.mikepenz.iconics.typeface.IIcon +import com.mikepenz.iconics.typeface.library.navlibfont.NavLibFont +import com.mikepenz.iconics.utils.sizeDp +import pl.szczodrzynski.navlib.bottomsheet.NavBottomSheet +import pl.szczodrzynski.navlib.drawer.NavDrawer + +class NavBottomBar : BottomAppBar { + constructor(context: Context) : super(context) { + create(null, 0) + } + + constructor(context: Context, attrs: AttributeSet) : super(context, attrs) { + create(attrs, 0) + } + + constructor(context: Context, attrs: AttributeSet, defStyle: Int) : super(context, attrs, defStyle) { + create(attrs, defStyle) + } + + var drawer: NavDrawer? = null + var bottomSheet: NavBottomSheet? = null + var fabView: FloatingActionButton? = null + var fabExtendedView: ExtendedFloatingActionButton? = null + + /** + * Shows the BottomAppBar and sets the contentView's margin to be + * above the BottomAppBar. + */ + var enable = true + set(value) { + field = value + visibility = if (value) View.VISIBLE else View.GONE + setFabParams() + } + /** + * Whether the FAB should be visible. + */ + var fabEnable = true + set(value) { + field = value + setFabVisibility() + } + /** + * Whether an ExtendableFloatingActionButton should be used + * instead of a normal FloatingActionButton. + * Note that the extendable button does not support end alignment/gravity + * when used together with the bottom app bar. + */ + var fabExtendable = true + set(value) { + field = value + setFabParams() + } + /** + * If BottomAppBar is enabled, sets its fabAlignmentMode. + * Else, sets the actual FAB's gravity. + */ + var fabGravity = Gravity.CENTER + set(value) { + field = value + setFabParams() + } + /** + * Whether the FAB should be extended and its text visible. + */ + var fabExtended = false + set(value) { + field = value + if (fabExtended) + fabExtendedView?.extend() + else + fabExtendedView?.shrink() + } + /** + * Set the FAB's icon. + */ + var fabIcon: IIcon? = null + set(value) { + field = value + fabView?.setImageDrawable(IconicsDrawable(context).apply { + icon = value + colorAttr(context, R.attr.colorFabIcon) + sizeDp = 24 + }) + fabExtendedView?.icon = IconicsDrawable(context).apply { + icon = value + colorAttr(context, R.attr.colorFabIcon) + sizeDp = 24 + } + } + /** + * Set the ExtendedFAB's text. + */ + var fabExtendedText + get() = fabExtendedView?.text + set(value) { + fabExtendedView?.text = value + } + + /** + * Set the FAB's on click listener + */ + fun setFabOnClickListener(onClickListener: OnClickListener?) { + fabView?.setOnClickListener(onClickListener) + fabExtendedView?.setOnClickListener(onClickListener) + } + + @SuppressLint("ClickableViewAccessibility") + private fun create(attrs: AttributeSet?, defStyle: Int) { + setOnTouchListener { _, event -> + if (bottomSheet?.enable != true || bottomSheet?.enableDragToOpen != true) + return@setOnTouchListener false + bottomSheet?.dispatchBottomBarEvent(event) + true + } + + elevation = 0f + + val icon = ContextCompat.getDrawable(context, R.drawable.ic_menu_badge) as LayerDrawable? + icon?.apply { + mutate() + setDrawableByLayerId(R.id.ic_menu, IconicsDrawable(context).apply { + this.icon = NavLibFont.Icon.nav_menu + sizeDp = 24 + colorAttr(context, R.attr.colorOnPrimary) + }) + setDrawableByLayerId(R.id.ic_badge, BadgeDrawable(context)) + } + navigationIcon = icon + + menu.add(0, -1, 0, "Menu") + .setIcon(IconicsDrawable(context).apply { + this.icon = NavLibFont.Icon.nav_dots_vertical + sizeDp = 24 + colorAttr(context, R.attr.colorOnPrimary) + }) + .setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS) + + setNavigationOnClickListener { + drawer?.toggle() + } + + super.setOnMenuItemClickListener { + if (it.itemId == -1 && bottomSheet?.enable == true) { + bottomSheet?.toggle() + } + else { + onMenuItemClickListener?.onMenuItemClick(it) + } + true + } + } + + private fun setFabParams() { + val layoutParams = + ((if (fabExtendable) fabExtendedView?.layoutParams else fabView?.layoutParams) ?: return) as CoordinatorLayout.LayoutParams + + if (enable) { + layoutParams.anchorId = this.id + if (fabExtendable) + layoutParams.anchorGravity = if (fabExtendable) fabGravity or Gravity.TOP else Gravity.NO_GRAVITY + layoutParams.gravity = Gravity.NO_GRAVITY + } + else { + layoutParams.anchorId = View.NO_ID + if (fabExtendable) + layoutParams.anchorGravity = Gravity.NO_GRAVITY + layoutParams.gravity = fabGravity or Gravity.BOTTOM + } + fabAlignmentMode = if (fabGravity == Gravity.END) FAB_ALIGNMENT_MODE_END else FAB_ALIGNMENT_MODE_CENTER + if (fabExtendable) + fabExtendedView?.layoutParams = layoutParams + else + fabView?.layoutParams = layoutParams + setFabVisibility() + } + private fun setFabVisibility() { + if (fabEnable && fabExtendable) { + fabView?.hide() + fabExtendedView?.show() + } + else if (fabEnable) { + fabView?.show() + fabExtendedView?.hide() + } + else { + fabView?.hide() + fabExtendedView?.hide() + } + } + + private var onMenuItemClickListener: OnMenuItemClickListener? = null + override fun setOnMenuItemClickListener(listener: OnMenuItemClickListener?) { + onMenuItemClickListener = listener + } +} diff --git a/navlib/src/main/java/pl/szczodrzynski/navlib/NavToolbar.kt b/navlib/src/main/java/pl/szczodrzynski/navlib/NavToolbar.kt new file mode 100644 index 00000000..1bc88813 --- /dev/null +++ b/navlib/src/main/java/pl/szczodrzynski/navlib/NavToolbar.kt @@ -0,0 +1,55 @@ +package pl.szczodrzynski.navlib + +import android.content.Context +import android.util.AttributeSet +import android.widget.ImageView +import com.google.android.material.appbar.MaterialToolbar + +class NavToolbar : MaterialToolbar { + + constructor(context: Context) : super(context) { + create(null, 0) + } + + constructor(context: Context, attrs: AttributeSet) : super(context, attrs) { + create(attrs, 0) + } + + constructor(context: Context, attrs: AttributeSet, defStyle: Int) : super(context, attrs, defStyle) { + create(attrs, defStyle) + } + + var toolbarImage: ImageView? = null + set(value) { + field = value + toolbarImage?.setOnClickListener { + profileImageClickListener?.invoke() + } + } + + override fun setSubtitle(subtitle: CharSequence?) { + if(subtitle.isNullOrEmpty()) { + setPadding(0, 0, 0, 0) + toolbarImage?.translationY = 0f + } else { + setPadding(0, -1, 0, 5) + toolbarImage?.translationY = 6f + } + super.setSubtitle(subtitle) + } + + private fun create(attrs: AttributeSet?, defStyle: Int) { + + } + + var subtitleFormat: Int? = null + var subtitleFormatWithUnread: Int? = null + + var profileImageClickListener: (() -> Unit)? = null + + var profileImage + get() = toolbarImage?.drawable + set(value) { + toolbarImage?.setImageDrawable(value) + } +} \ No newline at end of file diff --git a/navlib/src/main/java/pl/szczodrzynski/navlib/NavView.kt b/navlib/src/main/java/pl/szczodrzynski/navlib/NavView.kt new file mode 100644 index 00000000..496597c3 --- /dev/null +++ b/navlib/src/main/java/pl/szczodrzynski/navlib/NavView.kt @@ -0,0 +1,213 @@ +package pl.szczodrzynski.navlib + +import android.content.Context +import android.content.res.Configuration +import android.content.res.Configuration.ORIENTATION_PORTRAIT +import android.graphics.Point +import android.util.AttributeSet +import android.util.Log +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.view.ViewGroup.LayoutParams.MATCH_PARENT +import android.widget.FrameLayout +import android.widget.LinearLayout +import androidx.coordinatorlayout.widget.CoordinatorLayout +import androidx.core.view.children +import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton +import com.google.android.material.floatingactionbutton.FloatingActionButton +import kotlinx.android.synthetic.main.nav_view.view.* +import pl.szczodrzynski.navlib.bottomsheet.NavBottomSheet +import pl.szczodrzynski.navlib.drawer.NavDrawer + + +class NavView : FrameLayout { + companion object { + const val SOURCE_OTHER = 0 + const val SOURCE_DRAWER = 1 + const val SOURCE_BOTTOM_SHEET = 1 + } + + private var contentView: LinearLayout? = null + + private lateinit var statusBarBackground: View + private lateinit var navigationBarBackground: View + private lateinit var mainView: LinearLayout + private lateinit var floatingActionButton: FloatingActionButton + private lateinit var extendedFloatingActionButton: ExtendedFloatingActionButton + + lateinit var drawer: NavDrawer + lateinit var toolbar: NavToolbar + lateinit var bottomBar: NavBottomBar + lateinit var bottomSheet: NavBottomSheet + val coordinator by lazy { + findViewById(R.id.nv_coordinator) + } + + var navigationLoader: NavigationLoader? = null + + constructor(context: Context) : super(context) { + create(null, 0) + } + + constructor(context: Context, attrs: AttributeSet) : super(context, attrs) { + create(attrs, 0) + } + + constructor(context: Context, attrs: AttributeSet, defStyle: Int) : super(context, attrs, defStyle) { + create(attrs, defStyle) + } + + private fun create(attrs: AttributeSet?, defStyle: Int) { + // Load attributes + val a = context.obtainStyledAttributes(attrs, R.styleable.NavView, defStyle, 0) + /*_exampleString = a.getString( + R.styleable.NavView_exampleString + )*/ + a.recycle() + + val layoutInflater = LayoutInflater.from(context) + layoutInflater.inflate(R.layout.nav_view, this) + + contentView = findViewById(R.id.nv_content) + + statusBarBackground = findViewById(R.id.nv_statusBarBackground) + navigationBarBackground = findViewById(R.id.nv_navigationBarBackground) + mainView = findViewById(R.id.nv_main) + floatingActionButton = findViewById(R.id.nv_floatingActionButton) + extendedFloatingActionButton = findViewById(R.id.nv_extendedFloatingActionButton) + + drawer = NavDrawer( + context, + findViewById(R.id.nv_drawerLayout), + findViewById(R.id.nv_drawerContainerLandscape), + findViewById(R.id.nv_miniDrawerContainerPortrait), + findViewById(R.id.nv_miniDrawerElevation) + ) + toolbar = findViewById(R.id.nv_toolbar) + bottomBar = findViewById(R.id.nv_bottomBar) + bottomSheet = findViewById(R.id.nv_bottomSheet) + + drawer.toolbar = toolbar + drawer.bottomBar = bottomBar + + toolbar.toolbarImage = findViewById(R.id.nv_toolbar_image) + + bottomBar.drawer = drawer + bottomBar.bottomSheet = bottomSheet + bottomBar.fabView = floatingActionButton + bottomBar.fabExtendedView = extendedFloatingActionButton + + ripple.isEnabled = false + ripple.children.forEach { it.isEnabled = false } + + //bottomSheetBehavior.peekHeight = displayHeight + } + + private fun convertDpToPixel(dp: Float): Float { + val resources = context.resources + val metrics = resources.displayMetrics + return dp * (metrics.densityDpi / 160f) + } + + fun gainAttentionOnBottomBar() { + var x = ripple.width.toFloat() + var y = ripple.height.toFloat() + x -= convertDpToPixel(56f) / 2 + y -= convertDpToPixel(56f) / 2 + ripple.performRipple(Point(x.toInt(), y.toInt())) + } + + fun configSystemBarsUtil(systemBarsUtil: SystemBarsUtil) { + this.systemBarsUtil = systemBarsUtil.apply { + this.statusBarBgView = statusBarBackground + this.navigationBarBgView = navigationBarBackground + this.statusBarDarkView = nv_statusBarDarker + //this.navigationBarDarkView = navigationBarBackground + this.insetsListener = nv_drawerLayout + this.marginBySystemBars = mainView + this.paddingByNavigationBar = bottomSheet.getContentView() + } + } + + + var enableBottomSheet = true + var enableBottomSheetDrag = true + + var bottomBarEnable = true + get() = bottomBar.enable + set(value) { + field = value + bottomBar.enable = value + setContentMargins() // TODO combine bottomBarEnable and bottomBar.enable + } + + /** + * Shows the toolbar and sets the contentView's margin to be + * below the toolbar. + */ + var showToolbar = true; set(value) { + toolbar.visibility = if (value) View.VISIBLE else View.GONE + field = value + setContentMargins() + } + + /** + * Set the FAB's on click listener + */ + fun setFabOnClickListener(onClickListener: OnClickListener?) { + bottomBar.setFabOnClickListener(onClickListener) + } + + internal var systemBarsUtil: SystemBarsUtil? = null + + private fun setContentMargins() { + val layoutParams = CoordinatorLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT) + val actionBarSize = 56 * context.resources.displayMetrics.density + layoutParams.topMargin = if (showToolbar) actionBarSize.toInt() else 0 + layoutParams.bottomMargin = if (bottomBarEnable) actionBarSize.toInt() else 0 + contentView?.layoutParams = layoutParams + } + + override fun onConfigurationChanged(newConfig: Configuration?) { + super.onConfigurationChanged(newConfig) + + Log.d( + "NavLib", + "CONFIGURATION CHANGED: ${newConfig?.screenWidthDp}x${newConfig?.screenHeightDp} "+if (newConfig?.orientation == ORIENTATION_PORTRAIT) "portrait" else "landscape" + ) + + systemBarsUtil?.commit() + + drawer.decideDrawerMode( + newConfig?.orientation ?: ORIENTATION_PORTRAIT, + newConfig?.screenWidthDp ?: 0, + newConfig?.screenHeightDp ?: 0 + ) + } + + fun onBackPressed(): Boolean { + if (drawer.isOpen && !drawer.fixedDrawerEnabled()) { + if (drawer.profileSelectionIsOpen) { + drawer.profileSelectionClose() + return true + } + drawer.close() + return true + } + if (bottomSheet.isOpen) { + bottomSheet.close() + return true + } + return false + } + + override fun addView(child: View?, index: Int, params: ViewGroup.LayoutParams?) { + if (contentView == null) { + super.addView(child, index, params) + } + else { + contentView!!.addView(child, index, params) + } + } +} diff --git a/navlib/src/main/java/pl/szczodrzynski/navlib/NavigationLoader.kt b/navlib/src/main/java/pl/szczodrzynski/navlib/NavigationLoader.kt new file mode 100644 index 00000000..09428d74 --- /dev/null +++ b/navlib/src/main/java/pl/szczodrzynski/navlib/NavigationLoader.kt @@ -0,0 +1,5 @@ +package pl.szczodrzynski.navlib + +interface NavigationLoader { + fun load(itemId: Int, callerId: Int, source: Int, args: Map) +} \ No newline at end of file diff --git a/navlib/src/main/java/pl/szczodrzynski/navlib/SystemBarsUtil.kt b/navlib/src/main/java/pl/szczodrzynski/navlib/SystemBarsUtil.kt new file mode 100644 index 00000000..07d4501c --- /dev/null +++ b/navlib/src/main/java/pl/szczodrzynski/navlib/SystemBarsUtil.kt @@ -0,0 +1,376 @@ +package pl.szczodrzynski.navlib + +import android.app.Activity +import android.content.res.Configuration.ORIENTATION_PORTRAIT +import android.content.res.Resources +import android.graphics.Color +import android.os.Build.VERSION.SDK_INT +import android.os.Build.VERSION_CODES +import android.util.Log +import android.view.View +import android.view.View.* +import android.view.Window +import android.view.WindowManager +import androidx.core.graphics.ColorUtils +import androidx.core.view.ViewCompat +import com.mikepenz.materialize.util.KeyboardUtil + + +class SystemBarsUtil(private val activity: Activity) { + companion object { + private const val COLOR_TRANSPARENT = Color.TRANSPARENT + /** + * A fallback color. + * Tells to apply a #22000000 overlay over the status/nav bar color. + * This has the same effect as [statusBarDarker]. + */ + const val COLOR_HALF_TRANSPARENT = -1 + /** + * Use ?colorPrimaryDark as a fallback or status bar color. + */ + const val COLOR_PRIMARY_DARK = -2 + /** + * A fallback color. + * Not recommended to use as [statusBarFallbackLight] because it will make status bar + * icons almost invisible. + */ + const val COLOR_DO_NOT_CHANGE = -3 + + private const val TARGET_MODE_NORMAL = 0 + private const val TARGET_MODE_LIGHT = 1 + private const val TARGET_MODE_GRADIENT = 2 + } + + val window: Window by lazy { + activity.window + } + val resources: Resources by lazy { + activity.resources + } + + /** + * A view which will have the padding added when the soft input keyboard appears. + */ + var paddingByKeyboard: View? = null + /** + * Whether the app should be fullscreen. + * + * This means it will display under the system bars + * and you should probably provide [statusBarBgView], + * [navigationBarBgView] and [marginBySystemBars]. + */ + var appFullscreen = false + + /** + * Define the color used to tint the status bar background. + * + * Valid values are [COLOR_PRIMARY_DARK] or a color integer. + * + * You cannot use neither [COLOR_HALF_TRANSPARENT] nor [COLOR_DO_NOT_CHANGE] here. + * See [statusBarDarker]. + */ + var statusBarColor = COLOR_PRIMARY_DARK + /** + * Whether the status bar should have a dark overlay (#22000000). + * + * Useful if the [statusBarColor] is set to a bright color and is the same as an action bar. + * Not useful if [statusBarColor] is [COLOR_PRIMARY_DARK]. + */ + var statusBarDarker = false + /** + * A fallback status bar color used on Android Lollipop + * when the [statusBarColor] combined with [statusBarDarker] is + * too bright not to blend with status bar icons (they cannot be + * set to dark). + * + * This will (most likely) not be used when [statusBarDarker] is true. + * + * Valid values are [COLOR_HALF_TRANSPARENT], [COLOR_PRIMARY_DARK], [COLOR_DO_NOT_CHANGE]. + */ + var statusBarFallbackLight = COLOR_HALF_TRANSPARENT + /** + * A fallback status bar color used on Android KitKat and older. + * On these systems there is a black-to-transparent gradient as + * the status bar background. + * + * Valid values are [COLOR_HALF_TRANSPARENT], [COLOR_PRIMARY_DARK], [COLOR_DO_NOT_CHANGE]. + */ + var statusBarFallbackGradient = COLOR_DO_NOT_CHANGE + + // TODO remove - test for huawei + var statusBarTranslucent = false + + /** + * If false, the nav bar is mostly translucent but not completely transparent. + */ + var navigationBarTransparent = true + + /** + * A background view to be resized in order to fit under the status bar. + */ + var statusBarBgView: View? = null + /** + * A background view to be resized in order to fit under the nav bar. + */ + var navigationBarBgView: View? = null + + /** + * A dark, half-transparent view to be resized in order to fit under the status bar. + */ + var statusBarDarkView: View? = null + /** + * A dark, half-transparent view to be resized in order to fit under the nav bar. + */ + var navigationBarDarkView: View? = null + + /** + * A view which will have the margin added not to overlap with the status/nav bar. + */ + var marginBySystemBars: View? = null + /** + * A view which will listen to the inset applying. + */ + var insetsListener: View? = null + /** + * A view which will have the padding added not to overlap with the nav bar. + * Useful for persistent bottom sheets. + * Requires [marginBySystemBars]. + */ + var paddingByNavigationBar: View? = null + + private var keyboardUtil: KeyboardUtil? = null + private var insetsApplied = false + + fun commit() { + Log.d("NavLib", "SystemBarsUtil applying") + insetsApplied = false + if (paddingByKeyboard != null) { + // thanks mikepenz for this life-saving class + keyboardUtil = KeyboardUtil(activity, paddingByKeyboard) + keyboardUtil?.enable() + } + + // get the correct target SB color + var targetStatusBarColor = statusBarColor + if (targetStatusBarColor == COLOR_PRIMARY_DARK) + targetStatusBarColor = getColorFromAttr(activity, R.attr.colorPrimaryDark) + + var targetStatusBarDarker = statusBarDarker + + // fallback if the SB color is too light for the icons to be visible + // applicable on Lollipop 5.0 and TouchWiz 4.1-4.3 + var targetStatusBarFallbackLight = statusBarFallbackLight + if (targetStatusBarFallbackLight == COLOR_PRIMARY_DARK) + targetStatusBarFallbackLight = getColorFromAttr(activity, R.attr.colorPrimaryDark) + + // fallback if there is a gradient under the status bar + // applicable on AOSP/similar 4.4 and Huawei EMUI Lollipop + // TODO check huawei 6.0+ for gradient bars, check huawei 4.4 + var targetStatusBarFallbackGradient = statusBarFallbackGradient + if (targetStatusBarFallbackGradient == COLOR_PRIMARY_DARK) + targetStatusBarFallbackGradient = getColorFromAttr(activity, R.attr.colorPrimaryDark) + + // determines the target mode that will be applied + var targetStatusBarMode = TARGET_MODE_NORMAL + + val targetStatusBarLight = ColorUtils.calculateLuminance(targetStatusBarColor) > 0.75 && !targetStatusBarDarker + + if (appFullscreen) { + window.decorView.systemUiVisibility = 0 + // API 19+ (KitKat 4.4+) - make the app fullscreen. + // On lower APIs this is useless because + // #1 the status/nav bar cannot be transparent (except Samsung TouchWiz) + // #2 tablets do not report status/nav bar height correctly + // #3 SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN makes the activity not resize when keyboard is open + // Samsung TouchWiz - app will go fullscreen. There is a problem though, see #3. + var targetAppFullscreen = false + if (SDK_INT >= VERSION_CODES.KITKAT) { + targetAppFullscreen = true + } + + + if (SDK_INT in VERSION_CODES.KITKAT until VERSION_CODES.LOLLIPOP) { + // API 19-20 (KitKat 4.4) - set gradient status bar + window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS) + // take FallbackGradient color + targetStatusBarMode = TARGET_MODE_GRADIENT + // disable darker even if [statusBarDarker] == true BUT gradient fallback is not COLOR_HALF_TRANSPARENT + //targetStatusBarDarker = targetStatusBarDarker && targetStatusBarFallbackGradient == COLOR_HALF_TRANSPARENT + } + else if (SDK_INT >= VERSION_CODES.LOLLIPOP) { + // API 21+ (Lollipop 5.0+) - set transparent status bar + if (statusBarTranslucent) { + window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS) + } + else { + window.statusBarColor = Color.TRANSPARENT + } + if (SDK_INT < VERSION_CODES.M && targetStatusBarLight) { + // take FallbackLight color + targetStatusBarMode = TARGET_MODE_LIGHT + } + } + if (SDK_INT >= VERSION_CODES.M && targetStatusBarLight) { + // API 23+ (Marshmallow 6.0+) - set the status bar icons to dark color if [statusBarLight] is true + window.decorView.systemUiVisibility = window.decorView.systemUiVisibility or SYSTEM_UI_FLAG_LIGHT_STATUS_BAR + } + // FOR SAMSUNG/SONY DEVICES (TouchWiz 4.1-4.3) + if (SDK_INT < VERSION_CODES.KITKAT) { + val libs = activity.packageManager.systemSharedLibraryNames + var reflect: String? = null + // TODO galaxy s3 - opening keyboard does not resize activity if fullscreen + if (libs != null) { + for (lib in libs) { + Log.d("SBU", lib) + if (lib == "touchwiz") + // SYSTEM_UI_FLAG_TRANSPARENT_BACKGROUND = 0x00001000 + reflect = "SYSTEM_UI_FLAG_TRANSPARENT_BACKGROUND" + else if (lib.startsWith("com.sonyericsson.navigationbar")) + reflect = "SYSTEM_UI_FLAG_TRANSPARENT" + } + if (reflect != null) { + try { + val field = View::class.java.getField(reflect) + var flag = 0 + if (field.type === Integer.TYPE) + flag = field.getInt(null) + if (flag != 0) { + window.decorView.systemUiVisibility = window.decorView.systemUiVisibility or flag + targetStatusBarMode = TARGET_MODE_LIGHT /* or TARGET_MODE_GRADIENT */ + targetAppFullscreen = true + } + } catch (e: Exception) { + } + } + } + } + // TODO huawei detection for 5.0+ + + targetStatusBarColor = when (targetStatusBarMode) { + TARGET_MODE_LIGHT -> when (targetStatusBarFallbackLight) { + COLOR_DO_NOT_CHANGE -> targetStatusBarColor + COLOR_HALF_TRANSPARENT -> { + targetStatusBarDarker = true + targetStatusBarColor + } + else -> targetStatusBarFallbackLight + } + TARGET_MODE_GRADIENT -> when (targetStatusBarFallbackGradient) { + COLOR_DO_NOT_CHANGE -> { + targetStatusBarDarker = false + targetStatusBarColor + } + COLOR_HALF_TRANSPARENT -> { + targetStatusBarDarker = true + targetStatusBarColor + } + else -> { + targetStatusBarDarker = false + targetStatusBarFallbackGradient + } + } + else -> targetStatusBarColor + } + + statusBarBgView?.setBackgroundColor(targetStatusBarColor) + statusBarDarkView?.visibility = if (targetStatusBarDarker) VISIBLE else GONE + + if (targetAppFullscreen) { + window.decorView.systemUiVisibility = window.decorView.systemUiVisibility or SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION + } + + // TODO navigation bar options like status bar + // NAVIGATION BAR + if (SDK_INT >= VERSION_CODES.KITKAT && (SDK_INT < VERSION_CODES.LOLLIPOP || !navigationBarTransparent)) { + // API 19-20 (KitKat 4.4) - set gradient navigation bar + // API 21+ (Lollipop 5.0+) - set half-transparent navigation bar if [navigationBarTransparent] is false + window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION) + } + + if (SDK_INT >= VERSION_CODES.LOLLIPOP && navigationBarTransparent) { + // API 21+ (Lollipop 5.0+) - set fully transparent navigation bar if [navigationBarTransparent] is true + window.navigationBarColor = Color.TRANSPARENT + } + + // PADDING + if (insetsListener != null) { + if (SDK_INT >= VERSION_CODES.LOLLIPOP && false) { + ViewCompat.setOnApplyWindowInsetsListener(insetsListener!!) { _, insets -> + Log.d("NavLib", "Got insets left = ${insets.systemWindowInsetLeft}, top = ${insets.systemWindowInsetTop}, right = ${insets.systemWindowInsetRight}, bottom = ${insets.systemWindowInsetBottom}") + if (insetsApplied) + return@setOnApplyWindowInsetsListener insets.consumeSystemWindowInsets() + Log.d("NavLib", "Applied insets left = ${insets.systemWindowInsetLeft}, top = ${insets.systemWindowInsetTop}, right = ${insets.systemWindowInsetRight}, bottom = ${insets.systemWindowInsetBottom}") + insetsApplied = true + applyPadding( + insets.systemWindowInsetLeft, + insets.systemWindowInsetTop, + insets.systemWindowInsetRight, + insets.systemWindowInsetBottom + ) + insets.consumeSystemWindowInsets() + } + } + else { + var statusBarSize = 0 + val statusBarRes = resources.getIdentifier("status_bar_height", "dimen", "android") + if (statusBarRes > 0 && targetAppFullscreen) { + statusBarSize = resources.getDimensionPixelSize(statusBarRes) + } + + + var navigationBarSize = 0 + if (hasNavigationBar(activity) && targetAppFullscreen) { + val orientation = resources.configuration.orientation + + val navigationBarRes = when { + orientation == ORIENTATION_PORTRAIT -> + resources.getIdentifier("navigation_bar_height", "dimen", "android") + isTablet(activity) -> + resources.getIdentifier("navigation_bar_height_landscape", "dimen", "android") + else -> + resources.getIdentifier("navigation_bar_width", "dimen", "android") + } + + if (navigationBarRes > 0) { + navigationBarSize = resources.getDimensionPixelSize(navigationBarRes) + } + } + + applyPadding( + 0, + statusBarSize, + 0, + navigationBarSize + ) + } + } + } + else { + // app not fullscreen + // TODO statusBarColor & navigationBarColor if not fullscreen (it's possible) + } + } + + private fun applyPadding(left: Int, top: Int, right: Int, bottom: Int) { + marginBySystemBars?.setPadding(left, top, right, bottom) + + statusBarBgView?.layoutParams?.height = top + navigationBarBgView?.layoutParams?.height = bottom + + statusBarDarkView?.layoutParams?.height = top + navigationBarDarkView?.layoutParams?.height = bottom + + paddingByNavigationBar?.setPadding( + (8 * resources.displayMetrics.density).toInt(), + 0, + (8 * resources.displayMetrics.density).toInt(), + bottom + ) + } + + fun destroy() { + if (paddingByKeyboard != null) { + keyboardUtil?.disable() + } + } +} diff --git a/navlib/src/main/java/pl/szczodrzynski/navlib/Utils.kt b/navlib/src/main/java/pl/szczodrzynski/navlib/Utils.kt new file mode 100644 index 00000000..fc324f77 --- /dev/null +++ b/navlib/src/main/java/pl/szczodrzynski/navlib/Utils.kt @@ -0,0 +1,164 @@ +package pl.szczodrzynski.navlib + +import android.app.Activity +import android.content.Context +import android.content.res.Configuration +import android.graphics.drawable.Drawable +import android.os.Build +import android.util.DisplayMetrics +import android.util.TypedValue +import android.view.View +import android.view.WindowManager +import androidx.annotation.AttrRes +import androidx.annotation.ColorInt +import androidx.annotation.ColorRes +import androidx.annotation.DrawableRes +import com.google.android.material.elevation.ElevationOverlayProvider +import com.mikepenz.iconics.IconicsColor +import com.mikepenz.iconics.IconicsDrawable +import com.mikepenz.iconics.utils.colorInt + + +/*private val displayMetrics by lazy { + context.resources.displayMetrics +}*/ +/*private val configuration by lazy { context.resources.configuration } +private val displayWidth: Int by lazy { configuration.screenWidthDp } +private val displayHeight: Int by lazy { configuration.screenHeightDp }*/ + +fun getTopInset(context: Context, view: View): Float { + return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + (view.rootWindowInsets?.systemWindowInsetTop ?: 24) + } else { + 24 + } * context.resources.displayMetrics.density +} +fun getLeftInset(context: Context, view: View): Float { + return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + (view.rootWindowInsets?.systemWindowInsetLeft ?: 0) + } else { + 0 + } * context.resources.displayMetrics.density +} +fun getRightInset(context: Context, view: View): Float { + return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + (view.rootWindowInsets?.systemWindowInsetRight ?: 0) + } else { + 0 + } * context.resources.displayMetrics.density +} +fun getBottomInset(context: Context, view: View): Float { + return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + (view.rootWindowInsets?.systemWindowInsetBottom ?: 48) + } else { + 48 + } * context.resources.displayMetrics.density +} + +fun View.getActivity(): Activity { + return findViewById(android.R.id.content).context as Activity +} + +fun blendColors(background: Int, foreground: Int): Int { + val r1 = (background shr 16 and 0xff) + val g1 = (background shr 8 and 0xff) + val b1 = (background and 0xff) + + val r2 = (foreground shr 16 and 0xff) + val g2 = (foreground shr 8 and 0xff) + val b2 = (foreground and 0xff) + val a2 = (foreground shr 24 and 0xff) + //ColorUtils.compositeColors() + + val factor = a2.toFloat() / 255f + val red = (r1 * (1 - factor) + r2 * factor) + val green = (g1 * (1 - factor) + g2 * factor) + val blue = (b1 * (1 - factor) + b2 * factor) + + return (0xff000000 or (red.toLong() shl 16) or (green.toLong() shl 8) or (blue.toLong())).toInt() +} + +fun elevateSurface(context: Context, dp: Int): Int { + ElevationOverlayProvider(context).apply { + return compositeOverlay(themeSurfaceColor, dp * context.resources.displayMetrics.density) + } +} + +fun isTablet(c: Context): Boolean { + return (c.resources.configuration.screenLayout and Configuration.SCREENLAYOUT_SIZE_MASK) >= Configuration.SCREENLAYOUT_SIZE_LARGE +} + +fun hasNavigationBar(context: Context): Boolean { + val id = context.resources.getIdentifier("config_showNavigationBar", "bool", "android") + var hasNavigationBar = id > 0 && context.resources.getBoolean(id) + + if (!hasNavigationBar && Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { + val d = (context.getSystemService(Context.WINDOW_SERVICE) as WindowManager).defaultDisplay + + val realDisplayMetrics = DisplayMetrics() + d.getRealMetrics(realDisplayMetrics) + + val realHeight = realDisplayMetrics.heightPixels + val realWidth = realDisplayMetrics.widthPixels + + val displayMetrics = DisplayMetrics() + d.getMetrics(displayMetrics) + + val displayHeight = displayMetrics.heightPixels + val displayWidth = displayMetrics.widthPixels + + hasNavigationBar = realWidth - displayWidth > 0 || realHeight - displayHeight > 0 + } + + // Allow a system property to override this. Used by the emulator. + // See also hasNavigationBar(). + val navBarOverride = System.getProperty("qemu.hw.mainkeys") + if (navBarOverride == "1") + hasNavigationBar = true + else if (navBarOverride == "0") hasNavigationBar = false + + return hasNavigationBar +} + +fun IconicsDrawable.colorAttr(context: Context, @AttrRes attrRes: Int) { + colorInt = getColorFromAttr(context, attrRes) +} + +fun getColorFromAttr(context: Context, @AttrRes color: Int): Int { + val typedValue = TypedValue() + context.theme.resolveAttribute(color, typedValue, true) + return typedValue.data +} + +fun Context.getDrawableFromRes(@DrawableRes id: Int): Drawable { + return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + resources.getDrawable(id, theme) + } + else { + resources.getDrawable(id) + } +} + +@ColorInt +fun Context.getColorFromRes(@ColorRes id: Int): Int { + return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + resources.getColor(id, theme) + } + else { + resources.getColor(id) + } +} + +fun crc16(buffer: String): Int { + /* Note the change here */ + var crc = 0x1D0F + for (j in buffer) { + crc = crc.ushr(8) or (crc shl 8) and 0xffff + crc = crc xor (j.toInt() and 0xff)//byte to int, trunc sign + crc = crc xor (crc and 0xff shr 4) + crc = crc xor (crc shl 12 and 0xffff) + crc = crc xor (crc and 0xFF shl 5 and 0xffff) + } + crc = crc and 0xffff + return crc +} diff --git a/navlib/src/main/java/pl/szczodrzynski/navlib/bottomsheet/BottomSheetAdapter.kt b/navlib/src/main/java/pl/szczodrzynski/navlib/bottomsheet/BottomSheetAdapter.kt new file mode 100644 index 00000000..bd2171d8 --- /dev/null +++ b/navlib/src/main/java/pl/szczodrzynski/navlib/bottomsheet/BottomSheetAdapter.kt @@ -0,0 +1,38 @@ +package pl.szczodrzynski.navlib.bottomsheet + +import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView +import pl.szczodrzynski.navlib.R +import pl.szczodrzynski.navlib.bottomsheet.items.IBottomSheetItem +import pl.szczodrzynski.navlib.bottomsheet.items.BottomSheetPrimaryItem +import pl.szczodrzynski.navlib.bottomsheet.items.BottomSheetSeparatorItem + +class BottomSheetAdapter(val items: List>) : RecyclerView.Adapter() { + + private val viewHolderProvider = ViewHolderProvider() + + init { + viewHolderProvider.registerViewHolderFactory(1, R.layout.nav_bs_item_primary) { itemView -> + BottomSheetPrimaryItem.ViewHolder(itemView) + } + viewHolderProvider.registerViewHolderFactory(2, R.layout.nav_bs_item_separator) { itemView -> + BottomSheetSeparatorItem.ViewHolder(itemView) + } + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { + return viewHolderProvider.provideViewHolder(viewGroup = parent, viewType = viewType) + } + + override fun getItemViewType(position: Int): Int { + return items[position].viewType + } + + override fun getItemCount(): Int { + return items.size + } + + override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { + items[position].bindViewHolder(viewHolder = holder) + } +} \ No newline at end of file diff --git a/navlib/src/main/java/pl/szczodrzynski/navlib/bottomsheet/NavBottomSheet.kt b/navlib/src/main/java/pl/szczodrzynski/navlib/bottomsheet/NavBottomSheet.kt new file mode 100644 index 00000000..024985ce --- /dev/null +++ b/navlib/src/main/java/pl/szczodrzynski/navlib/bottomsheet/NavBottomSheet.kt @@ -0,0 +1,438 @@ +package pl.szczodrzynski.navlib.bottomsheet + +import android.app.Activity +import android.content.Context +import android.graphics.PorterDuff +import android.graphics.PorterDuffColorFilter +import android.graphics.drawable.Drawable +import android.text.Editable +import android.text.TextWatcher +import android.util.AttributeSet +import android.view.LayoutInflater +import android.view.MotionEvent +import android.view.View +import android.view.ViewGroup.LayoutParams.WRAP_CONTENT +import android.view.inputmethod.InputMethodManager +import android.widget.LinearLayout +import android.widget.TextView +import androidx.annotation.DrawableRes +import androidx.coordinatorlayout.widget.CoordinatorLayout +import androidx.core.widget.NestedScrollView +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import com.google.android.material.bottomsheet.BottomSheetBehavior +import com.google.android.material.button.MaterialButton +import com.google.android.material.button.MaterialButtonToggleGroup +import com.google.android.material.textfield.TextInputEditText +import com.google.android.material.textfield.TextInputLayout +import com.mikepenz.iconics.IconicsDrawable +import com.mikepenz.iconics.typeface.IIcon +import com.mikepenz.iconics.typeface.library.navlibfont.NavLibFont +import com.mikepenz.iconics.utils.paddingDp +import com.mikepenz.iconics.utils.sizeDp +import pl.szczodrzynski.navlib.* +import pl.szczodrzynski.navlib.bottomsheet.items.BottomSheetSeparatorItem +import pl.szczodrzynski.navlib.bottomsheet.items.IBottomSheetItem + + +class NavBottomSheet : CoordinatorLayout { + companion object { + const val TOGGLE_GROUP_SINGLE_SELECTION = 0 + const val TOGGLE_GROUP_MULTIPLE_SELECTION = 1 + const val TOGGLE_GROUP_SORTING_ORDER = 2 + + const val SORT_MODE_ASCENDING = 0 + const val SORT_MODE_DESCENDING = 1 + } + + constructor(context: Context) : super(context) { + create(null, 0) + } + + constructor(context: Context, attrs: AttributeSet) : super(context, attrs) { + create(attrs, 0) + } + + constructor(context: Context, attrs: AttributeSet, defStyle: Int) : super(context, attrs, defStyle) { + create(attrs, defStyle) + } + + private lateinit var scrimView: View + private lateinit var bottomSheet: NestedScrollView + private lateinit var content: LinearLayout + private lateinit var dragBar: View + private lateinit var textInputLayout: TextInputLayout + private lateinit var textInputEditText: TextInputEditText + private lateinit var toggleGroupContainer: LinearLayout + private lateinit var toggleGroup: MaterialButtonToggleGroup + private lateinit var toggleGroupTitleView: TextView + private lateinit var list: RecyclerView + + private lateinit var bottomSheetBehavior: BottomSheetBehavior + private var bottomSheetVisible = false + + private val items = ArrayList>() + private val adapter = BottomSheetAdapter(items) + + /** + * Enable the bottom sheet. + * This value is mostly relevant to the [pl.szczodrzynski.navlib.NavBottomBar]. + */ + var enable = true + set(value) { + field = value + if (!value && bottomSheetVisible) + close() + } + /** + * Whether the [pl.szczodrzynski.navlib.NavBottomBar] should open this BottomSheet + * when the user drags the bottom bar. + */ + var enableDragToOpen = true + + /** + * Control the scrim view visibility, shown when BottomSheet + * is expanded. + */ + var scrimViewEnabled = true + set(value) { + scrimView.visibility = if (value) View.INVISIBLE else View.GONE // INVISIBLE + field = value + } + /** + * Whether tapping the Scrim view should hide the BottomSheet. + */ + var scrimViewTapToClose = true + + + fun hideKeyboard() { + val imm = context.getSystemService(Activity.INPUT_METHOD_SERVICE) as InputMethodManager + imm.hideSoftInputFromWindow(rootView.windowToken, 0) + } + + + private fun create(attrs: AttributeSet?, defStyle: Int) { + val layoutInflater = LayoutInflater.from(context) + layoutInflater.inflate(R.layout.nav_bottom_sheet, this) + + scrimView = findViewById(R.id.bs_scrim) + bottomSheet = findViewById(R.id.bs_view) + content = findViewById(R.id.bs_content) + dragBar = findViewById(R.id.bs_dragBar) + textInputLayout = findViewById(R.id.bs_textInputLayout) + textInputEditText = findViewById(R.id.bs_textInputEditText) + toggleGroupContainer = findViewById(R.id.bs_toggleGroupContainer) + toggleGroup = findViewById(R.id.bs_toggleGroup) + toggleGroupTitleView = findViewById(R.id.bs_toggleGroupTitle) + list = findViewById(R.id.bs_list) + + bottomSheetBehavior = BottomSheetBehavior.from(bottomSheet) + + bottomSheetBehavior.state = BottomSheetBehavior.STATE_HIDDEN + + scrimView.setOnTouchListener { _, event -> + if (!scrimViewTapToClose) + return@setOnTouchListener true + if (event.action == MotionEvent.ACTION_UP && bottomSheetBehavior.state != BottomSheetBehavior.STATE_HIDDEN) { + bottomSheetBehavior.state = BottomSheetBehavior.STATE_HIDDEN + } + true + } + + bottomSheetBehavior.setBottomSheetCallback(object : BottomSheetBehavior.BottomSheetCallback() { + override fun onSlide(v: View, p1: Float) {} + override fun onStateChanged(v: View, newState: Int) { + if (newState == BottomSheetBehavior.STATE_HIDDEN && bottomSheetVisible) { + bottomSheetVisible = false + bottomSheet.scrollTo(0, 0) + if (scrimViewEnabled) + Anim.fadeOut(scrimView, 300, null) + // steal the focus from any EditTexts + dragBar.requestFocus() + hideKeyboard() + onCloseListener?.invoke() + } + else if (!bottomSheetVisible) { + bottomSheetVisible = true + if (scrimViewEnabled) + Anim.fadeIn(scrimView, 300, null) + } + } + }) + + content.background.colorFilter = PorterDuffColorFilter( + elevateSurface(context, dp = 8), + PorterDuff.Mode.SRC_ATOP + ) + + // steal the focus from any EditTexts + dragBar.requestFocus() + + list.apply { + setHasFixedSize(true) + layoutManager = LinearLayoutManager(context) + adapter = this@NavBottomSheet.adapter + } + + toggleGroup.addOnButtonCheckedListener(toggleGroupCheckedListener) + textInputEditText.addTextChangedListener(textInputWatcher) + } + + var onCloseListener: (() -> Unit)? = null + + /* _____ _ + |_ _| | + | | | |_ ___ _ __ ___ ___ + | | | __/ _ \ '_ ` _ \/ __| + _| |_| || __/ | | | | \__ \ + |_____|\__\___|_| |_| |_|__*/ + operator fun plusAssign(item: IBottomSheetItem<*>) { + appendItem(item) + } + fun appendItem(item: IBottomSheetItem<*>) { + items.add(item) + adapter.notifyDataSetChanged() + //adapter.notifyItemInserted(items.size - 1) + } + fun appendItems(vararg items: IBottomSheetItem<*>) { + this.items.addAll(items) + adapter.notifyDataSetChanged() + //adapter.notifyItemRangeInserted(this.items.size - items.size, items.size) + } + fun prependItem(item: IBottomSheetItem<*>) { + items.add(0, item) + adapter.notifyDataSetChanged() + //adapter.notifyItemInserted(0) + } + fun prependItems(vararg items: IBottomSheetItem<*>) { + this.items.addAll(0, items.toList()) + adapter.notifyDataSetChanged() + //adapter.notifyItemRangeInserted(0, items.size) + } + fun addItemAt(index: Int, item: IBottomSheetItem<*>) { + items.add(index, item) + adapter.notifyDataSetChanged() + //adapter.notifyItemInserted(index) + } + fun removeItemById(id: Int) { + items.filterNot { it.id == id } + } + fun removeItemAt(index: Int) { + items.removeAt(index) + adapter.notifyDataSetChanged() + //adapter.notifyItemRemoved(index) + } + fun removeAllItems() { + items.clear() + adapter.notifyDataSetChanged() + } + fun removeAllStatic() { + items.removeAll { !it.isContextual } + adapter.notifyDataSetChanged() + } + fun removeAllContextual() { + items.removeAll { it.isContextual } + adapter.notifyDataSetChanged() + } + fun removeSeparators() { + items.removeAll { it is BottomSheetSeparatorItem } + adapter.notifyDataSetChanged() + } + + fun getItemById(id: Int, run: (it: IBottomSheetItem<*>?) -> Unit) { + items.singleOrNull { it.id == id }.also { + run(it) + if (it != null) + adapter.notifyItemChanged(items.indexOf(it)) + } + } + fun getItemByIndex(index: Int, run: (it: IBottomSheetItem<*>?) -> Unit) { + items.getOrNull(index).also { + run(it) + if (it != null) + adapter.notifyItemChanged(index) + } + } + + + /* _______ _ + |__ __| | | + | | ___ __ _ __ _| | ___ __ _ _ __ ___ _ _ _ __ + | |/ _ \ / _` |/ _` | |/ _ \ / _` | '__/ _ \| | | | '_ \ + | | (_) | (_| | (_| | | __/ | (_| | | | (_) | |_| | |_) | + |_|\___/ \__, |\__, |_|\___| \__, |_| \___/ \__,_| .__/ + __/ | __/ | __/ | | | + |___/ |___/ |___/ |*/ + var toggleGroupEnabled + get() = toggleGroupContainer.visibility == View.VISIBLE + set(value) { toggleGroupContainer.visibility = if (value) View.VISIBLE else View.GONE } + var toggleGroupTitle + get() = toggleGroupTitleView.text.toString() + set(value) { toggleGroupTitleView.text = value } + var toggleGroupSelectionMode: Int = TOGGLE_GROUP_SORTING_ORDER + set(value) { + field = value + toggleGroup.isSingleSelection = value != TOGGLE_GROUP_MULTIPLE_SELECTION + } + + private fun toggleGroupGetIconicsDrawable(context: Context, icon: IIcon?): Drawable? { + if (icon == null) + return null + return IconicsDrawable(context, icon).apply { + sizeDp = 24 + } + } + + fun toggleGroupAddItem(id: Int, text: String, @DrawableRes icon: Int, defaultSortOrder: Int = SORT_MODE_ASCENDING) { + toggleGroupAddItem(id, text, context.getDrawableFromRes(icon), defaultSortOrder) + } + fun toggleGroupAddItem(id: Int, text: String, icon: IIcon, defaultSortOrder: Int = SORT_MODE_ASCENDING) { + toggleGroupAddItem(id, text, toggleGroupGetIconicsDrawable(context, icon), defaultSortOrder) + } + fun toggleGroupAddItem(id: Int, text: String, icon: Drawable?, defaultSortOrder: Int = SORT_MODE_ASCENDING) { + if (id < 0) + throw IllegalArgumentException("ID cannot be less than 0") + toggleGroup.addView(MaterialButton(context, null, R.attr.materialButtonOutlinedStyle).apply { + this.id = id + 1 + this.tag = defaultSortOrder + this.text = text + this.icon = icon + }, WRAP_CONTENT, WRAP_CONTENT) + } + fun toggleGroupCheck(id: Int) { + toggleGroup.check(id) + } + fun toggleGroupRemoveItems() { + toggleGroup.removeAllViews() + } + + private val toggleGroupCheckedListener = MaterialButtonToggleGroup.OnButtonCheckedListener { group, checkedId, isChecked -> + if (group.checkedButtonId == View.NO_ID) { + group.check(checkedId) + return@OnButtonCheckedListener + } + /* TAG bit order + * bit 0 = default sorting mode + * bit 1 = is checked + * bit 2 = current sorting mode + */ + if (toggleGroupSelectionMode == TOGGLE_GROUP_SORTING_ORDER) { + val button = group.findViewById(checkedId) ?: return@OnButtonCheckedListener + var tag = button.tag as Int + var sortingMode: Int? = null + if (isChecked) { + sortingMode = if (tag and 0b010 == 1 shl 1) { + /* the view is checked and clicked once again */ + if (tag and 0b100 == SORT_MODE_ASCENDING shl 2) SORT_MODE_DESCENDING else SORT_MODE_ASCENDING + } else { + /* the view is first clicked so use the default sorting mode */ + if (tag and 0b001 == SORT_MODE_ASCENDING) SORT_MODE_ASCENDING else SORT_MODE_DESCENDING + } + tag = tag and 0b001 /* retain only default sorting mode */ + tag = tag or 0b010 /* set as checked */ + tag = tag or (sortingMode shl 2) /* set new sorting mode */ + } + else { + tag = tag and 0b001 /* retain only default sorting mode */ + } + button.tag = tag + button.icon = toggleGroupGetIconicsDrawable(context, when (sortingMode) { + SORT_MODE_ASCENDING -> NavLibFont.Icon.nav_sort_ascending + SORT_MODE_DESCENDING -> NavLibFont.Icon.nav_sort_descending + else -> null + }) + if (sortingMode != null) { + toggleGroupSortingOrderListener?.invoke(checkedId, sortingMode) + } + } + else if (toggleGroup.isSingleSelection && isChecked) { + toggleGroupSingleSelectionListener?.invoke(checkedId - 1) + } + else { + toggleGroupMultipleSelectionListener?.invoke(checkedId - 1, isChecked) + } + } + + var toggleGroupSingleSelectionListener: ((id: Int) -> Unit)? = null + var toggleGroupMultipleSelectionListener: ((id: Int, checked: Boolean) -> Unit)? = null + var toggleGroupSortingOrderListener: ((id: Int, sortMode: Int) -> Unit)? = null + + + /* _______ _ _ _ + |__ __| | | (_) | | + | | _____ _| |_ _ _ __ _ __ _ _| |_ + | |/ _ \ \/ / __| | | '_ \| '_ \| | | | __| + | | __/> <| |_ | | | | | |_) | |_| | |_ + |_|\___/_/\_\\__| |_|_| |_| .__/ \__,_|\__| + | | + |*/ + var textInputEnabled + get() = textInputLayout.visibility == View.VISIBLE + set(value) { textInputLayout.visibility = if (value) View.VISIBLE else View.GONE } + var textInputText + get() = textInputEditText.text.toString() + set(value) { textInputEditText.setText(value) } + var textInputHint + get() = textInputLayout.hint.toString() + set(value) { textInputLayout.hint = value } + var textInputHelperText + get() = textInputLayout.helperText.toString() + set(value) { textInputLayout.helperText = value } + var textInputError + get() = textInputLayout.error + set(value) { textInputLayout.error = value } + var textInputIcon: Any? + get() = textInputLayout.startIconDrawable + set(value) { + textInputLayout.startIconDrawable = when (value) { + is Drawable -> value + is IIcon -> IconicsDrawable(context).apply { + icon = value + sizeDp = 24 + // colorInt = Color.BLACK + } + is Int -> context.getDrawableFromRes(value) + else -> null + } + } + + private var textInputWatcher = object : TextWatcher { + override fun afterTextChanged(s: Editable?) {} + override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {} + override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) { + textInputChangedListener?.onTextChanged(s?.toString() ?: "", start, before, count) + } + } + + interface OnTextInputChangedListener { + fun onTextChanged(s: String, start: Int, before: Int, count: Int) + } + var textInputChangedListener: OnTextInputChangedListener? = null + + + + fun dispatchBottomBarEvent(event: MotionEvent) { + val location = IntArray(2) + bottomSheet.getLocationOnScreen(location) + event.setLocation(event.rawX - location[0], event.rawY - location[1]) + bottomSheet.dispatchTouchEvent(event) + } + + fun setContentPadding(left: Int, top: Int, right: Int, bottom: Int) { + content.setPadding(left, top, right, bottom) + } + fun getContentView() = content + + var isOpen + get() = bottomSheetBehavior.state != BottomSheetBehavior.STATE_HIDDEN + set(value) { + bottomSheetBehavior.state = if (value) BottomSheetBehavior.STATE_EXPANDED else BottomSheetBehavior.STATE_HIDDEN + } + fun open() { isOpen = true } + fun close() { isOpen = false } + fun toggle() { + if (!enable) + return + isOpen = !isOpen + } +} diff --git a/navlib/src/main/java/pl/szczodrzynski/navlib/bottomsheet/ViewHolderProvider.kt b/navlib/src/main/java/pl/szczodrzynski/navlib/bottomsheet/ViewHolderProvider.kt new file mode 100644 index 00000000..76ac8cb5 --- /dev/null +++ b/navlib/src/main/java/pl/szczodrzynski/navlib/bottomsheet/ViewHolderProvider.kt @@ -0,0 +1,21 @@ +package pl.szczodrzynski.navlib.bottomsheet + +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView +import kotlin.reflect.KClass + +class ViewHolderProvider { + private val viewHolderFactories = hashMapOf RecyclerView.ViewHolder>>() + + fun provideViewHolder(viewGroup: ViewGroup, viewType: Int): RecyclerView.ViewHolder { + val (layoutId: Int, f: (View) -> RecyclerView.ViewHolder) = viewHolderFactories[viewType]!! + val view = LayoutInflater.from(viewGroup.getContext()).inflate(layoutId, viewGroup, false) + return f(view) + } + + fun registerViewHolderFactory(viewType: Int, layoutId: Int, viewHolderFactory: (View) -> RecyclerView.ViewHolder) { + viewHolderFactories[viewType] = Pair(layoutId, viewHolderFactory) + } +} \ No newline at end of file diff --git a/navlib/src/main/java/pl/szczodrzynski/navlib/bottomsheet/items/BottomSheetPrimaryItem.kt b/navlib/src/main/java/pl/szczodrzynski/navlib/bottomsheet/items/BottomSheetPrimaryItem.kt new file mode 100644 index 00000000..3f7631cb --- /dev/null +++ b/navlib/src/main/java/pl/szczodrzynski/navlib/bottomsheet/items/BottomSheetPrimaryItem.kt @@ -0,0 +1,124 @@ +package pl.szczodrzynski.navlib.bottomsheet.items + +import android.graphics.drawable.Drawable +import android.view.View +import android.widget.ImageView +import android.widget.TextView +import androidx.annotation.DrawableRes +import androidx.annotation.StringRes +import androidx.recyclerview.widget.RecyclerView +import com.mikepenz.iconics.IconicsDrawable +import com.mikepenz.iconics.typeface.IIcon +import com.mikepenz.iconics.utils.sizeDp +import pl.szczodrzynski.navlib.ImageHolder +import pl.szczodrzynski.navlib.R +import pl.szczodrzynski.navlib.colorAttr +import pl.szczodrzynski.navlib.getColorFromAttr + +data class BottomSheetPrimaryItem(override val isContextual: Boolean = true) : IBottomSheetItem { + + /*_ _ + | | | | + | | __ _ _ _ ___ _ _| |_ + | | / _` | | | |/ _ \| | | | __| + | |___| (_| | |_| | (_) | |_| | |_ + |______\__,_|\__, |\___/ \__,_|\__| + __/ | + |__*/ + override var id: Int = -1 + override val viewType: Int + get() = 1 + override val layoutId: Int + get() = R.layout.nav_bs_item_primary + + class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { + val root = itemView.findViewById(R.id.item_root) + val image = itemView.findViewById(R.id.item_icon) + val text = itemView.findViewById(R.id.item_text) + val description = itemView.findViewById(R.id.item_description) + } + + override fun bindViewHolder(viewHolder: ViewHolder) { + viewHolder.root.setOnClickListener(onClickListener) + + viewHolder.image.setImageDrawable(IconicsDrawable(viewHolder.text.context).apply { + icon = iconicsIcon + colorAttr(viewHolder.text.context, android.R.attr.textColorSecondary) + sizeDp = 24 + }) + + viewHolder.description.visibility = View.VISIBLE + when { + descriptionRes != null -> viewHolder.description.setText(descriptionRes!!) + description != null -> viewHolder.description.text = description + else -> viewHolder.description.visibility = View.GONE + } + + when { + titleRes != null -> viewHolder.text.setText(titleRes!!) + else -> viewHolder.text.text = title + } + viewHolder.text.setTextColor(getColorFromAttr(viewHolder.text.context, android.R.attr.textColorPrimary)) + } + + /*_____ _ + | __ \ | | + | | | | __ _| |_ __ _ + | | | |/ _` | __/ _` | + | |__| | (_| | || (_| | + |_____/ \__,_|\__\__,*/ + var title: CharSequence? = null + @StringRes + var titleRes: Int? = null + var description: CharSequence? = null + @StringRes + var descriptionRes: Int? = null + var icon: ImageHolder? = null + var iconicsIcon: IIcon? = null + var onClickListener: View.OnClickListener? = null + + fun withId(id: Int): BottomSheetPrimaryItem { + this.id = id + return this + } + + fun withTitle(title: CharSequence): BottomSheetPrimaryItem { + this.title = title + this.titleRes = null + return this + } + fun withTitle(@StringRes title: Int): BottomSheetPrimaryItem { + this.title = null + this.titleRes = title + return this + } + + fun withDescription(description: CharSequence): BottomSheetPrimaryItem { + this.description = description + this.descriptionRes = null + return this + } + fun withDescription(@StringRes description: Int): BottomSheetPrimaryItem { + this.description = null + this.descriptionRes = description + return this + } + + fun withIcon(icon: Drawable): BottomSheetPrimaryItem { + this.icon = ImageHolder(icon) + return this + } + fun withIcon(@DrawableRes icon: Int): BottomSheetPrimaryItem { + this.icon = ImageHolder(icon) + return this + } + fun withIcon(icon: IIcon): BottomSheetPrimaryItem { + this.iconicsIcon = icon + return this + } + + fun withOnClickListener(onClickListener: View.OnClickListener): BottomSheetPrimaryItem { + this.onClickListener = onClickListener + return this + } +} diff --git a/navlib/src/main/java/pl/szczodrzynski/navlib/bottomsheet/items/BottomSheetSeparatorItem.kt b/navlib/src/main/java/pl/szczodrzynski/navlib/bottomsheet/items/BottomSheetSeparatorItem.kt new file mode 100644 index 00000000..cf5403e3 --- /dev/null +++ b/navlib/src/main/java/pl/szczodrzynski/navlib/bottomsheet/items/BottomSheetSeparatorItem.kt @@ -0,0 +1,28 @@ +package pl.szczodrzynski.navlib.bottomsheet.items + +import android.view.View +import androidx.recyclerview.widget.RecyclerView +import pl.szczodrzynski.navlib.R + +data class BottomSheetSeparatorItem(override val isContextual: Boolean = true) : IBottomSheetItem { + + /*_ _ + | | | | + | | __ _ _ _ ___ _ _| |_ + | | / _` | | | |/ _ \| | | | __| + | |___| (_| | |_| | (_) | |_| | |_ + |______\__,_|\__, |\___/ \__,_|\__| + __/ | + |__*/ + override var id: Int = -1 + override val viewType: Int + get() = 2 + override val layoutId: Int + get() = R.layout.nav_bs_item_separator + + class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) + + override fun bindViewHolder(viewHolder: ViewHolder) { + + } +} \ No newline at end of file diff --git a/navlib/src/main/java/pl/szczodrzynski/navlib/bottomsheet/items/IBottomSheetItem.kt b/navlib/src/main/java/pl/szczodrzynski/navlib/bottomsheet/items/IBottomSheetItem.kt new file mode 100644 index 00000000..e55f337f --- /dev/null +++ b/navlib/src/main/java/pl/szczodrzynski/navlib/bottomsheet/items/IBottomSheetItem.kt @@ -0,0 +1,19 @@ +package pl.szczodrzynski.navlib.bottomsheet.items + +import android.view.View +import androidx.annotation.LayoutRes +import androidx.recyclerview.widget.RecyclerView +import kotlin.reflect.KClass + +interface IBottomSheetItem { + + val isContextual: Boolean + var id: Int + val viewType: Int + val layoutId: Int + + fun bindViewHolder(viewHolder: T) + fun bindViewHolder(viewHolder: RecyclerView.ViewHolder) { + bindViewHolder(viewHolder as T) + } +} \ No newline at end of file diff --git a/navlib/src/main/java/pl/szczodrzynski/navlib/drawer/IDrawerProfile.kt b/navlib/src/main/java/pl/szczodrzynski/navlib/drawer/IDrawerProfile.kt new file mode 100644 index 00000000..30c15166 --- /dev/null +++ b/navlib/src/main/java/pl/szczodrzynski/navlib/drawer/IDrawerProfile.kt @@ -0,0 +1,17 @@ +package pl.szczodrzynski.navlib.drawer + +import android.content.Context +import android.graphics.drawable.Drawable +import android.widget.ImageView +import pl.szczodrzynski.navlib.ImageHolder + +interface IDrawerProfile { + val id: Int + var name: String + var subname: String? + var image: String? + + fun getImageDrawable(context: Context): Drawable? + fun getImageHolder(context: Context): ImageHolder? + fun applyImageTo(imageView: ImageView) +} \ No newline at end of file diff --git a/navlib/src/main/java/pl/szczodrzynski/navlib/drawer/IUnreadCounter.kt b/navlib/src/main/java/pl/szczodrzynski/navlib/drawer/IUnreadCounter.kt new file mode 100644 index 00000000..ef213c2a --- /dev/null +++ b/navlib/src/main/java/pl/szczodrzynski/navlib/drawer/IUnreadCounter.kt @@ -0,0 +1,8 @@ +package pl.szczodrzynski.navlib.drawer + +interface IUnreadCounter { + var profileId: Int + var type: Int + var drawerItemId: Int? + var count: Int +} \ No newline at end of file diff --git a/navlib/src/main/java/pl/szczodrzynski/navlib/drawer/NavDrawer.kt b/navlib/src/main/java/pl/szczodrzynski/navlib/drawer/NavDrawer.kt new file mode 100644 index 00000000..63c66766 --- /dev/null +++ b/navlib/src/main/java/pl/szczodrzynski/navlib/drawer/NavDrawer.kt @@ -0,0 +1,730 @@ +package pl.szczodrzynski.navlib.drawer + +import android.annotation.SuppressLint +import android.app.Activity +import android.content.Context +import android.content.res.Configuration +import android.content.res.Resources +import android.graphics.Color +import android.graphics.PorterDuff +import android.graphics.drawable.LayerDrawable +import android.util.Log +import android.view.Gravity +import android.view.View +import android.view.ViewGroup +import android.view.ViewGroup.LayoutParams.MATCH_PARENT +import android.view.ViewGroup.LayoutParams.WRAP_CONTENT +import android.widget.FrameLayout +import android.widget.LinearLayout +import android.widget.Toast +import androidx.core.content.ContextCompat +import androidx.customview.widget.ViewDragHelper +import androidx.drawerlayout.widget.DrawerLayout +import com.mikepenz.fastadapter.IAdapter +import com.mikepenz.itemanimators.AlphaCrossFadeAnimator +import com.mikepenz.materialdrawer.* +import com.mikepenz.materialdrawer.holder.BadgeStyle +import com.mikepenz.materialdrawer.holder.ColorHolder +import com.mikepenz.materialdrawer.holder.StringHolder +import com.mikepenz.materialdrawer.model.BaseDrawerItem +import com.mikepenz.materialdrawer.model.MiniProfileDrawerItem +import com.mikepenz.materialdrawer.model.ProfileDrawerItem +import com.mikepenz.materialdrawer.model.ProfileSettingDrawerItem +import com.mikepenz.materialdrawer.model.interfaces.* +import com.mikepenz.materialdrawer.util.* +import com.mikepenz.materialdrawer.widget.AccountHeaderView +import com.mikepenz.materialdrawer.widget.MaterialDrawerSliderView +import com.mikepenz.materialdrawer.widget.MiniDrawerSliderView +import com.mikepenz.materialize.util.UIUtils +import pl.szczodrzynski.navlib.* +import pl.szczodrzynski.navlib.R +import pl.szczodrzynski.navlib.drawer.items.DrawerPrimaryItem + +class NavDrawer( + val context: Context, + val drawerLayout: DrawerLayout, + val drawerContainerLandscape: FrameLayout, + val drawerContainerPortrait: FrameLayout, + val miniDrawerElevation: View +) { + companion object { + private const val DRAWER_MODE_NORMAL = 0 + private const val DRAWER_MODE_MINI = 1 + private const val DRAWER_MODE_FIXED = 2 + } + + private lateinit var activity: Activity + private val resources: Resources + get() = context.resources + + internal lateinit var toolbar: NavToolbar + internal lateinit var bottomBar: NavBottomBar + + private lateinit var drawer: MaterialDrawerSliderView + private lateinit var accountHeader: AccountHeaderView + private lateinit var miniDrawer: MiniDrawerSliderView + + private var drawerMode: Int = DRAWER_MODE_NORMAL + private var selection: Int = -1 + + lateinit var badgeStyle: BadgeStyle + + @SuppressLint("ClickableViewAccessibility") + fun init(activity: Activity) { + this.activity = activity + + /*badgeStyle = BadgeStyle( + R.drawable.material_drawer_badge, + getColorFromAttr(context, R.attr.colorError), + getColorFromAttr(context, R.attr.colorError), + getColorFromAttr(context, R.attr.colorOnError) + )*/ + + badgeStyle = BadgeStyle().apply { + textColor = ColorHolder.fromColor(Color.WHITE) + color = ColorHolder.fromColor(0xffd32f2f.toInt()) + } + + drawerLayout.addDrawerListener(object : DrawerLayout.DrawerListener { + override fun onDrawerStateChanged(newState: Int) {} + override fun onDrawerSlide(drawerView: View, slideOffset: Float) {} + override fun onDrawerClosed(drawerView: View) { + drawerClosedListener?.invoke() + profileSelectionClose() + } + override fun onDrawerOpened(drawerView: View) { + drawerOpenedListener?.invoke() + } + }) + + accountHeader = AccountHeaderView(context).apply { + headerBackground = ImageHolder(R.drawable.header) + displayBadgesOnSmallProfileImages = true + + onAccountHeaderListener = { view, profile, current -> + if (profile is ProfileSettingDrawerItem) { + drawerProfileSettingClickListener?.invoke(profile.identifier.toInt(), view) ?: false + } + else { + updateBadges() + if (current) { + close() + profileSelectionClose() + true + } + else { + (drawerProfileSelectedListener?.invoke(profile.identifier.toInt(), profile, current, view) ?: false).also { + setToolbarProfileImage(profileList.singleOrNull { it.id == profile.identifier.toInt() }) + } + } + } + } + + onAccountHeaderItemLongClickListener = { view, profile, current -> + if (profile is ProfileSettingDrawerItem) { + drawerProfileSettingLongClickListener?.invoke(profile.identifier.toInt(), view) ?: true + } + else { + drawerProfileLongClickListener?.invoke(profile.identifier.toInt(), profile, current, view) ?: false + } + } + + onAccountHeaderProfileImageListener = { view, profile, current -> + drawerProfileImageClickListener?.invoke(profile.identifier.toInt(), profile, current, view) ?: false + } + //.withTextColor(ContextCompat.getColor(context, R.color.material_drawer_dark_primary_text)) + } + + drawer = MaterialDrawerSliderView(context).apply { + accountHeader = this@NavDrawer.accountHeader + itemAnimator = AlphaCrossFadeAnimator() + //hasStableIds = true + + onDrawerItemClickListener = { _, drawerItem, position -> + if (drawerItem.identifier.toInt() == selection) { + false + } + else { + val consumed = drawerItemSelectedListener?.invoke(drawerItem.identifier.toInt(), position, drawerItem) + if (consumed == false || !drawerItem.isSelectable) { + setSelection(selection, false) + consumed == false + } + else if (consumed == true) { + when (drawerItem) { + is DrawerPrimaryItem -> toolbar.title = drawerItem.appTitle ?: drawerItem.name?.getText(context) ?: "" + is BaseDrawerItem<*, *> -> toolbar.title = drawerItem.name?.getText(context) ?: "" + } + false + } + else { + false + } + } + } + + onDrawerItemLongClickListener = { _, drawerItem, position -> + drawerItemLongClickListener?.invoke(drawerItem.identifier.toInt(), position, drawerItem) ?: true + } + } + + miniDrawer = MiniDrawerSliderView(context).apply { + drawer = this@NavDrawer.drawer + includeSecondaryDrawerItems = false + try { + this::class.java.getDeclaredField("onMiniDrawerItemClickListener").let { + it.isAccessible = true + it.set(this, { v: View?, position: Int, item: IDrawerItem<*>, type: Int -> + if (item is MiniProfileDrawerItem) { + profileSelectionOpen() + open() + true + } else false + }) + } + } catch (_: Exception) { } + } + + updateMiniDrawer() + + toolbar.profileImageClickListener = { + profileSelectionOpen() + open() + } + + val configuration = context.resources.configuration + decideDrawerMode( + configuration.orientation, + configuration.screenWidthDp, + configuration.screenHeightDp + ) + } + + /* _____ _ + |_ _| | + | | | |_ ___ _ __ ___ ___ + | | | __/ _ \ '_ ` _ \/ __| + _| |_| || __/ | | | | \__ \ + |_____|\__\___|_| |_| |_|__*/ + operator fun plusAssign(item: IDrawerItem<*>) { + appendItem(item) + } + fun appendItem(item: IDrawerItem<*>) { + drawer.addItems(item) + updateMiniDrawer() + } + fun appendItems(vararg items: IDrawerItem<*>) { + drawer.addItems(*items) + updateMiniDrawer() + } + fun prependItem(item: IDrawerItem<*>) { + drawer.addItemAtPosition(0, item) + updateMiniDrawer() + } + fun prependItems(vararg items: IDrawerItem<*>) { + drawer.addItemsAtPosition(0, *items) + updateMiniDrawer() + } + fun addItemAt(index: Int, item: IDrawerItem<*>) { + drawer.addItemAtPosition(index, item) + updateMiniDrawer() + } + fun addItemsAt(index: Int, vararg items: IDrawerItem<*>) { + drawer.addItemsAtPosition(index, *items) + updateMiniDrawer() + } + fun removeItemById(id: Int) { + drawer.removeItems(id.toLong()) + updateMiniDrawer() + } + fun removeItemAt(index: Int) { + drawer.removeItemByPosition(index) + updateMiniDrawer() + } + fun removeAllItems() { + drawer.removeAllItems() + updateMiniDrawer() + } + + fun getItemById(id: Int, run: (it: IDrawerItem<*>?) -> Unit) { + drawer.getDrawerItem(id.toLong()).also { + run(it) + if (it != null) + drawer.updateItem(it) + updateMiniDrawer() + } + } + fun getItemByIndex(index: Int, run: (it: IDrawerItem<*>?) -> Unit) { + drawer.itemAdapter.itemList.get(index).also { + run(it) + if (it != null) + drawer.updateItem(it) + updateMiniDrawer() + } + } + + fun setItems(vararg items: IDrawerItem<*>) { + drawer.removeAllItems() + drawer.addItems(*items) + updateMiniDrawer() + } + + /* _____ _ _ _ _ _ + | __ \ (_) | | | | | | | | + | |__) | __ ___ ____ _| |_ ___ _ __ ___ ___| |_| |__ ___ __| |___ + | ___/ '__| \ \ / / _` | __/ _ \ | '_ ` _ \ / _ \ __| '_ \ / _ \ / _` / __| + | | | | | |\ V / (_| | || __/ | | | | | | __/ |_| | | | (_) | (_| \__ \ + |_| |_| |_| \_/ \__,_|\__\___| |_| |_| |_|\___|\__|_| |_|\___/ \__,_|__*/ + private fun drawerSetDragMargin(size: Float) { + try { + val mDrawerLayout = drawerLayout + val mDragger = mDrawerLayout::class.java.getDeclaredField( + "mLeftDragger" + ) + mDragger.isAccessible = true + val draggerObj = mDragger.get(mDrawerLayout) as ViewDragHelper? + draggerObj?.edgeSize = size.toInt() + + // update for SDK >= 29 (Android 10) + val useSystemInsets = mDrawerLayout::class.java.getDeclaredField( + "sEdgeSizeUsingSystemGestureInsets" + ) + useSystemInsets.isAccessible = true + useSystemInsets.set(null, false) + } + catch (e: Exception) { + e.printStackTrace() + Toast.makeText(context, "Oops, proguard works", Toast.LENGTH_SHORT).show() + } + } + + var miniDrawerVisiblePortrait: Boolean? = null + set(value) { + field = value + val configuration = context.resources.configuration + decideDrawerMode( + configuration.orientation, + configuration.screenWidthDp, + configuration.screenHeightDp + ) + } + var miniDrawerVisibleLandscape: Boolean? = null + set(value) { + field = value + val configuration = context.resources.configuration + decideDrawerMode( + configuration.orientation, + configuration.screenWidthDp, + configuration.screenHeightDp + ) + } + + internal fun decideDrawerMode(orientation: Int, widthDp: Int, heightDp: Int) { + val drawerLayoutParams = DrawerLayout.LayoutParams(WRAP_CONTENT, MATCH_PARENT).apply { + gravity = Gravity.START + } + val fixedLayoutParams = FrameLayout.LayoutParams(UIUtils.convertDpToPixel(300f, context).toInt(), MATCH_PARENT) + + Log.d("NavLib", "Deciding drawer mode:") + if (orientation == Configuration.ORIENTATION_PORTRAIT) { + Log.d("NavLib", "- fixed container disabled") + + if (drawerContainerLandscape.childCount > 0) { + drawerContainerLandscape.removeAllViews() + } + Log.d("NavLib", "- mini drawer land disabled") + + if (drawerLayout.indexOfChild(drawer) == -1) { + drawerLayout.addView(drawer, drawerLayoutParams) + } + drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED) + Log.d("NavLib", "- slider enabled") + + if ((widthDp >= 480 && miniDrawerVisiblePortrait != false) || miniDrawerVisiblePortrait == true) { + if (drawerContainerPortrait.indexOfChild(miniDrawer) == -1) + drawerContainerPortrait.addView(miniDrawer) + Log.d("NavLib", "- mini drawer port enabled") + drawerSetDragMargin(72 * resources.displayMetrics.density) + drawerMode = DRAWER_MODE_MINI + updateMiniDrawer() + } + else { + if (drawerContainerPortrait.childCount > 0) { + drawerContainerPortrait.removeAllViews() + } + Log.d("NavLib", "- mini drawer port disabled") + drawerSetDragMargin(20 * resources.displayMetrics.density) + drawerMode = DRAWER_MODE_NORMAL + } + } + else { + if (drawerContainerPortrait.childCount > 0) { + drawerContainerPortrait.removeAllViews() + } + Log.d("NavLib", "- mini drawer port disabled") + + if ((widthDp in 480 until 900 && miniDrawerVisibleLandscape != false) || miniDrawerVisibleLandscape == true) { + if (drawerContainerLandscape.indexOfChild(miniDrawer) == -1) + drawerContainerLandscape.addView(miniDrawer) + Log.d("NavLib", "- mini drawer land enabled") + drawerSetDragMargin(72 * resources.displayMetrics.density) + drawerMode = DRAWER_MODE_MINI + updateMiniDrawer() + } + else { + if (drawerContainerLandscape.childCount > 0) { + drawerContainerLandscape.removeAllViews() + } + Log.d("NavLib", "- mini drawer land disabled") + drawerSetDragMargin(20 * resources.displayMetrics.density) + drawerMode = DRAWER_MODE_NORMAL + } + if (widthDp >= 900) { + // screen is big enough to show fixed drawer + if (drawerLayout.indexOfChild(drawer) != -1) { + // remove from slider + drawerLayout.removeView(drawer) + } + // lock the slider + drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED) + Log.d("NavLib", "- slider disabled") + // add to fixed container + if (drawerContainerLandscape.indexOfChild(drawer) == -1) + drawerContainerLandscape.addView(drawer, fixedLayoutParams) + drawer.visibility = View.VISIBLE + Log.d("NavLib", "- fixed container enabled") + drawerMode = DRAWER_MODE_FIXED + } + else { + // screen is too small for the fixed drawer + if (drawerContainerLandscape.indexOfChild(drawer) != -1) { + // remove from fixed container + drawerContainerLandscape.removeView(drawer) + } + Log.d("NavLib", "- fixed container disabled") + // unlock the slider + drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED) + if (drawerLayout.indexOfChild(drawer) == -1) { + // add to slider + drawerLayout.addView(drawer, drawerLayoutParams) + } + Log.d("NavLib", "- slider enabled") + } + } + + miniDrawerElevation.visibility = if (drawerMode == DRAWER_MODE_MINI || drawerMode == DRAWER_MODE_FIXED) View.VISIBLE else View.GONE + } + + private fun updateMiniDrawer() { + selection = drawer.selectedItemIdentifier.toInt() + //if (drawerMode == DRAWER_MODE_MINI) + miniDrawer.createItems() + } + + /* _____ _ _ _ _ _ _ + | __ \ | | | (_) | | | | | | + | |__) | _| |__ | |_ ___ _ __ ___ ___| |_| |__ ___ __| |___ + | ___/ | | | '_ \| | |/ __| | '_ ` _ \ / _ \ __| '_ \ / _ \ / _` / __| + | | | |_| | |_) | | | (__ | | | | | | __/ |_| | | | (_) | (_| \__ \ + |_| \__,_|_.__/|_|_|\___| |_| |_| |_|\___|\__|_| |_|\___/ \__,_|__*/ + var isOpen + get() = drawerLayout.isOpen || drawerMode == DRAWER_MODE_FIXED + set(value) { + if (drawerMode == DRAWER_MODE_FIXED) + return + if (value && !isOpen) drawerLayout.open() else if (!value && isOpen) drawerLayout.close() + } + fun open() { isOpen = true } + fun close() { isOpen = false } + fun toggle() { isOpen = !isOpen } + + var profileSelectionIsOpen + get() = accountHeader.selectionListShown + set(value) { + if (value != profileSelectionIsOpen) + profileSelectionToggle() + } + fun profileSelectionOpen() { profileSelectionIsOpen = true } + fun profileSelectionClose() { profileSelectionIsOpen = false } + fun profileSelectionToggle() { accountHeader.selectionListShown = !accountHeader.selectionListShown } + + var drawerOpenedListener: (() -> Unit)? = null + var drawerClosedListener: (() -> Unit)? = null + var drawerItemSelectedListener: ((id: Int, position: Int, drawerItem: IDrawerItem<*>) -> Boolean)? = null + var drawerItemLongClickListener: ((id: Int, position: Int, drawerItem: IDrawerItem<*>) -> Boolean)? = null + var drawerProfileSelectedListener: ((id: Int, profile: IProfile, current: Boolean, view: View?) -> Boolean)? = null + var drawerProfileLongClickListener: ((id: Int, profile: IProfile, current: Boolean, view: View?) -> Boolean)? = null + var drawerProfileImageClickListener: ((id: Int, profile: IProfile, current: Boolean, view: View) -> Boolean)? = null + var drawerProfileImageLongClickListener: ((id: Int, profile: IProfile, current: Boolean, view: View) -> Boolean)? = null + var drawerProfileListEmptyListener: (() -> Unit)? = null + var drawerProfileSettingClickListener: ((id: Int, view: View?) -> Boolean)? = null + var drawerProfileSettingLongClickListener: ((id: Int, view: View?) -> Boolean)? = null + + fun miniDrawerEnabled(): Boolean = drawerMode == DRAWER_MODE_MINI + fun fixedDrawerEnabled(): Boolean = drawerMode == DRAWER_MODE_FIXED + + fun setSelection(id: Int, fireOnClick: Boolean = true) { + Log.d("NavDebug", "setSelection(id = $id, fireOnClick = $fireOnClick)") + // seems that this cannot be open, because the itemAdapter has Profile items + // instead of normal Drawer items... + profileSelectionClose() + selection = id + + if (drawer.selectedItemIdentifier != id.toLong()) { + + } + + if (drawer.selectedItemIdentifier != id.toLong() || !fireOnClick) + drawer.setSelectionAtPosition(drawer.getPosition(id.toLong()), fireOnClick) + + miniDrawer.setSelection(-1L) + if (drawerMode == DRAWER_MODE_MINI) + miniDrawer.setSelection(id.toLong()) + } + fun getSelection(): Int = selection + + // TODO 2019-08-27 add methods for Drawable, @DrawableRes + fun setAccountHeaderBackground(path: String?) { + if (path == null) { + accountHeader.headerBackground = ImageHolder(R.drawable.header) + return + } + accountHeader.headerBackground = ImageHolder(path) + } + + /* _____ __ _ _ + | __ \ / _(_) | + | |__) | __ ___ | |_ _| | ___ ___ + | ___/ '__/ _ \| _| | |/ _ \/ __| + | | | | | (_) | | | | | __/\__ \ + |_| |_| \___/|_| |_|_|\___||__*/ + private var profileList: MutableList = mutableListOf() + + fun addProfileSettings(vararg items: ProfileSettingDrawerItem) { + accountHeader.profiles?.addAll(items) + } + + private fun updateProfileList() { + // remove all profile items + val profiles = accountHeader.profiles?.filterNot { it is ProfileDrawerItem } as MutableList? + + if (profileList.isEmpty()) + drawerProfileListEmptyListener?.invoke() + + profileList.forEachIndexed { index, profile -> + val image = profile.getImageHolder(context) + ProfileDrawerItem() + .withIdentifier(profile.id.toLong()) + .withName(profile.name) + .withEmail(profile.subname) + .also { it.icon = image } + .withBadgeStyle(badgeStyle) + .withNameShown(true) + .also { profiles?.add(index, it) } + } + + accountHeader.profiles = profiles + + updateBadges() + updateMiniDrawer() + } + + fun setProfileList(profiles: MutableList) { + profileList = profiles as MutableList + updateProfileList() + } + private var currentProfileObj: IDrawerProfile? = null + val profileListEmpty: Boolean + get() = profileList.isEmpty() + var currentProfile: Int + get() = accountHeader.activeProfile?.identifier?.toInt() ?: -1 + set(value) { + Log.d("NavDebug", "currentProfile = $value") + accountHeader.setActiveProfile(value.toLong(), false) + currentProfileObj = profileList.singleOrNull { it.id == value } + setToolbarProfileImage(currentProfileObj) + updateBadges() + } + fun appendProfile(profile: IDrawerProfile) { + profileList.add(profile) + updateProfileList() + } + fun appendProfiles(vararg profiles: IDrawerProfile) { + profileList.addAll(profiles) + updateProfileList() + } + fun prependProfile(profile: IDrawerProfile) { + profileList.add(0, profile) + updateProfileList() + } + fun prependProfiles(vararg profiles: IDrawerProfile) { + profileList.addAll(0, profiles.asList()) + updateProfileList() + } + fun addProfileAt(index: Int, profile: IDrawerProfile) { + profileList.add(index, profile) + updateProfileList() + } + fun addProfilesAt(index: Int, vararg profiles: IDrawerProfile) { + profileList.addAll(index, profiles.asList()) + updateProfileList() + } + fun removeProfileById(id: Int) { + profileList = profileList.filterNot { it.id == id }.toMutableList() + updateProfileList() + } + fun removeProfileAt(index: Int) { + profileList.removeAt(index) + updateProfileList() + } + fun removeAllProfile() { + profileList.clear() + updateProfileList() + } + fun removeAllProfileSettings() { + accountHeader.profiles = accountHeader.profiles?.filterNot { it is ProfileSettingDrawerItem }?.toMutableList() + } + + fun getProfileById(id: Int, run: (it: IDrawerProfile?) -> Unit) { + profileList.singleOrNull { it.id == id }.also { + run(it) + updateProfileList() + } + } + fun getProfileByIndex(index: Int, run: (it: IDrawerProfile?) -> Unit) { + profileList.getOrNull(index).also { + run(it) + updateProfileList() + } + } + + private fun setToolbarProfileImage(profile: IDrawerProfile?) { + toolbar.profileImage = profile?.getImageDrawable(context) + } + + + /* ____ _ + | _ \ | | + | |_) | __ _ __| | __ _ ___ ___ + | _ < / _` |/ _` |/ _` |/ _ \/ __| + | |_) | (_| | (_| | (_| | __/\__ \ + |____/ \__,_|\__,_|\__, |\___||___/ + __/ | + |__*/ + private var unreadCounterList: MutableList = mutableListOf() + private val unreadCounterTypeMap = mutableMapOf() + + fun updateBadges() { + + currentProfileObj = profileList.singleOrNull { it.id == currentProfile } + + drawer.itemAdapter.itemList.items.forEachIndexed { index, item -> + if (item is Badgeable) { + item.badge = null + drawer.updateItem(item) + } + } + + var profileCounters = listOf() + + accountHeader.profiles?.forEach { profile -> + if (profile !is ProfileDrawerItem) return@forEach + val counters = unreadCounterList.filter { it.profileId == profile.identifier.toInt() } + val count = counters.sumBy { it.count } + val badge = when { + count == 0 -> null + count >= 99 -> StringHolder("99+") + else -> StringHolder(count.toString()) + } + if (profile.badge != badge) { + profile.badge = badge + accountHeader.updateProfile(profile) + } + + if (currentProfile == profile.identifier.toInt()) + profileCounters = counters + } + + Log.d("NavDebug", "updateBadges()") + profileCounters.map { + it.drawerItemId = unreadCounterTypeMap[it.type] + } + var totalCount = 0 + profileCounters.forEach { + if (it.drawerItemId == null) + return@forEach + if (it.profileId != currentProfile) { + //Log.d("NavDebug", "- Remove badge for ${it.drawerItemId}") + //drawer?.updateBadge(it.drawerItemId?.toLong() ?: 0, null) + return@forEach + } + Log.d("NavDebug", "- Set badge ${it.count} for ${it.drawerItemId}") + drawer.updateBadge( + it.drawerItemId?.toLong() ?: 0, + when { + it.count == 0 -> null + it.count >= 99 -> StringHolder("99+") + else -> StringHolder(it.count.toString()) + } + ) + totalCount += it.count + } + updateMiniDrawer() + + if (bottomBar.navigationIcon is LayerDrawable) { + (bottomBar.navigationIcon as LayerDrawable?)?.apply { + findDrawableByLayerId(R.id.ic_badge) + .takeIf { it is BadgeDrawable } + ?.also { badge -> + (badge as BadgeDrawable).setCount(totalCount.toString()) + mutate() + setDrawableByLayerId(R.id.ic_badge, badge) + } + } + } + + if (totalCount == 0) { + toolbar.subtitle = resources.getString( + toolbar.subtitleFormat ?: return, + currentProfileObj?.name ?: "" + ) + } + else { + toolbar.subtitle = resources.getQuantityString( + toolbar.subtitleFormatWithUnread ?: toolbar.subtitleFormat ?: return, + totalCount, + currentProfileObj?.name ?: "", + totalCount + ) + } + } + + fun setUnreadCounterList(unreadCounterList: MutableList) { + this.unreadCounterList = unreadCounterList as MutableList + updateBadges() + } + + fun addUnreadCounterType(type: Int, drawerItem: Int) { + unreadCounterTypeMap[type] = drawerItem + } + + data class UnreadCounter( + override var profileId: Int, + override var type: Int, + override var drawerItemId: Int?, + override var count: Int + ) : IUnreadCounter + + fun setUnreadCount(profileId: Int, type: Int, count: Int) { + val item = unreadCounterList.singleOrNull { + it.type == type && it.profileId == profileId + } + if (item != null) { + item.count = count + } + else { + unreadCounterList.add(UnreadCounter(profileId, type, null, count)) + } + updateBadges() + } +} diff --git a/navlib/src/main/java/pl/szczodrzynski/navlib/drawer/items/DrawerPrimaryItem.kt b/navlib/src/main/java/pl/szczodrzynski/navlib/drawer/items/DrawerPrimaryItem.kt new file mode 100644 index 00000000..7a623747 --- /dev/null +++ b/navlib/src/main/java/pl/szczodrzynski/navlib/drawer/items/DrawerPrimaryItem.kt @@ -0,0 +1,18 @@ +package pl.szczodrzynski.navlib.drawer.items + +import com.mikepenz.materialdrawer.model.PrimaryDrawerItem + +class DrawerPrimaryItem : PrimaryDrawerItem() { + var appTitle: String? = null + fun withAppTitle(appTitle: String?): PrimaryDrawerItem { + this.appTitle = appTitle + return this + } +} + +fun PrimaryDrawerItem.withAppTitle(appTitle: String?): PrimaryDrawerItem { + if (this !is DrawerPrimaryItem) + return this + this.appTitle = appTitle + return this +} \ No newline at end of file diff --git a/navlib/src/main/res/color/mtrl_filled_background_color.xml b/navlib/src/main/res/color/mtrl_filled_background_color.xml new file mode 100644 index 00000000..ed0fc82d --- /dev/null +++ b/navlib/src/main/res/color/mtrl_filled_background_color.xml @@ -0,0 +1,21 @@ + + + + + + + diff --git a/navlib/src/main/res/color/text_input_layout_background.xml b/navlib/src/main/res/color/text_input_layout_background.xml new file mode 100644 index 00000000..7bfcd005 --- /dev/null +++ b/navlib/src/main/res/color/text_input_layout_background.xml @@ -0,0 +1,22 @@ + + + + + + + + diff --git a/navlib/src/main/res/drawable-v21/bs_item_background.xml b/navlib/src/main/res/drawable-v21/bs_item_background.xml new file mode 100644 index 00000000..1cf74bcc --- /dev/null +++ b/navlib/src/main/res/drawable-v21/bs_item_background.xml @@ -0,0 +1,10 @@ + + + + \ No newline at end of file diff --git a/navlib/src/main/res/drawable/bottom_sheet_background.xml b/navlib/src/main/res/drawable/bottom_sheet_background.xml new file mode 100644 index 00000000..1e6a6852 --- /dev/null +++ b/navlib/src/main/res/drawable/bottom_sheet_background.xml @@ -0,0 +1,9 @@ + + + + + \ No newline at end of file diff --git a/navlib/src/main/res/drawable/bottom_sheet_control_bar.xml b/navlib/src/main/res/drawable/bottom_sheet_control_bar.xml new file mode 100644 index 00000000..ac5256a5 --- /dev/null +++ b/navlib/src/main/res/drawable/bottom_sheet_control_bar.xml @@ -0,0 +1,9 @@ + + + + + + \ No newline at end of file diff --git a/navlib/src/main/res/drawable/bs_item_background.xml b/navlib/src/main/res/drawable/bs_item_background.xml new file mode 100644 index 00000000..59ad9b32 --- /dev/null +++ b/navlib/src/main/res/drawable/bs_item_background.xml @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/navlib/src/main/res/drawable/bs_item_background_base.xml b/navlib/src/main/res/drawable/bs_item_background_base.xml new file mode 100644 index 00000000..454d22b1 --- /dev/null +++ b/navlib/src/main/res/drawable/bs_item_background_base.xml @@ -0,0 +1,11 @@ + + + + + + + + + \ No newline at end of file diff --git a/navlib/src/main/res/drawable/header.png b/navlib/src/main/res/drawable/header.png new file mode 100644 index 0000000000000000000000000000000000000000..d0730a1a7f388b74c1e2b3301d9d8f756e519a86 GIT binary patch literal 92843 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:%e65g#;Mh= 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 literal 0 HcmV?d00001 diff --git a/navlib/src/main/res/drawable/ic_android.xml b/navlib/src/main/res/drawable/ic_android.xml new file mode 100644 index 00000000..fa2bc215 --- /dev/null +++ b/navlib/src/main/res/drawable/ic_android.xml @@ -0,0 +1,9 @@ + + + diff --git a/navlib/src/main/res/drawable/ic_light.xml b/navlib/src/main/res/drawable/ic_light.xml new file mode 100644 index 00000000..4f7c019c --- /dev/null +++ b/navlib/src/main/res/drawable/ic_light.xml @@ -0,0 +1,9 @@ + + + diff --git a/navlib/src/main/res/drawable/ic_menu_badge.xml b/navlib/src/main/res/drawable/ic_menu_badge.xml new file mode 100644 index 00000000..4355777f --- /dev/null +++ b/navlib/src/main/res/drawable/ic_menu_badge.xml @@ -0,0 +1,10 @@ + + + + + \ No newline at end of file diff --git a/navlib/src/main/res/drawable/ic_night.xml b/navlib/src/main/res/drawable/ic_night.xml new file mode 100644 index 00000000..008a4fbf --- /dev/null +++ b/navlib/src/main/res/drawable/ic_night.xml @@ -0,0 +1,9 @@ + + + diff --git a/navlib/src/main/res/drawable/placeholder.xml b/navlib/src/main/res/drawable/placeholder.xml new file mode 100644 index 00000000..0a9be837 --- /dev/null +++ b/navlib/src/main/res/drawable/placeholder.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/navlib/src/main/res/drawable/profile.xml b/navlib/src/main/res/drawable/profile.xml new file mode 100644 index 00000000..eea87f66 --- /dev/null +++ b/navlib/src/main/res/drawable/profile.xml @@ -0,0 +1,15 @@ + + + + + diff --git a/navlib/src/main/res/drawable/shadow_bottom.xml b/navlib/src/main/res/drawable/shadow_bottom.xml new file mode 100644 index 00000000..d8dec9b0 --- /dev/null +++ b/navlib/src/main/res/drawable/shadow_bottom.xml @@ -0,0 +1,8 @@ + + + + \ No newline at end of file diff --git a/navlib/src/main/res/drawable/shadow_right.xml b/navlib/src/main/res/drawable/shadow_right.xml new file mode 100644 index 00000000..36223703 --- /dev/null +++ b/navlib/src/main/res/drawable/shadow_right.xml @@ -0,0 +1,8 @@ + + + + \ No newline at end of file diff --git a/navlib/src/main/res/layout/material_drawer.xml b/navlib/src/main/res/layout/material_drawer.xml new file mode 100644 index 00000000..9369ea24 --- /dev/null +++ b/navlib/src/main/res/layout/material_drawer.xml @@ -0,0 +1,6 @@ + + \ No newline at end of file diff --git a/navlib/src/main/res/layout/material_drawer_slider.xml b/navlib/src/main/res/layout/material_drawer_slider.xml new file mode 100644 index 00000000..a903073d --- /dev/null +++ b/navlib/src/main/res/layout/material_drawer_slider.xml @@ -0,0 +1,25 @@ + + + + + \ No newline at end of file diff --git a/navlib/src/main/res/layout/nav_bottom_sheet.xml b/navlib/src/main/res/layout/nav_bottom_sheet.xml new file mode 100644 index 00000000..5cb25d10 --- /dev/null +++ b/navlib/src/main/res/layout/nav_bottom_sheet.xml @@ -0,0 +1,109 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/navlib/src/main/res/layout/nav_bs_item_primary.xml b/navlib/src/main/res/layout/nav_bs_item_primary.xml new file mode 100644 index 00000000..7092d1a6 --- /dev/null +++ b/navlib/src/main/res/layout/nav_bs_item_primary.xml @@ -0,0 +1,49 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/navlib/src/main/res/layout/nav_bs_item_separator.xml b/navlib/src/main/res/layout/nav_bs_item_separator.xml new file mode 100644 index 00000000..2f948ca4 --- /dev/null +++ b/navlib/src/main/res/layout/nav_bs_item_separator.xml @@ -0,0 +1,12 @@ + + + + + \ No newline at end of file diff --git a/navlib/src/main/res/layout/nav_view.xml b/navlib/src/main/res/layout/nav_view.xml new file mode 100644 index 00000000..4b46d117 --- /dev/null +++ b/navlib/src/main/res/layout/nav_view.xml @@ -0,0 +1,211 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/navlib/src/main/res/values-w600dp/styles.xml b/navlib/src/main/res/values-w600dp/styles.xml new file mode 100644 index 00000000..6674487f --- /dev/null +++ b/navlib/src/main/res/values-w600dp/styles.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/navlib/src/main/res/values/attrs.xml b/navlib/src/main/res/values/attrs.xml new file mode 100644 index 00000000..a239e4e4 --- /dev/null +++ b/navlib/src/main/res/values/attrs.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/navlib/src/main/res/values/attrs_nav_view.xml b/navlib/src/main/res/values/attrs_nav_view.xml new file mode 100644 index 00000000..c6169965 --- /dev/null +++ b/navlib/src/main/res/values/attrs_nav_view.xml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/navlib/src/main/res/values/colors.xml b/navlib/src/main/res/values/colors.xml new file mode 100644 index 00000000..e881e7bd --- /dev/null +++ b/navlib/src/main/res/values/colors.xml @@ -0,0 +1,19 @@ + + + #202196f3 + #154FBC + + #ffffff + #242424 + #000000 + + #0dffffff + #12ffffff + #14ffffff + #17ffffff + #1cffffff + #1fffffff + #24ffffff + #26ffffff + #29ffffff + \ No newline at end of file diff --git a/navlib/src/main/res/values/dimens.xml b/navlib/src/main/res/values/dimens.xml new file mode 100644 index 00000000..3a8e4fd5 --- /dev/null +++ b/navlib/src/main/res/values/dimens.xml @@ -0,0 +1,4 @@ + + + 10sp + \ No newline at end of file diff --git a/navlib/src/main/res/values/strings.xml b/navlib/src/main/res/values/strings.xml new file mode 100644 index 00000000..6f67dcd6 --- /dev/null +++ b/navlib/src/main/res/values/strings.xml @@ -0,0 +1,4 @@ + + NavLib + %1$s + diff --git a/navlib/src/main/res/values/styles.xml b/navlib/src/main/res/values/styles.xml new file mode 100644 index 00000000..6ad92c22 --- /dev/null +++ b/navlib/src/main/res/values/styles.xml @@ -0,0 +1,159 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/settings.gradle b/settings.gradle index 02529180..d8939d3d 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,2 +1,2 @@ rootProject.name='Szkolny.eu' -include ':app' +include ':app', ':navlib', ':navlib-font'