├── app
├── .gitignore
├── src
│ └── main
│ │ ├── res
│ │ ├── values
│ │ │ ├── strings.xml
│ │ │ ├── colors.xml
│ │ │ └── themes.xml
│ │ ├── mipmap-hdpi
│ │ │ ├── ic_launcher.webp
│ │ │ └── ic_launcher_round.webp
│ │ ├── mipmap-mdpi
│ │ │ ├── ic_launcher.webp
│ │ │ └── ic_launcher_round.webp
│ │ ├── mipmap-xhdpi
│ │ │ ├── ic_launcher.webp
│ │ │ └── ic_launcher_round.webp
│ │ ├── mipmap-xxhdpi
│ │ │ ├── ic_launcher.webp
│ │ │ └── ic_launcher_round.webp
│ │ ├── mipmap-xxxhdpi
│ │ │ ├── ic_launcher.webp
│ │ │ └── ic_launcher_round.webp
│ │ ├── mipmap-anydpi-v26
│ │ │ ├── ic_launcher.xml
│ │ │ └── ic_launcher_round.xml
│ │ ├── layout
│ │ │ └── activity_main.xml
│ │ ├── values-night
│ │ │ └── themes.xml
│ │ ├── drawable-v24
│ │ │ └── ic_launcher_foreground.xml
│ │ └── drawable
│ │ │ └── ic_launcher_background.xml
│ │ ├── java
│ │ └── com
│ │ │ └── dizcoding
│ │ │ └── collection
│ │ │ └── MainActivity.kt
│ │ └── AndroidManifest.xml
├── proguard-rules.pro
└── build.gradle
├── base
├── .gitignore
├── consumer-rules.pro
├── src
│ └── main
│ │ ├── res
│ │ ├── values
│ │ │ ├── strings.xml
│ │ │ ├── colors.xml
│ │ │ ├── themes.xml
│ │ │ └── dimens.xml
│ │ └── drawable
│ │ │ └── dizcoding_base_white_background_with_top_corner_15dp.xml
│ │ ├── AndroidManifest.xml
│ │ └── java
│ │ └── com
│ │ └── dizcoding
│ │ └── base
│ │ ├── extension
│ │ ├── ViewExtension.kt
│ │ ├── DialogExtension.kt
│ │ └── ContextExtension.kt
│ │ ├── BaseMapper.kt
│ │ ├── BaseFragment.kt
│ │ ├── BaseActivity.kt
│ │ ├── BaseBottomSheetDialogFragment.kt
│ │ ├── BaseDialog.kt
│ │ └── BaseDialogFragment.kt
├── proguard-rules.pro
└── build.gradle
├── showcase
├── .gitignore
├── consumer-rules.pro
├── src
│ └── main
│ │ ├── AndroidManifest.xml
│ │ ├── java
│ │ └── com
│ │ │ └── dizcoding
│ │ │ └── showcase
│ │ │ ├── ShowCaseListener.java
│ │ │ ├── ShowCaseContentPosition.java
│ │ │ ├── ShowCasePreference.java
│ │ │ ├── ShowCaseObject.java
│ │ │ ├── ViewHelper.java
│ │ │ ├── ShowCaseBuilder.java
│ │ │ ├── ShowCaseDialog.java
│ │ │ └── ShowCaseLayout.java
│ │ └── res
│ │ ├── values
│ │ ├── public.xml
│ │ ├── strings.xml
│ │ ├── colors.xml
│ │ ├── dimens.xml
│ │ └── styles.xml
│ │ ├── drawable
│ │ ├── rectangle_rounded.xml
│ │ ├── background_round_white_10.xml
│ │ ├── selector_white.xml
│ │ ├── selector_circle_blue.xml
│ │ ├── selector_circle_green.xml
│ │ ├── ic_button_prev.xml
│ │ ├── ic_button_finish.xml
│ │ ├── ic_button_next.xml
│ │ └── ic_button_skip.xml
│ │ ├── layout
│ │ ├── circle_green_view.xml
│ │ └── tutorial_view.xml
│ │ └── drawable-v21
│ │ └── selector_white.xml
├── proguard-rules.pro
└── build.gradle
├── README.md
├── adapterdelegate
├── .gitignore
├── consumer-rules.pro
├── src
│ └── main
│ │ ├── AndroidManifest.xml
│ │ ├── java
│ │ └── com
│ │ │ └── dizcoding
│ │ │ └── adapterdelegate
│ │ │ ├── LayoutContainer.kt
│ │ │ ├── ItemDelegate.kt
│ │ │ ├── CreateItemDelegate.kt
│ │ │ ├── ClickableItemDelegate.kt
│ │ │ ├── BindItemDelegate.kt
│ │ │ ├── ExtensionViewHolder.kt
│ │ │ ├── LayoutItemDelegate.kt
│ │ │ ├── ExtensionAdapterDelegate.kt
│ │ │ ├── EndlessRecyclerViewScrollListener.kt
│ │ │ └── DelegatesAdapter.kt
│ │ └── res
│ │ └── drawable
│ │ └── vector_ic_peoples_blue.xml
├── proguard-rules.pro
└── build.gradle
├── .idea
├── .gitignore
├── compiler.xml
├── vcs.xml
├── misc.xml
└── gradle.xml
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── .gitignore
├── settings.gradle
├── gradle.properties
├── gradlew.bat
└── gradlew
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/base/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/base/consumer-rules.pro:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/showcase/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/showcase/consumer-rules.pro:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # android-collection
--------------------------------------------------------------------------------
/adapterdelegate/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/adapterdelegate/consumer-rules.pro:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.idea/.gitignore:
--------------------------------------------------------------------------------
1 | # Default ignored files
2 | /shelf/
3 | /workspace.xml
4 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | Collection
3 |
--------------------------------------------------------------------------------
/base/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | Collection
3 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gandhi-wibowo/android-collection/main/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gandhi-wibowo/android-collection/main/app/src/main/res/mipmap-hdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gandhi-wibowo/android-collection/main/app/src/main/res/mipmap-mdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gandhi-wibowo/android-collection/main/app/src/main/res/mipmap-xhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gandhi-wibowo/android-collection/main/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gandhi-wibowo/android-collection/main/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gandhi-wibowo/android-collection/main/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gandhi-wibowo/android-collection/main/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gandhi-wibowo/android-collection/main/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gandhi-wibowo/android-collection/main/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gandhi-wibowo/android-collection/main/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/base/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
--------------------------------------------------------------------------------
/.idea/compiler.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/showcase/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
--------------------------------------------------------------------------------
/adapterdelegate/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
--------------------------------------------------------------------------------
/adapterdelegate/src/main/java/com/dizcoding/adapterdelegate/LayoutContainer.kt:
--------------------------------------------------------------------------------
1 | package com.dizcoding.adapterdelegate
2 |
3 | import android.view.View
4 |
5 | interface LayoutContainer {
6 | val containerView: View
7 | }
--------------------------------------------------------------------------------
/showcase/src/main/java/com/dizcoding/showcase/ShowCaseListener.java:
--------------------------------------------------------------------------------
1 | package com.dizcoding.showcase;
2 |
3 | public interface ShowCaseListener {
4 | void onPrevious();
5 | void onNext();
6 |
7 | void onComplete();
8 | }
9 |
--------------------------------------------------------------------------------
/showcase/src/main/res/values/public.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/showcase/src/main/res/drawable/rectangle_rounded.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Fri Apr 15 23:20:29 WIB 2022
2 | distributionBase=GRADLE_USER_HOME
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-bin.zip
4 | distributionPath=wrapper/dists
5 | zipStorePath=wrapper/dists
6 | zipStoreBase=GRADLE_USER_HOME
7 |
--------------------------------------------------------------------------------
/showcase/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | tutors
3 |
4 | <
5 | >
6 | OK
7 | Skip
8 |
9 |
--------------------------------------------------------------------------------
/showcase/src/main/res/drawable/background_round_white_10.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea/caches
5 | /.idea/libraries
6 | /.idea/modules.xml
7 | /.idea/workspace.xml
8 | /.idea/navEditor.xml
9 | /.idea/assetWizardSettings.xml
10 | .DS_Store
11 | /build
12 | /captures
13 | .externalNativeBuild
14 | .cxx
15 | local.properties
16 |
--------------------------------------------------------------------------------
/showcase/src/main/res/layout/circle_green_view.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/base/src/main/java/com/dizcoding/base/extension/ViewExtension.kt:
--------------------------------------------------------------------------------
1 | package com.dizcoding.base.extension
2 |
3 | import android.view.View
4 |
5 | fun View?.avoidDoubleClicks() {
6 | this ?: return
7 | if (!this.isClickable) {
8 | return
9 | }
10 | this.isClickable = false
11 | this.postDelayed({ this.isClickable = true }, 1000L)
12 | }
--------------------------------------------------------------------------------
/showcase/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #B0000000
4 | #42b549
5 | #1B4DB3
6 | #FFFFFF
7 | #DDDDDD
8 | #434655
9 |
--------------------------------------------------------------------------------
/base/src/main/java/com/dizcoding/base/BaseMapper.kt:
--------------------------------------------------------------------------------
1 | package com.dizcoding.base
2 |
3 | abstract class BaseMapper {
4 | fun apply(oldItem: T): R {
5 | return map(oldItem)
6 | }
7 |
8 | fun apply(oldItem: List): List {
9 | return oldItem.map {
10 | apply(it)
11 | }
12 | }
13 |
14 | protected abstract fun map(oldItem: T): R
15 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/dizcoding/collection/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package com.dizcoding.collection
2 |
3 | import androidx.appcompat.app.AppCompatActivity
4 | import android.os.Bundle
5 |
6 | class MainActivity : AppCompatActivity() {
7 | override fun onCreate(savedInstanceState: Bundle?) {
8 | super.onCreate(savedInstanceState)
9 | setContentView(R.layout.activity_main)
10 | }
11 | }
--------------------------------------------------------------------------------
/adapterdelegate/src/main/java/com/dizcoding/adapterdelegate/ItemDelegate.kt:
--------------------------------------------------------------------------------
1 | package com.dizcoding.adapterdelegate
2 |
3 | import android.view.ViewGroup
4 | import androidx.recyclerview.widget.RecyclerView
5 |
6 | interface ItemDelegate {
7 | fun itemType(): Class
8 | fun createViewHolder(parent: ViewGroup): H
9 | fun bindView(position: Int, item: I, holder: H)
10 | }
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | dependencyResolutionManagement {
2 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
3 | repositories {
4 | google()
5 | mavenCentral()
6 | jcenter() // Warning: this repository is going to shut down soon
7 | }
8 | }
9 | rootProject.name = "Collection"
10 | include ':app'
11 | include ':adapterdelegate'
12 | include ':showcase'
13 | include ':base'
14 |
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #FFBB86FC
4 | #FF6200EE
5 | #FF3700B3
6 | #FF03DAC5
7 | #FF018786
8 | #FF000000
9 | #FFFFFFFF
10 |
--------------------------------------------------------------------------------
/base/src/main/res/drawable/dizcoding_base_white_background_with_top_corner_15dp.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
7 |
8 |
--------------------------------------------------------------------------------
/base/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #FFBB86FC
4 | #FF6200EE
5 | #FF3700B3
6 | #FF03DAC5
7 | #FF018786
8 | #FF000000
9 | #FFFFFFFF
10 |
--------------------------------------------------------------------------------
/adapterdelegate/src/main/java/com/dizcoding/adapterdelegate/CreateItemDelegate.kt:
--------------------------------------------------------------------------------
1 | package com.dizcoding.adapterdelegate
2 |
3 | import android.view.ViewGroup
4 |
5 | class CreateItemDelegate(
6 | private val origin: ExtensionItemDelegate,
7 | private val createBlock: ExtensionViewHolder.() -> Unit
8 | ) : ExtensionItemDelegate by origin {
9 |
10 | override fun createViewHolder(parent: ViewGroup) = origin.createViewHolder(parent).apply(createBlock)
11 | }
--------------------------------------------------------------------------------
/showcase/src/main/java/com/dizcoding/showcase/ShowCaseContentPosition.java:
--------------------------------------------------------------------------------
1 | package com.dizcoding.showcase;
2 |
3 | public enum ShowCaseContentPosition {
4 | UNDEFINED (0),
5 | TOP (1),
6 | RIGHT (2),
7 | BOTTOM (3),
8 | LEFT (4);
9 |
10 | private final int position;
11 |
12 | ShowCaseContentPosition(int position) {
13 | this.position = position;
14 | }
15 |
16 | public int getPosition() {
17 | return position;
18 | }
19 | }
20 |
21 |
--------------------------------------------------------------------------------
/showcase/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 16dp
4 | 18sp
5 | 14sp
6 | 1dp
7 |
8 | 10dp
9 | 16dp
10 | 2dp
11 |
12 | 85dp
13 |
--------------------------------------------------------------------------------
/adapterdelegate/src/main/java/com/dizcoding/adapterdelegate/ClickableItemDelegate.kt:
--------------------------------------------------------------------------------
1 | package com.dizcoding.adapterdelegate
2 |
3 | import android.view.ViewGroup
4 |
5 | class ClickableItemDelegate(
6 | private val origin: ExtensionItemDelegate,
7 | private val onClick: (I) -> Unit
8 | ) : ExtensionItemDelegate by origin {
9 | override fun createViewHolder(parent: ViewGroup) =
10 | origin.createViewHolder(parent).apply {
11 | itemView bindClick onClick
12 | }
13 | }
--------------------------------------------------------------------------------
/adapterdelegate/src/main/java/com/dizcoding/adapterdelegate/BindItemDelegate.kt:
--------------------------------------------------------------------------------
1 | package com.dizcoding.adapterdelegate
2 |
3 | class BindItemDelegate(
4 | private val origin: ExtensionItemDelegate,
5 | private val bindBlock: ExtensionViewHolder.(I) -> Unit
6 | ) : ExtensionItemDelegate by origin {
7 |
8 | override fun bindView(position: Int, item: I, holder: ExtensionViewHolder) {
9 | origin.bindView(position, item, holder)
10 | holder.holdItem?.let { holder.bindBlock(it) }
11 | }
12 | }
--------------------------------------------------------------------------------
/adapterdelegate/src/main/java/com/dizcoding/adapterdelegate/ExtensionViewHolder.kt:
--------------------------------------------------------------------------------
1 | package com.dizcoding.adapterdelegate
2 |
3 | import android.view.View
4 | import androidx.recyclerview.widget.RecyclerView
5 |
6 | class ExtensionViewHolder(override val containerView: View) :
7 | RecyclerView.ViewHolder(containerView), LayoutContainer {
8 | var holdItem: I? = null
9 |
10 | inline infix fun View.bindClick(
11 | crossinline onClick: (I) -> Unit
12 | ) = setOnClickListener { holdItem?.let(onClick) }
13 | }
14 |
--------------------------------------------------------------------------------
/showcase/src/main/res/drawable-v21/selector_white.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | -
4 |
6 |
7 |
8 |
9 | -
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/showcase/src/main/res/drawable/selector_white.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | -
4 |
5 |
6 |
7 |
8 | -
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/base/src/main/res/values/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
10 |
14 |
15 |
--------------------------------------------------------------------------------
/showcase/src/main/res/drawable/selector_circle_blue.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | -
5 |
7 |
8 |
9 |
10 | -
11 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/showcase/src/main/res/drawable/selector_circle_green.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | -
5 |
7 |
8 |
9 |
10 | -
11 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/adapterdelegate/src/main/java/com/dizcoding/adapterdelegate/LayoutItemDelegate.kt:
--------------------------------------------------------------------------------
1 | package com.dizcoding.adapterdelegate
2 |
3 | import android.view.LayoutInflater
4 | import android.view.ViewGroup
5 | import androidx.annotation.LayoutRes
6 |
7 | class LayoutItemDelegate(
8 | private val type: Class,
9 | @LayoutRes private val layoutId: Int
10 | ) : ExtensionItemDelegate {
11 |
12 | override fun itemType() = type
13 |
14 | override fun createViewHolder(parent: ViewGroup) =
15 | ExtensionViewHolder(LayoutInflater.from(parent.context).inflate(layoutId, parent, false))
16 |
17 | override fun bindView(position: Int, item: I, holder: ExtensionViewHolder) {
18 | holder.holdItem = item
19 | }
20 | }
--------------------------------------------------------------------------------
/showcase/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
14 |
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/base/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/showcase/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/adapterdelegate/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
17 |
18 |
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
12 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/app/src/main/res/values/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
16 |
--------------------------------------------------------------------------------
/app/src/main/res/values-night/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
16 |
--------------------------------------------------------------------------------
/base/src/main/java/com/dizcoding/base/extension/DialogExtension.kt:
--------------------------------------------------------------------------------
1 | package com.dizcoding.base.extension
2 |
3 | import android.app.Dialog
4 | import android.util.DisplayMetrics
5 |
6 | var deviceWidth = 0
7 | var deviceHeight = 0
8 |
9 | fun Dialog.getWidth(): Int {
10 | return if (deviceWidth == 0) {
11 | val displayMetrics = DisplayMetrics()
12 | this.window?.windowManager?.defaultDisplay?.getMetrics(displayMetrics)
13 | deviceWidth = displayMetrics.widthPixels
14 | deviceWidth
15 | } else {
16 | deviceWidth
17 | }
18 | }
19 |
20 | fun Dialog.getHigh(): Int {
21 | return if (deviceHeight == 0) {
22 | val displayMetrics = DisplayMetrics()
23 | this.window?.windowManager?.defaultDisplay?.getMetrics(displayMetrics)
24 | deviceHeight = displayMetrics.heightPixels
25 | deviceHeight
26 | } else {
27 | deviceHeight
28 | }
29 | }
--------------------------------------------------------------------------------
/showcase/src/main/java/com/dizcoding/showcase/ShowCasePreference.java:
--------------------------------------------------------------------------------
1 | package com.dizcoding.showcase;
2 |
3 | import android.content.Context;
4 | import android.content.SharedPreferences;
5 |
6 | public class ShowCasePreference {
7 | private static final String SHOWCASE_PREFERENCES = "show_case_pref";
8 |
9 | public static boolean hasShown(Context context, String tag){
10 | SharedPreferences sharedPreferences = context.getSharedPreferences(SHOWCASE_PREFERENCES,
11 | Context.MODE_PRIVATE);
12 | return sharedPreferences.getBoolean(tag, false);
13 | }
14 |
15 | public static void setShown(Context context, String tag, boolean hasShown){
16 | SharedPreferences.Editor sharedPreferencesEditor = context.getSharedPreferences(SHOWCASE_PREFERENCES,
17 | Context.MODE_PRIVATE).edit();
18 | sharedPreferencesEditor.putBoolean (tag, hasShown);
19 | sharedPreferencesEditor.apply();
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/.idea/gradle.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
23 |
24 |
--------------------------------------------------------------------------------
/showcase/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id 'com.android.library'
3 | }
4 | android {
5 | compileSdkVersion 30
6 | buildToolsVersion "30.0.2"
7 |
8 | defaultConfig {
9 | minSdkVersion 16
10 | targetSdkVersion 30
11 | versionCode 1
12 | versionName "1.0"
13 |
14 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
15 | consumerProguardFiles "consumer-rules.pro"
16 | }
17 |
18 | buildTypes {
19 | release {
20 | minifyEnabled false
21 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
22 | }
23 | }
24 | compileOptions {
25 | sourceCompatibility JavaVersion.VERSION_1_8
26 | targetCompatibility JavaVersion.VERSION_1_8
27 | }
28 | }
29 |
30 | dependencies {
31 |
32 | implementation "androidx.appcompat:appcompat:1.2.0"
33 | implementation "com.google.android.material:material:1.2.1"
34 | testImplementation 'junit:junit:4.+'
35 | androidTestImplementation 'androidx.test.ext:junit:1.1.3'
36 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
37 | }
--------------------------------------------------------------------------------
/adapterdelegate/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id 'com.android.library'
3 | id 'kotlin-android'
4 | }
5 | android {
6 | compileSdkVersion 30
7 | buildToolsVersion "30.0.2"
8 |
9 | defaultConfig {
10 | minSdkVersion 16
11 | targetSdkVersion 30
12 | versionCode 1
13 | versionName "1.0"
14 |
15 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
16 | consumerProguardFiles "consumer-rules.pro"
17 | }
18 |
19 | buildTypes {
20 | release {
21 | minifyEnabled false
22 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
23 | }
24 | }
25 | compileOptions {
26 | sourceCompatibility JavaVersion.VERSION_1_8
27 | targetCompatibility JavaVersion.VERSION_1_8
28 | }
29 | kotlinOptions {
30 | jvmTarget = '1.8'
31 | }
32 | }
33 |
34 | dependencies {
35 | implementation "androidx.appcompat:appcompat:1.2.0"
36 | implementation "com.google.android.material:material:1.2.1"
37 | testImplementation 'junit:junit:4.+'
38 | androidTestImplementation 'androidx.test.ext:junit:1.1.3'
39 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
40 | }
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 | # IDE (e.g. Android Studio) users:
3 | # Gradle settings configured through the IDE *will override*
4 | # any settings specified in this file.
5 | # For more details on how to configure your build environment visit
6 | # http://www.gradle.org/docs/current/userguide/build_environment.html
7 | # Specifies the JVM arguments used for the daemon process.
8 | # The setting is particularly useful for tweaking memory settings.
9 | org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
10 | # When configured, Gradle will run in incubating parallel mode.
11 | # This option should only be used with decoupled projects. More details, visit
12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
13 | # org.gradle.parallel=true
14 | # AndroidX package structure to make it clearer which packages are bundled with the
15 | # Android operating system, and which are packaged with your app"s APK
16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn
17 | android.useAndroidX=true
18 | # Automatically convert third-party libraries to use AndroidX
19 | android.enableJetifier=true
20 | # Kotlin code style for this project: "official" or "obsolete":
21 | kotlin.code.style=official
--------------------------------------------------------------------------------
/showcase/src/main/res/drawable/ic_button_prev.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
13 |
14 |
--------------------------------------------------------------------------------
/showcase/src/main/res/drawable/ic_button_finish.xml:
--------------------------------------------------------------------------------
1 |
7 |
9 |
10 |
16 |
17 |
18 |
19 |
20 |
21 |
26 |
27 |
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id 'com.android.application'
3 | id 'kotlin-android'
4 | }
5 |
6 | android {
7 | compileSdk 31
8 |
9 | defaultConfig {
10 | applicationId "com.dizcoding.collection"
11 | minSdk 21
12 | targetSdk 31
13 | versionCode 1
14 | versionName "1.0"
15 |
16 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
17 | }
18 |
19 | buildTypes {
20 | release {
21 | minifyEnabled false
22 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
23 | }
24 | }
25 | compileOptions {
26 | sourceCompatibility JavaVersion.VERSION_1_8
27 | targetCompatibility JavaVersion.VERSION_1_8
28 | }
29 | kotlinOptions {
30 | jvmTarget = '1.8'
31 | }
32 | // enable viewbinding
33 | buildFeatures {
34 | viewBinding = true
35 | }
36 | }
37 |
38 | dependencies {
39 |
40 | implementation 'androidx.core:core-ktx:1.7.0'
41 | implementation 'androidx.appcompat:appcompat:1.4.1'
42 | implementation 'com.google.android.material:material:1.5.0'
43 | implementation 'androidx.constraintlayout:constraintlayout:2.1.3'
44 |
45 | implementation project(path: ':adapterdelegate')
46 | implementation project(path: ':showcase')
47 |
48 | }
--------------------------------------------------------------------------------
/adapterdelegate/src/main/java/com/dizcoding/adapterdelegate/ExtensionAdapterDelegate.kt:
--------------------------------------------------------------------------------
1 | package com.dizcoding.adapterdelegate
2 |
3 | import android.annotation.SuppressLint
4 | import androidx.annotation.LayoutRes
5 |
6 | typealias ExtensionItemDelegate = ItemDelegate>
7 |
8 |
9 | inline fun itemDelegate(
10 | @LayoutRes layoutId: Int
11 | ) = LayoutItemDelegate(T::class.java, layoutId)
12 |
13 | fun ExtensionItemDelegate.click(
14 | onClick: (T) -> Unit
15 | ) = ClickableItemDelegate(this, onClick)
16 |
17 | fun ExtensionItemDelegate.create(
18 | block: ExtensionViewHolder.() -> Unit
19 | ) = CreateItemDelegate(this, block)
20 |
21 | fun ExtensionItemDelegate.bind(
22 | block: ExtensionViewHolder.(item: T) -> Unit
23 | ) = BindItemDelegate(this, block)
24 |
25 |
26 | fun DelegatesAdapter.getItem(
27 | predict: (I) -> Boolean
28 | ): I? {
29 | return this.items.find(predict)
30 | }
31 |
32 | @SuppressLint("NotifyDataSetChanged")
33 | fun DelegatesAdapter.updateItems(
34 | item: List
35 | ) {
36 | this.items = item
37 | notifyDataSetChanged()
38 | }
39 |
40 | fun DelegatesAdapter.updateItem(
41 | item: I, itemPosition: Int
42 | ) {
43 | this.items.toMutableList()[itemPosition] = item
44 | notifyItemChanged(itemPosition)
45 | }
--------------------------------------------------------------------------------
/base/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id 'com.android.library'
3 | id 'kotlin-android'
4 | }
5 | android {
6 | compileSdk 31
7 | compileSdkVersion 31
8 | buildToolsVersion '30.0.3'
9 |
10 | defaultConfig {
11 | minSdk 16
12 | targetSdk 31
13 | versionCode 1
14 | versionName "1.0"
15 |
16 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
17 | consumerProguardFiles "consumer-rules.pro"
18 | targetSdkVersion 31
19 | minSdkVersion 16
20 | }
21 |
22 | buildTypes {
23 | release {
24 | minifyEnabled false
25 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
26 | }
27 | }
28 | compileOptions {
29 | sourceCompatibility JavaVersion.VERSION_1_8
30 | targetCompatibility JavaVersion.VERSION_1_8
31 | }
32 | kotlinOptions {
33 | jvmTarget = '1.8'
34 | }
35 | // enable viewbinding
36 | buildFeatures {
37 | viewBinding = true
38 | }
39 |
40 | }
41 |
42 | dependencies {
43 |
44 | implementation 'androidx.core:core-ktx:1.7.0'
45 | implementation "androidx.appcompat:appcompat:1.2.0"
46 | implementation "com.google.android.material:material:1.2.1"
47 |
48 | testImplementation 'junit:junit:4.+'
49 | androidTestImplementation 'androidx.test.ext:junit:1.1.3'
50 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
51 | }
--------------------------------------------------------------------------------
/adapterdelegate/src/main/java/com/dizcoding/adapterdelegate/EndlessRecyclerViewScrollListener.kt:
--------------------------------------------------------------------------------
1 | package com.dizcoding.adapterdelegate
2 |
3 | import androidx.recyclerview.widget.LinearLayoutManager
4 | import androidx.recyclerview.widget.RecyclerView
5 |
6 |
7 | abstract class EndlessRecyclerViewScrollListener(layoutManager: LinearLayoutManager) :
8 | RecyclerView.OnScrollListener() {
9 | private val visibleThreshold = 5
10 | private var previousTotalItemCount = 0
11 | private var loading = true
12 | var mLayoutManager: RecyclerView.LayoutManager = layoutManager
13 | override fun onScrolled(view: RecyclerView, dx: Int, dy: Int) {
14 | var lastVisibleItemPosition = 0
15 | val totalItemCount = mLayoutManager.itemCount
16 | lastVisibleItemPosition =
17 | (mLayoutManager as LinearLayoutManager).findLastVisibleItemPosition()
18 | if (totalItemCount < previousTotalItemCount) {
19 | previousTotalItemCount = totalItemCount
20 | if (totalItemCount == 0) {
21 | loading = true
22 | }
23 | }
24 | if (loading && totalItemCount > previousTotalItemCount) {
25 | loading = false
26 | previousTotalItemCount = totalItemCount
27 | }
28 | if (!loading && lastVisibleItemPosition + visibleThreshold > totalItemCount) {
29 | onLoadMore()
30 | loading = true
31 | }
32 | }
33 |
34 | fun resetState() {
35 | previousTotalItemCount = 0
36 | loading = true
37 | }
38 |
39 | abstract fun onLoadMore()
40 |
41 | }
--------------------------------------------------------------------------------
/showcase/src/main/res/drawable/ic_button_next.xml:
--------------------------------------------------------------------------------
1 |
7 |
9 |
10 |
16 |
17 |
18 |
19 |
20 |
21 |
25 |
26 |
--------------------------------------------------------------------------------
/base/src/main/java/com/dizcoding/base/BaseFragment.kt:
--------------------------------------------------------------------------------
1 | package com.dizcoding.base
2 |
3 | import android.os.Bundle
4 | import android.view.LayoutInflater
5 | import android.view.View
6 | import android.view.ViewGroup
7 | import androidx.core.os.bundleOf
8 | import androidx.fragment.app.DialogFragment
9 | import androidx.fragment.app.Fragment
10 | import androidx.viewbinding.ViewBinding
11 | import com.dizcoding.base.extension.avoidDoubleClicks
12 |
13 |
14 | abstract class BaseFragment : Fragment(), View.OnClickListener {
15 |
16 | private var _binding: ViewBinding? = null
17 | abstract val bindingInflater: (LayoutInflater, ViewGroup?, Boolean) -> VB
18 |
19 | @Suppress("UNCHECKED_CAST")
20 | protected val binding: VB
21 | get() = _binding as VB
22 |
23 | override fun onCreateView(
24 | inflater: LayoutInflater,
25 | container: ViewGroup?,
26 | savedInstanceState: Bundle?
27 | ): View? {
28 | _binding = bindingInflater.invoke(inflater, container, false)
29 | return requireNotNull(_binding).root
30 | }
31 |
32 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
33 | super.onViewCreated(view, savedInstanceState)
34 |
35 | setup()
36 | }
37 |
38 | abstract fun setup()
39 |
40 | override fun onDestroyView() {
41 | super.onDestroyView()
42 | _binding = null
43 | }
44 |
45 | override fun onClick(v: View?) {
46 | v.avoidDoubleClicks()
47 | }
48 |
49 | fun showDialogFragment(dialogFragment: DialogFragment, bundle: Bundle = bundleOf()) {
50 | if (!bundle.isEmpty) dialogFragment.arguments = bundle
51 | dialogFragment.show(childFragmentManager, "Tag")
52 | }
53 | }
--------------------------------------------------------------------------------
/adapterdelegate/src/main/java/com/dizcoding/adapterdelegate/DelegatesAdapter.kt:
--------------------------------------------------------------------------------
1 | package com.dizcoding.adapterdelegate
2 |
3 | import android.annotation.SuppressLint
4 | import android.view.ViewGroup
5 | import androidx.recyclerview.widget.RecyclerView
6 |
7 | class DelegatesAdapter(
8 | vararg delegates: ItemDelegate
9 | ) : RecyclerView.Adapter() {
10 |
11 | private val delegatesIndexMap: Map, Int>
12 | private val delegatesList: List>
13 |
14 | var items = emptyList()
15 |
16 | init {
17 | val map = mutableMapOf, Int>()
18 | delegates.forEachIndexed { index, delegate ->
19 | if (map.put(delegate.itemType(), index) != null) {
20 | throw IllegalArgumentException()
21 | }
22 | }
23 |
24 | delegatesIndexMap = map
25 | delegatesList = delegates.map {
26 | @Suppress("UNCHECKED_CAST")
27 | it as? ItemDelegate
28 | ?: throw IllegalArgumentException()
29 | }
30 | }
31 |
32 | override fun getItemViewType(position: Int) =
33 | delegatesIndexMap[items[position]::class.java] ?: throw IllegalArgumentException()
34 |
35 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) =
36 | delegatesList[viewType].createViewHolder(parent)
37 |
38 | override fun getItemCount() = items.size
39 |
40 | override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
41 | delegatesList[getItemViewType(position)].bindView(position, items[position], holder)
42 | }
43 |
44 | @SuppressLint("NotifyDataSetChanged")
45 | fun submitList(list: List) {
46 | items = list
47 | notifyDataSetChanged()
48 | }
49 | }
--------------------------------------------------------------------------------
/app/src/main/res/drawable-v24/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
15 |
18 |
21 |
22 |
23 |
24 |
30 |
--------------------------------------------------------------------------------
/base/src/main/java/com/dizcoding/base/BaseActivity.kt:
--------------------------------------------------------------------------------
1 | package com.dizcoding.base
2 |
3 | import android.content.Intent
4 | import android.os.Bundle
5 | import android.view.LayoutInflater
6 | import android.view.View
7 | import androidx.appcompat.app.AppCompatActivity
8 | import androidx.core.os.bundleOf
9 | import androidx.fragment.app.DialogFragment
10 | import androidx.viewbinding.ViewBinding
11 | import com.dizcoding.base.extension.avoidDoubleClicks
12 |
13 | abstract class BaseActivity : AppCompatActivity(), View.OnClickListener {
14 | private var _binding: ViewBinding? = null
15 | abstract val bindingInflater: (LayoutInflater) -> VB
16 |
17 | @Suppress("UNCHECKED_CAST")
18 | protected val binding: VB
19 | get() = _binding as VB
20 |
21 | override fun onCreate(savedInstanceState: Bundle?) {
22 | super.onCreate(savedInstanceState)
23 | _binding = bindingInflater.invoke(layoutInflater)
24 | setContentView(requireNotNull(_binding).root)
25 | setup()
26 | }
27 |
28 | abstract fun setup()
29 |
30 | override fun onDestroy() {
31 | super.onDestroy()
32 | _binding = null
33 | }
34 |
35 | override fun onClick(v: View?) {
36 | v.avoidDoubleClicks()
37 | }
38 |
39 | fun intentTo(cls: Class<*>, vararg bundle: Bundle) {
40 | startActivity(
41 | Intent(this, cls)
42 | .apply {
43 | if (bundle.isNotEmpty()) {
44 | bundle.forEach {
45 | putExtras(it)
46 | }
47 | }
48 | }
49 | )
50 | }
51 |
52 | fun showDialogFragment(dialogFragment: DialogFragment, bundle: Bundle = bundleOf()) {
53 | if (!bundle.isEmpty) dialogFragment.arguments = bundle
54 | dialogFragment.show(supportFragmentManager, "Tag")
55 | }
56 | }
--------------------------------------------------------------------------------
/base/src/main/java/com/dizcoding/base/BaseBottomSheetDialogFragment.kt:
--------------------------------------------------------------------------------
1 | package com.dizcoding.base
2 |
3 | import android.app.Dialog
4 | import android.os.Bundle
5 | import android.view.LayoutInflater
6 | import android.view.View
7 | import android.view.ViewGroup
8 | import androidx.viewbinding.ViewBinding
9 | import com.dizcoding.base.extension.avoidDoubleClicks
10 | import com.google.android.material.bottomsheet.BottomSheetBehavior
11 | import com.google.android.material.bottomsheet.BottomSheetDialog
12 | import com.google.android.material.bottomsheet.BottomSheetDialogFragment
13 |
14 |
15 | abstract class BaseBottomSheetDialogFragment : BottomSheetDialogFragment(),
16 | View.OnClickListener {
17 |
18 | private var _binding: ViewBinding? = null
19 | abstract val bindingInflater: (LayoutInflater, ViewGroup?, Boolean) -> VB
20 |
21 | @Suppress("UNCHECKED_CAST")
22 | protected val binding: VB
23 | get() = _binding as VB
24 |
25 | protected abstract fun isFullHeigh(): Boolean
26 |
27 | override fun onCreateView(
28 | inflater: LayoutInflater,
29 | container: ViewGroup?,
30 | savedInstanceState: Bundle?
31 | ): View {
32 | _binding = bindingInflater.invoke(inflater, container, false)
33 | return requireNotNull(_binding).root
34 | }
35 |
36 | override fun getTheme(): Int = R.style.DizcodingBaseBottomSheetDialog
37 | override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
38 | val dialog = BottomSheetDialog(requireContext(), theme)
39 | if (isFullHeigh()) dialog.behavior.state = BottomSheetBehavior.STATE_EXPANDED
40 | return dialog
41 | }
42 |
43 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
44 | super.onViewCreated(view, savedInstanceState)
45 | setup()
46 | }
47 |
48 | abstract fun setup()
49 |
50 | override fun onDestroyView() {
51 | super.onDestroyView()
52 | _binding = null
53 | }
54 |
55 | override fun onClick(v: View?) {
56 | v.avoidDoubleClicks()
57 | }
58 | }
--------------------------------------------------------------------------------
/base/src/main/java/com/dizcoding/base/BaseDialog.kt:
--------------------------------------------------------------------------------
1 | package com.dizcoding.base
2 |
3 | import android.app.Dialog
4 | import android.content.Context
5 | import android.graphics.Color
6 | import android.graphics.drawable.ColorDrawable
7 | import android.os.Bundle
8 | import android.view.LayoutInflater
9 | import android.view.View
10 | import android.view.WindowManager
11 | import androidx.viewbinding.ViewBinding
12 | import com.dizcoding.base.extension.avoidDoubleClicks
13 | import com.dizcoding.base.extension.getWidth
14 |
15 | abstract class BaseDialog(context: Context) : Dialog(context),
16 | View.OnClickListener {
17 |
18 | private var _binding: ViewBinding? = null
19 | abstract val bindingInflater: (LayoutInflater) -> VB
20 | abstract val dialogCanCancle: Boolean
21 |
22 | @Suppress("UNCHECKED_CAST")
23 | protected val binding: VB
24 | get() = _binding as VB
25 |
26 | override fun onStart() {
27 | super.onStart()
28 | window?.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
29 | }
30 |
31 | override fun onCreate(savedInstanceState: Bundle?) {
32 | super.onCreate(savedInstanceState)
33 | _binding = bindingInflater.invoke(layoutInflater)
34 | setContentView(requireNotNull(_binding).root)
35 | setCancelable(dialogCanCancle)
36 |
37 | window?.setLayout(
38 | WindowManager.LayoutParams.MATCH_PARENT,
39 | WindowManager.LayoutParams.WRAP_CONTENT
40 | )
41 | val layoutParams = window?.attributes
42 | val width = getWidth()
43 | val dimens = context.resources?.getDimension(R.dimen.dizcoding_base_dimens_space_60)
44 |
45 | if (dimens != null) {
46 | layoutParams?.width = width - dimens.toInt()
47 | window?.attributes = layoutParams
48 | }
49 | setup()
50 | }
51 |
52 | abstract fun setup()
53 |
54 | override fun onDetachedFromWindow() {
55 | super.onDetachedFromWindow()
56 | _binding = null
57 | }
58 |
59 | override fun onClick(v: View?) {
60 | v.avoidDoubleClicks()
61 | }
62 | }
--------------------------------------------------------------------------------
/adapterdelegate/src/main/res/drawable/vector_ic_peoples_blue.xml:
--------------------------------------------------------------------------------
1 |
6 |
11 |
16 |
17 |
--------------------------------------------------------------------------------
/base/src/main/java/com/dizcoding/base/BaseDialogFragment.kt:
--------------------------------------------------------------------------------
1 | package com.dizcoding.base
2 |
3 | import android.graphics.Color
4 | import android.graphics.drawable.ColorDrawable
5 | import android.os.Bundle
6 | import android.view.LayoutInflater
7 | import android.view.View
8 | import android.view.ViewGroup
9 | import android.view.WindowManager
10 | import androidx.fragment.app.DialogFragment
11 | import androidx.viewbinding.ViewBinding
12 | import com.dizcoding.base.extension.avoidDoubleClicks
13 | import com.dizcoding.base.extension.getWidth
14 |
15 |
16 | abstract class BaseDialogFragment : DialogFragment(), View.OnClickListener {
17 |
18 | private var _binding: ViewBinding? = null
19 | abstract val bindingInflater: (LayoutInflater, ViewGroup?, Boolean) -> VB
20 |
21 | @Suppress("UNCHECKED_CAST")
22 | protected val binding: VB
23 | get() = _binding as VB
24 |
25 | override fun onCreateView(
26 | inflater: LayoutInflater,
27 | container: ViewGroup?,
28 | savedInstanceState: Bundle?
29 | ): View? {
30 | _binding = bindingInflater.invoke(inflater, container, false)
31 | return requireNotNull(_binding).root
32 | }
33 |
34 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
35 | super.onViewCreated(view, savedInstanceState)
36 | dialog?.window?.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
37 | dialog?.window?.setLayout(
38 | WindowManager.LayoutParams.MATCH_PARENT,
39 | WindowManager.LayoutParams.WRAP_CONTENT
40 | )
41 | val layoutParams = dialog?.window?.attributes
42 | val width = dialog?.getWidth() ?: 0
43 | val dimens = context?.resources?.getDimension(R.dimen.dizcoding_base_dimens_space_16)
44 |
45 | if (dimens != null) {
46 | layoutParams?.width = width - dimens.toInt()
47 | dialog?.window?.attributes = layoutParams
48 | }
49 | setup()
50 | }
51 |
52 | override fun onDestroyView() {
53 | super.onDestroyView()
54 | _binding = null
55 | }
56 |
57 | override fun onClick(v: View?) {
58 | v.avoidDoubleClicks()
59 | }
60 |
61 | abstract fun setup()
62 |
63 | }
--------------------------------------------------------------------------------
/showcase/src/main/java/com/dizcoding/showcase/ShowCaseObject.java:
--------------------------------------------------------------------------------
1 | package com.dizcoding.showcase;
2 |
3 | import android.view.View;
4 | import android.view.ViewGroup;
5 |
6 | import androidx.annotation.Nullable;
7 |
8 | public class ShowCaseObject {
9 |
10 | private View view;
11 | protected String title;
12 | protected String text;
13 | protected ShowCaseContentPosition showCaseContentPosition;
14 | protected int tintBackgroundColor;
15 | private ViewGroup scrollView;
16 |
17 | public ShowCaseObject(@Nullable View view, @Nullable String title, String text ) {
18 | this(view, title, text, ShowCaseContentPosition.UNDEFINED);
19 | }
20 | public ShowCaseObject(@Nullable View view, @Nullable String title,
21 | String text, ShowCaseContentPosition showCaseContentPosition) {
22 | this(view, title, text, showCaseContentPosition, 0);
23 | }
24 |
25 | public ShowCaseObject(@Nullable View view, @Nullable String title,
26 | String text, ShowCaseContentPosition showCaseContentPosition,
27 | int tintBackgroundColor) {
28 | this(view, title, text, showCaseContentPosition, tintBackgroundColor, null);
29 | }
30 | public ShowCaseObject(@Nullable View view, @Nullable String title,
31 | String text, ShowCaseContentPosition showCaseContentPosition,
32 | int tintBackgroundColor, ViewGroup scrollView) {
33 | this.view = view;
34 | this.title = title;
35 | this.text = text;
36 | this.showCaseContentPosition = showCaseContentPosition;
37 | this.tintBackgroundColor = tintBackgroundColor;
38 | this.scrollView = scrollView;
39 | }
40 |
41 | public String getTitle() {
42 | return title;
43 | }
44 |
45 | public String getText() {
46 | return text;
47 | }
48 |
49 | public int getTintBackgroundColor() {
50 | return tintBackgroundColor;
51 | }
52 |
53 | public ShowCaseContentPosition getShowCaseContentPosition() {
54 | return showCaseContentPosition;
55 | }
56 |
57 | private int[] location;
58 | private int radius;
59 | public ShowCaseObject withCustomTarget(int[] location, int radius){
60 | if (location.length != 2) {
61 | return this;
62 | }
63 | this.location = location;
64 | this.radius = radius;
65 | return this;
66 | }
67 |
68 | public ShowCaseObject withCustomTarget(int[] location){
69 | if (location.length!= 4) {
70 | return this;
71 | }
72 | this.location = location;
73 | return this;
74 | }
75 |
76 | public int[] getLocation() {
77 | return location;
78 | }
79 |
80 | public int getRadius() {
81 | return radius;
82 | }
83 |
84 | public View getView() {
85 | return view;
86 | }
87 | public ViewGroup getScrollView() {
88 | return scrollView;
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/base/src/main/java/com/dizcoding/base/extension/ContextExtension.kt:
--------------------------------------------------------------------------------
1 | package com.dizcoding.base.extension
2 |
3 | import android.app.DatePickerDialog
4 | import android.app.TimePickerDialog
5 | import android.content.Context
6 | import android.content.Intent
7 | import android.database.Cursor
8 | import android.net.Uri
9 | import android.provider.MediaStore
10 | import java.util.*
11 |
12 |
13 | fun Context.openChatWhatsApp(number: String, messages: String? = null) {
14 | var url = "https://api.whatsapp.com/send?phone=+$number"
15 | if (!messages.isNullOrEmpty()) {
16 | url += "&text=$messages"
17 | }
18 | val i = Intent(Intent.ACTION_VIEW)
19 | i.data = Uri.parse(url)
20 | this.startActivity(i)
21 | }
22 |
23 | fun Context.openSmsApp(number: String) {
24 | val i = Intent(Intent.ACTION_VIEW)
25 | i.data = Uri.parse("smsto:$number")
26 | this.startActivity(i)
27 | }
28 |
29 | fun Context.openCallApp(number: String) {
30 | val i = Intent(Intent.ACTION_DIAL)
31 | i.data = Uri.parse("tel:$number")
32 | this.startActivity(i)
33 | }
34 |
35 | fun Context.openBrowser(url: String?) {
36 | val i = Intent(Intent.ACTION_VIEW)
37 | i.data = Uri.parse(url)
38 | this.startActivity(i)
39 | }
40 |
41 | fun Context.openMailApp(email: String, hashMap: HashMap = hashMapOf()) {
42 | val i = Intent(Intent.ACTION_SENDTO)
43 | i.data = Uri.parse("mailto:$email")
44 | if (hashMap.isNotEmpty()) {
45 | hashMap.forEach {
46 | i.putExtra(it.key, it.value)
47 | }
48 | }
49 | this.startActivity(i)
50 | }
51 |
52 | fun Context.showDatePickerDialog(selectedDate: (Int, Int, Int) -> Unit) {
53 | val calendar = Calendar.getInstance(Locale.getDefault())
54 | DatePickerDialog(
55 | this,
56 | { _, p1, p2, p3 ->
57 | selectedDate.invoke(p1, p2, p3)
58 | },
59 | calendar.get(Calendar.YEAR),
60 | calendar.get(Calendar.MONTH),
61 | calendar.get(Calendar.DAY_OF_MONTH)
62 | ).show()
63 | }
64 |
65 | fun Context.showTimePickerDialog(selectedTime: (Int, Int) -> Unit) {
66 | val calendar = Calendar.getInstance(Locale.getDefault())
67 | TimePickerDialog(
68 | this,
69 | { _, p1, p2 ->
70 | selectedTime.invoke(p1, p2)
71 | },
72 | calendar.get(Calendar.HOUR_OF_DAY),
73 | calendar.get(Calendar.MINUTE),
74 | true
75 | ).show()
76 | }
77 |
78 | fun Context.getRealPath(file: Uri, result: (String) -> Unit) {
79 | var cursor: Cursor? = null
80 | try {
81 | val proj = arrayOf(MediaStore.Images.Media.DATA)
82 | cursor = this.contentResolver.query(file, proj, null, null, null)
83 | val columnIndex: Int? = cursor?.getColumnIndexOrThrow(MediaStore.Images.Media.DATA)
84 | cursor?.moveToFirst()
85 | columnIndex?.let { i ->
86 | cursor?.getString(i)?.let {
87 | result.invoke(it)
88 | }
89 | }
90 | } catch (e: Exception) {
91 | result.invoke("")
92 | } finally {
93 | cursor?.close()
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @rem
2 | @rem Copyright 2015 the original author or authors.
3 | @rem
4 | @rem Licensed under the Apache License, Version 2.0 (the "License");
5 | @rem you may not use this file except in compliance with the License.
6 | @rem You may obtain a copy of the License at
7 | @rem
8 | @rem https://www.apache.org/licenses/LICENSE-2.0
9 | @rem
10 | @rem Unless required by applicable law or agreed to in writing, software
11 | @rem distributed under the License is distributed on an "AS IS" BASIS,
12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | @rem See the License for the specific language governing permissions and
14 | @rem limitations under the License.
15 | @rem
16 |
17 | @if "%DEBUG%" == "" @echo off
18 | @rem ##########################################################################
19 | @rem
20 | @rem Gradle startup script for Windows
21 | @rem
22 | @rem ##########################################################################
23 |
24 | @rem Set local scope for the variables with windows NT shell
25 | if "%OS%"=="Windows_NT" setlocal
26 |
27 | set DIRNAME=%~dp0
28 | if "%DIRNAME%" == "" set DIRNAME=.
29 | set APP_BASE_NAME=%~n0
30 | set APP_HOME=%DIRNAME%
31 |
32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter.
33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
34 |
35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
37 |
38 | @rem Find java.exe
39 | if defined JAVA_HOME goto findJavaFromJavaHome
40 |
41 | set JAVA_EXE=java.exe
42 | %JAVA_EXE% -version >NUL 2>&1
43 | if "%ERRORLEVEL%" == "0" goto execute
44 |
45 | echo.
46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
47 | echo.
48 | echo Please set the JAVA_HOME variable in your environment to match the
49 | echo location of your Java installation.
50 |
51 | goto fail
52 |
53 | :findJavaFromJavaHome
54 | set JAVA_HOME=%JAVA_HOME:"=%
55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
56 |
57 | if exist "%JAVA_EXE%" goto execute
58 |
59 | echo.
60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
61 | echo.
62 | echo Please set the JAVA_HOME variable in your environment to match the
63 | echo location of your Java installation.
64 |
65 | goto fail
66 |
67 | :execute
68 | @rem Setup the command line
69 |
70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
71 |
72 |
73 | @rem Execute Gradle
74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
75 |
76 | :end
77 | @rem End local scope for the variables with windows NT shell
78 | if "%ERRORLEVEL%"=="0" goto mainEnd
79 |
80 | :fail
81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
82 | rem the _cmd.exe /c_ return code!
83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
84 | exit /b 1
85 |
86 | :mainEnd
87 | if "%OS%"=="Windows_NT" endlocal
88 |
89 | :omega
90 |
--------------------------------------------------------------------------------
/showcase/src/main/res/layout/tutorial_view.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
17 |
18 |
24 |
25 |
31 |
32 |
37 |
38 |
45 |
46 |
49 |
50 |
57 |
58 |
65 |
66 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
--------------------------------------------------------------------------------
/showcase/src/main/res/drawable/ic_button_skip.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
12 |
13 |
--------------------------------------------------------------------------------
/showcase/src/main/java/com/dizcoding/showcase/ViewHelper.java:
--------------------------------------------------------------------------------
1 | package com.dizcoding.showcase;
2 |
3 | import android.content.Context;
4 | import android.graphics.Bitmap;
5 | import android.graphics.Canvas;
6 | import android.graphics.Paint;
7 | import android.graphics.PorterDuff;
8 | import android.graphics.PorterDuffXfermode;
9 | import android.graphics.Rect;
10 | import android.graphics.drawable.Drawable;
11 | import android.graphics.drawable.GradientDrawable;
12 | import android.graphics.drawable.ShapeDrawable;
13 | import android.view.View;
14 | import android.view.ViewParent;
15 |
16 | public class ViewHelper {
17 | static public void getRelativePositionRec(View myView, ViewParent root, int[] location) {
18 | if (myView.getParent() == root) {
19 | location[0] += myView.getLeft();
20 | location[1] += myView.getTop();
21 | } else {
22 | location[0] += myView.getLeft();
23 | location[1] += myView.getTop();
24 | getRelativePositionRec((View) myView.getParent(), root, location);
25 | }
26 | }
27 |
28 | public static void setBackgroundColor(View v, int color){
29 | Drawable background = v.getBackground();
30 | if (background instanceof ShapeDrawable) {
31 | ShapeDrawable shapeDrawable = (ShapeDrawable)background;
32 | shapeDrawable.getPaint().setColor(color);
33 | } else if (background instanceof GradientDrawable) {
34 | GradientDrawable gradientDrawable = (GradientDrawable)background;
35 | gradientDrawable.setColor(color);
36 | } else {
37 | v.setBackgroundColor(color);
38 | }
39 | }
40 |
41 | public static Bitmap getCroppedBitmap(Bitmap bitmap, int centerLocation[], int radius) {
42 | Bitmap output = Bitmap.createBitmap(2*radius,
43 | 2*radius, Bitmap.Config.ARGB_8888);
44 | Canvas canvas = new Canvas(output);
45 |
46 | final int color = 0xff424242;
47 | final Paint paint = new Paint();
48 |
49 | Rect sourceRect = new Rect(centerLocation[0] - radius,
50 | centerLocation[1] - radius,
51 | centerLocation[0] + radius,
52 | centerLocation[1] + radius);
53 | Rect destRect = new Rect(0,0,2*radius, 2*radius);
54 |
55 | paint.setAntiAlias(true);
56 | canvas.drawARGB(0, 0, 0, 0);
57 | paint.setColor(color);
58 | canvas.drawCircle(radius, radius,
59 | radius, paint);
60 | paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
61 | canvas.drawBitmap(bitmap, sourceRect, destRect, paint);
62 | return output;
63 | }
64 |
65 | public static Bitmap getCroppedBitmap(Bitmap bitmap, int rectLocation[]) {
66 | int xStart = rectLocation[0];
67 | int yStart = rectLocation[1];
68 | int xEnd = rectLocation[2];
69 | int yEnd = rectLocation[3];
70 | int width = xEnd - xStart;
71 | int height = yEnd - yStart;
72 |
73 | Bitmap output = Bitmap.createBitmap(width,
74 | height, Bitmap.Config.ARGB_8888);
75 | Canvas canvas = new Canvas(output);
76 |
77 | final int color = 0xff424242;
78 | final Paint paint = new Paint();
79 |
80 | Rect sourceRect = new Rect(xStart,
81 | yStart,
82 | xEnd,
83 | yEnd);
84 | Rect destRect = new Rect(0,0,width, height);
85 |
86 | paint.setAntiAlias(true);
87 | canvas.drawARGB(0, 0, 0, 0);
88 | paint.setColor(color);
89 | canvas.drawRect(destRect, paint);
90 | // canvas.drawCircle(radius, radius,
91 | // radius, paint);
92 | paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
93 | canvas.drawBitmap(bitmap, sourceRect, destRect, paint);
94 | return output;
95 | }
96 |
97 | public static int getStatusBarHeight(Context context) {
98 | int height = 0;
99 | int resId = context.getResources().getIdentifier("status_bar_height", "dimen", "android");
100 | if (resId > 0) {
101 | height = context.getResources().getDimensionPixelSize(resId);
102 | }
103 | return height;
104 | }
105 | }
106 |
--------------------------------------------------------------------------------
/base/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | 2sp
6 | 11sp
7 | 12sp
8 | 13sp
9 | 14sp
10 | 15sp
11 | 16sp
12 | 18sp
13 | 21sp
14 | 24sp
15 | 30sp
16 | 36sp
17 |
18 | 4dp
19 | 5dp
20 | 10dp
21 | 12dp
22 | 15dp
23 | 20dp
24 | 26dp
25 |
26 | 0dp
27 | 0.3dp
28 | 1dp
29 | 2dp
30 | 3dp
31 | 4dp
32 | 5dp
33 | 6dp
34 | 7dp
35 | 8dp
36 | 10dp
37 | 11dp
38 | 12dp
39 | 14dp
40 | 15dp
41 | 16dp
42 | 18dp
43 | 20dp
44 | 23dp
45 | 24dp
46 | 25dp
47 | 27dp
48 | 30dp
49 | 32dp
50 | 33dp
51 | 36dp
52 | 38dp
53 | 40dp
54 | 48dp
55 | 50dp
56 | 52dp
57 | 55dp
58 | 56dp
59 | 60dp
60 | 66dp
61 | 70dp
62 | 78dp
63 | 84dp
64 | 100dp
65 | 115dp
66 | 120dp
67 | 160dp
68 | 200dp
69 | 300dp
70 | 500dp
71 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
10 |
15 |
20 |
25 |
30 |
35 |
40 |
45 |
50 |
55 |
60 |
65 |
70 |
75 |
80 |
85 |
90 |
95 |
100 |
105 |
110 |
115 |
120 |
125 |
130 |
135 |
140 |
145 |
150 |
155 |
160 |
165 |
170 |
171 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | #
4 | # Copyright 2015 the original author or authors.
5 | #
6 | # Licensed under the Apache License, Version 2.0 (the "License");
7 | # you may not use this file except in compliance with the License.
8 | # You may obtain a copy of the License at
9 | #
10 | # https://www.apache.org/licenses/LICENSE-2.0
11 | #
12 | # Unless required by applicable law or agreed to in writing, software
13 | # distributed under the License is distributed on an "AS IS" BASIS,
14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | # See the License for the specific language governing permissions and
16 | # limitations under the License.
17 | #
18 |
19 | ##############################################################################
20 | ##
21 | ## Gradle start up script for UN*X
22 | ##
23 | ##############################################################################
24 |
25 | # Attempt to set APP_HOME
26 | # Resolve links: $0 may be a link
27 | PRG="$0"
28 | # Need this for relative symlinks.
29 | while [ -h "$PRG" ] ; do
30 | ls=`ls -ld "$PRG"`
31 | link=`expr "$ls" : '.*-> \(.*\)$'`
32 | if expr "$link" : '/.*' > /dev/null; then
33 | PRG="$link"
34 | else
35 | PRG=`dirname "$PRG"`"/$link"
36 | fi
37 | done
38 | SAVED="`pwd`"
39 | cd "`dirname \"$PRG\"`/" >/dev/null
40 | APP_HOME="`pwd -P`"
41 | cd "$SAVED" >/dev/null
42 |
43 | APP_NAME="Gradle"
44 | APP_BASE_NAME=`basename "$0"`
45 |
46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
48 |
49 | # Use the maximum available, or set MAX_FD != -1 to use that value.
50 | MAX_FD="maximum"
51 |
52 | warn () {
53 | echo "$*"
54 | }
55 |
56 | die () {
57 | echo
58 | echo "$*"
59 | echo
60 | exit 1
61 | }
62 |
63 | # OS specific support (must be 'true' or 'false').
64 | cygwin=false
65 | msys=false
66 | darwin=false
67 | nonstop=false
68 | case "`uname`" in
69 | CYGWIN* )
70 | cygwin=true
71 | ;;
72 | Darwin* )
73 | darwin=true
74 | ;;
75 | MINGW* )
76 | msys=true
77 | ;;
78 | NONSTOP* )
79 | nonstop=true
80 | ;;
81 | esac
82 |
83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
84 |
85 |
86 | # Determine the Java command to use to start the JVM.
87 | if [ -n "$JAVA_HOME" ] ; then
88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
89 | # IBM's JDK on AIX uses strange locations for the executables
90 | JAVACMD="$JAVA_HOME/jre/sh/java"
91 | else
92 | JAVACMD="$JAVA_HOME/bin/java"
93 | fi
94 | if [ ! -x "$JAVACMD" ] ; then
95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
96 |
97 | Please set the JAVA_HOME variable in your environment to match the
98 | location of your Java installation."
99 | fi
100 | else
101 | JAVACMD="java"
102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
103 |
104 | Please set the JAVA_HOME variable in your environment to match the
105 | location of your Java installation."
106 | fi
107 |
108 | # Increase the maximum file descriptors if we can.
109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
110 | MAX_FD_LIMIT=`ulimit -H -n`
111 | if [ $? -eq 0 ] ; then
112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
113 | MAX_FD="$MAX_FD_LIMIT"
114 | fi
115 | ulimit -n $MAX_FD
116 | if [ $? -ne 0 ] ; then
117 | warn "Could not set maximum file descriptor limit: $MAX_FD"
118 | fi
119 | else
120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
121 | fi
122 | fi
123 |
124 | # For Darwin, add options to specify how the application appears in the dock
125 | if $darwin; then
126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
127 | fi
128 |
129 | # For Cygwin or MSYS, switch paths to Windows format before running java
130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
133 |
134 | JAVACMD=`cygpath --unix "$JAVACMD"`
135 |
136 | # We build the pattern for arguments to be converted via cygpath
137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
138 | SEP=""
139 | for dir in $ROOTDIRSRAW ; do
140 | ROOTDIRS="$ROOTDIRS$SEP$dir"
141 | SEP="|"
142 | done
143 | OURCYGPATTERN="(^($ROOTDIRS))"
144 | # Add a user-defined pattern to the cygpath arguments
145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
147 | fi
148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
149 | i=0
150 | for arg in "$@" ; do
151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
153 |
154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
156 | else
157 | eval `echo args$i`="\"$arg\""
158 | fi
159 | i=`expr $i + 1`
160 | done
161 | case $i in
162 | 0) set -- ;;
163 | 1) set -- "$args0" ;;
164 | 2) set -- "$args0" "$args1" ;;
165 | 3) set -- "$args0" "$args1" "$args2" ;;
166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;;
167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
172 | esac
173 | fi
174 |
175 | # Escape application args
176 | save () {
177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
178 | echo " "
179 | }
180 | APP_ARGS=`save "$@"`
181 |
182 | # Collect all arguments for the java command, following the shell quoting and substitution rules
183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
184 |
185 | exec "$JAVACMD" "$@"
186 |
--------------------------------------------------------------------------------
/showcase/src/main/java/com/dizcoding/showcase/ShowCaseBuilder.java:
--------------------------------------------------------------------------------
1 | package com.dizcoding.showcase;
2 |
3 |
4 | import android.os.Parcel;
5 | import android.os.Parcelable;
6 |
7 | public class ShowCaseBuilder implements Parcelable {
8 |
9 | private int layoutRes;
10 | private int titleTextColorRes;
11 | private int textColorRes;
12 | private int shadowColorRes;
13 | private int titleTextSizeRes;
14 | private int textSizeRes;
15 | private int spacingRes;
16 | private int backgroundContentColorRes;
17 | private int circleIndicatorBackgroundDrawableRes;
18 | private int prevDrawableRes;
19 | private int nextDrawableRes;
20 | private int finishDrawableRes;
21 | private boolean useCircleIndicator = true;
22 | private boolean clickable = false;
23 | private boolean useArrow = true;
24 | private int arrowWidth;
25 | private String packageName;
26 |
27 | private int skipDrawableRes;
28 | private int lineColorRes;
29 | private boolean useSkipWord;
30 |
31 | public ShowCaseBuilder setPackageName(String packageName) {
32 | this.packageName = packageName;
33 | return this;
34 | }
35 |
36 | public ShowCaseBuilder customView(int customViewRes) {
37 | this.layoutRes = customViewRes;
38 | return this;
39 | }
40 |
41 | public ShowCaseBuilder textColorRes(int textColorRes) {
42 | this.textColorRes = textColorRes;
43 | return this;
44 | }
45 |
46 | public ShowCaseBuilder titleTextColorRes(int titleTextColorRes) {
47 | this.titleTextColorRes = titleTextColorRes;
48 | return this;
49 | }
50 |
51 | public ShowCaseBuilder shadowColorRes(int shadowColorRes) {
52 | this.shadowColorRes = shadowColorRes;
53 | return this;
54 | }
55 |
56 | public ShowCaseBuilder useArrow(boolean useArrow) {
57 | this.useArrow = useArrow;
58 | return this;
59 | }
60 |
61 | public ShowCaseBuilder useSkipWord(boolean useSkipWord) {
62 | this.useSkipWord = useSkipWord;
63 | return this;
64 | }
65 |
66 | public ShowCaseBuilder textSizeRes(int textSizeRes) {
67 | this.textSizeRes = textSizeRes;
68 | return this;
69 | }
70 |
71 | public ShowCaseBuilder titleTextSizeRes(int titleTextSizeRes) {
72 | this.titleTextSizeRes = titleTextSizeRes;
73 | return this;
74 | }
75 |
76 | public ShowCaseBuilder spacingRes(int spacingRes) {
77 | this.spacingRes = spacingRes;
78 | return this;
79 | }
80 |
81 | public ShowCaseBuilder arrowWidth(int arrowWidth) {
82 | this.arrowWidth = arrowWidth;
83 | return this;
84 | }
85 |
86 | public ShowCaseBuilder backgroundContentColorRes(int backgroundContentColorRes) {
87 | this.backgroundContentColorRes = backgroundContentColorRes;
88 | return this;
89 | }
90 |
91 | public ShowCaseBuilder circleIndicatorBackgroundDrawableRes(int circleIndicatorBackgroundDrawableRes) {
92 | this.circleIndicatorBackgroundDrawableRes = circleIndicatorBackgroundDrawableRes;
93 | return this;
94 | }
95 |
96 | public ShowCaseBuilder setLineColorRes(int lineColorRes) {
97 | this.lineColorRes = lineColorRes;
98 | return this;
99 | }
100 |
101 | public ShowCaseBuilder finishDrawableRes(int finishDrawableRes) {
102 | this.finishDrawableRes = finishDrawableRes;
103 | return this;
104 | }
105 |
106 | public ShowCaseBuilder prevDrawableRes(int prevDrawableRes) {
107 | this.prevDrawableRes = prevDrawableRes;
108 | return this;
109 | }
110 |
111 | public ShowCaseBuilder nextDrawableRes(int nextDrawableRes) {
112 | this.nextDrawableRes = nextDrawableRes;
113 | return this;
114 | }
115 |
116 | public ShowCaseBuilder skipDrawableRes(int skipDrawableRes) {
117 | this.skipDrawableRes = skipDrawableRes;
118 | return this;
119 | }
120 |
121 |
122 | public ShowCaseBuilder clickable(boolean clickable) {
123 | this.clickable = clickable;
124 | return this;
125 | }
126 |
127 | public ShowCaseBuilder useCircleIndicator(boolean useCircleIndicator) {
128 | this.useCircleIndicator = useCircleIndicator;
129 | return this;
130 | }
131 |
132 | public String getPackageName() {
133 | return packageName;
134 | }
135 | public int getLineColorRes() {
136 | return lineColorRes;
137 | }
138 |
139 | public int getTextColorRes() {
140 | return textColorRes;
141 | }
142 |
143 | public int getTitleTextColorRes() {
144 | return titleTextColorRes;
145 | }
146 |
147 | public int getTitleTextSizeRes() {
148 | return titleTextSizeRes;
149 | }
150 |
151 | public int getFinishDrawableRes() {
152 | return finishDrawableRes;
153 | }
154 |
155 | public int getNextDrawableRes() {
156 | return nextDrawableRes;
157 | }
158 |
159 | public int getSkipDrawableRes() {
160 | return skipDrawableRes;
161 | }
162 |
163 | public int getPrevDrawableRes() {
164 | return prevDrawableRes;
165 | }
166 |
167 | public boolean useCircleIndicator() {
168 | return useCircleIndicator;
169 | }
170 |
171 | public int getShadowColorRes() {
172 | return shadowColorRes;
173 | }
174 |
175 | public int getTextSizeRes() {
176 | return textSizeRes;
177 | }
178 |
179 | public int getBackgroundContentColorRes() {
180 | return backgroundContentColorRes;
181 | }
182 |
183 | public int getCircleIndicatorBackgroundDrawableRes() {
184 | return circleIndicatorBackgroundDrawableRes;
185 | }
186 |
187 | public boolean isUseArrow() {
188 | return useArrow;
189 | }
190 |
191 | public boolean isUseSkipWord(){
192 | return useSkipWord;
193 | }
194 |
195 | public int getLayoutRes() {
196 | return layoutRes;
197 | }
198 |
199 | public int getArrowWidth() {
200 | return arrowWidth;
201 | }
202 |
203 | public int getSpacingRes() {
204 | return spacingRes;
205 | }
206 |
207 | public boolean isClickable() {
208 | return clickable;
209 | }
210 |
211 | public ShowCaseDialog build() {
212 | return ShowCaseDialog.newInstance(this);
213 | }
214 |
215 | @Override
216 | public int describeContents() {
217 | return 0;
218 | }
219 |
220 | @Override
221 | public void writeToParcel(Parcel dest, int flags) {
222 | dest.writeInt(this.layoutRes);
223 | dest.writeInt(this.titleTextColorRes);
224 | dest.writeInt(this.textColorRes);
225 | dest.writeInt(this.shadowColorRes);
226 | dest.writeInt(this.titleTextSizeRes);
227 | dest.writeInt(this.textSizeRes);
228 | dest.writeInt(this.spacingRes);
229 | dest.writeInt(this.backgroundContentColorRes);
230 | dest.writeInt(this.circleIndicatorBackgroundDrawableRes);
231 | dest.writeInt(this.prevDrawableRes);
232 | dest.writeInt(this.nextDrawableRes);
233 | dest.writeInt(this.finishDrawableRes);
234 | dest.writeByte(this.useCircleIndicator ? (byte) 1 : (byte) 0);
235 | dest.writeByte(this.clickable ? (byte) 1 : (byte) 0);
236 | dest.writeByte(this.useArrow ? (byte) 1 : (byte) 0);
237 | dest.writeInt(this.arrowWidth);
238 | dest.writeInt(this.skipDrawableRes);
239 | dest.writeInt(this.lineColorRes);
240 | dest.writeByte(this.useSkipWord ? (byte) 1 : (byte) 0);
241 | }
242 |
243 | public ShowCaseBuilder() {
244 | }
245 |
246 | protected ShowCaseBuilder(Parcel in) {
247 | this.layoutRes = in.readInt();
248 | this.titleTextColorRes = in.readInt();
249 | this.textColorRes = in.readInt();
250 | this.shadowColorRes = in.readInt();
251 | this.titleTextSizeRes = in.readInt();
252 | this.textSizeRes = in.readInt();
253 | this.spacingRes = in.readInt();
254 | this.backgroundContentColorRes = in.readInt();
255 | this.circleIndicatorBackgroundDrawableRes = in.readInt();
256 | this.prevDrawableRes = in.readInt();
257 | this.nextDrawableRes = in.readInt();
258 | this.finishDrawableRes = in.readInt();
259 | this.useCircleIndicator = in.readByte() != 0;
260 | this.clickable = in.readByte() != 0;
261 | this.useArrow = in.readByte() != 0;
262 | this.arrowWidth = in.readInt();
263 | this.skipDrawableRes = in.readInt();
264 | this.lineColorRes = in.readInt();
265 | this.useSkipWord = in.readByte() != 0;
266 | }
267 |
268 | public static final Creator CREATOR = new Creator() {
269 | @Override
270 | public ShowCaseBuilder createFromParcel(Parcel source) {
271 | return new ShowCaseBuilder(source);
272 | }
273 |
274 | @Override
275 | public ShowCaseBuilder[] newArray(int size) {
276 | return new ShowCaseBuilder[size];
277 | }
278 | };
279 | }
280 |
281 |
--------------------------------------------------------------------------------
/showcase/src/main/java/com/dizcoding/showcase/ShowCaseDialog.java:
--------------------------------------------------------------------------------
1 | package com.dizcoding.showcase;
2 |
3 |
4 | import android.app.Dialog;
5 | import android.os.Bundle;
6 | import android.os.Handler;
7 | import android.text.TextUtils;
8 | import android.view.LayoutInflater;
9 | import android.view.View;
10 | import android.view.ViewGroup;
11 | import android.view.Window;
12 | import android.widget.ScrollView;
13 |
14 | import androidx.annotation.NonNull;
15 | import androidx.annotation.Nullable;
16 | import androidx.appcompat.app.AppCompatActivity;
17 | import androidx.core.widget.NestedScrollView;
18 | import androidx.fragment.app.DialogFragment;
19 | import androidx.fragment.app.FragmentActivity;
20 | import androidx.fragment.app.FragmentManager;
21 | import androidx.fragment.app.FragmentTransaction;
22 |
23 | import java.util.ArrayList;
24 |
25 | public class ShowCaseDialog extends DialogFragment {
26 | private static final String ARG_BUILDER = "BUILDER";
27 | public static final int DELAY_SCROLLING = 350;
28 | public static final String TAG = ShowCaseDialog.class.getSimpleName();
29 | public static final int MAX_RETRY_LAYOUT = 3;
30 |
31 | private ArrayList tutorsList;
32 | private int currentTutorIndex = -1;
33 | private ShowCaseBuilder builder;
34 | private String tag;
35 |
36 | boolean hasViewGroupHandled = false;
37 |
38 | private OnShowCaseStepListener listener;
39 |
40 | private int retryCounter = 0;
41 |
42 | public interface OnShowCaseStepListener {
43 | /**
44 | * @param previousStep
45 | * @param nextStep
46 | * @param showCaseObject
47 | * @return true if already fully handled show case step inthis function
48 | */
49 | boolean onShowCaseGoTo(int previousStep, int nextStep, ShowCaseObject showCaseObject);
50 | }
51 |
52 | public void setShowCaseStepListener(OnShowCaseStepListener listener) {
53 | this.listener = listener;
54 | }
55 |
56 | static ShowCaseDialog newInstance(ShowCaseBuilder builder) {
57 | final Bundle args = new Bundle();
58 | final ShowCaseDialog fragment = new ShowCaseDialog();
59 | args.putParcelable(ARG_BUILDER, builder);
60 | fragment.setArguments(args);
61 | return fragment;
62 | }
63 |
64 | @Override
65 | public void onCreate(@Nullable Bundle savedInstanceState) {
66 | super.onCreate(savedInstanceState);
67 | getArgs(getArguments());
68 | setRetainInstance(true);
69 | }
70 |
71 | private void getArgs(Bundle args) {
72 | builder = (ShowCaseBuilder) args.get(ARG_BUILDER);
73 | }
74 |
75 | @Override
76 | @NonNull
77 | public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
78 | final Dialog dialog = new Dialog(getActivity(), R.style.ShowCase) {
79 | @Override
80 | public void onBackPressed() {
81 | if (builder.isClickable()) {
82 | previous();
83 | }
84 | }
85 | };
86 | dialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
87 | return dialog;
88 | }
89 |
90 | @Override
91 | public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
92 | @Nullable Bundle savedInstanceState) {
93 | final View view = new ShowCaseLayout(getActivity(), builder);
94 | initViews(((ShowCaseLayout) view));
95 | return view;
96 | }
97 |
98 | private void initViews(ShowCaseLayout view) {
99 | view.setShowCaseListener(new ShowCaseListener() {
100 | @Override
101 | public void onPrevious() {
102 | previous();
103 | }
104 |
105 | @Override
106 | public void onNext() {
107 | next();
108 | }
109 |
110 | @Override
111 | public void onComplete() {
112 | if (!TextUtils.isEmpty(tag)) {
113 | ShowCasePreference.setShown(getActivity(), tag, true);
114 | }
115 | ShowCaseDialog.this.close();
116 | }
117 | });
118 |
119 | setCancelable(builder.isClickable());
120 | }
121 |
122 | public void next() {
123 | if ((currentTutorIndex + 1) >= tutorsList.size()) {
124 | this.close();
125 | } else {
126 | ShowCaseDialog.this.show(this.getActivity(), tag, tutorsList, currentTutorIndex + 1);
127 | }
128 | }
129 |
130 | public void previous() {
131 | if ((currentTutorIndex - 1) < 0) {
132 | currentTutorIndex = 0;
133 | } else {
134 | ShowCaseDialog.this.show(getActivity(), tag, tutorsList, currentTutorIndex - 1);
135 | }
136 | }
137 |
138 | @Override
139 | public void onStart() {
140 | super.onStart();
141 | final Window window = getDialog().getWindow();
142 | if (window != null) {
143 | window.setBackgroundDrawableResource(android.R.color.transparent);
144 | window.setDimAmount(0f);
145 | window.setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
146 | }
147 | }
148 |
149 | public boolean hasShown(AppCompatActivity activity, String tag) {
150 | return ShowCasePreference.hasShown(activity, tag);
151 | }
152 |
153 | public void show(AppCompatActivity activity, @Nullable String tag, final ArrayList tutorList) {
154 | show(activity, tag, tutorList, 0);
155 | }
156 |
157 | public void show(final FragmentActivity activity, @Nullable String tag, final ArrayList tutorList, int indexToShow) {
158 | if (activity == null || activity.isFinishing()) {
159 | return;
160 | }
161 | try {
162 | this.tutorsList = tutorList;
163 | this.tag = tag;
164 | if (indexToShow < 0 || indexToShow >= tutorList.size()) {
165 | indexToShow = 0;
166 | }
167 | int previousIndex = currentTutorIndex;
168 | currentTutorIndex = indexToShow;
169 |
170 | hasViewGroupHandled = false;
171 | if (listener != null) {
172 | hasViewGroupHandled = listener.onShowCaseGoTo(previousIndex, currentTutorIndex, tutorList.get(currentTutorIndex));
173 | }
174 |
175 | // has been handled by listener
176 | if (hasViewGroupHandled) return;
177 |
178 | final ShowCaseObject showCaseObject = tutorList.get(currentTutorIndex);
179 | final ViewGroup viewGroup = showCaseObject.getScrollView();
180 | if (viewGroup != null) {
181 | final View viewToFocus = showCaseObject.getView();
182 | if (viewToFocus != null) {
183 | hideLayout();
184 | viewGroup.post(new Runnable() {
185 | @Override
186 | public void run() {
187 | if (viewGroup instanceof ScrollView) {
188 | ScrollView scrollView = (ScrollView) viewGroup;
189 | int relativeLocation[] = new int[2];
190 | ViewHelper.getRelativePositionRec(viewToFocus, viewGroup, relativeLocation);
191 | scrollView.smoothScrollTo(0, relativeLocation[1]);
192 | scrollView.postDelayed(new Runnable() {
193 | @Override
194 | public void run() {
195 | showLayout(activity, showCaseObject);
196 | }
197 | }, DELAY_SCROLLING);
198 | } else if (viewGroup instanceof NestedScrollView) {
199 | NestedScrollView scrollView = (NestedScrollView) viewGroup;
200 | int relativeLocation[] = new int[2];
201 | ViewHelper.getRelativePositionRec(viewToFocus, viewGroup, relativeLocation);
202 | scrollView.smoothScrollTo(0, relativeLocation[1]);
203 | scrollView.postDelayed(new Runnable() {
204 | @Override
205 | public void run() {
206 | showLayout(activity, showCaseObject);
207 | }
208 | }, DELAY_SCROLLING);
209 | }
210 | }
211 | });
212 | hasViewGroupHandled = true;
213 | } else {
214 | hasViewGroupHandled = false;
215 | }
216 | }
217 |
218 | if (!hasViewGroupHandled) {
219 | showLayout(activity, tutorsList.get(currentTutorIndex));
220 | }
221 | } catch (Exception e) {
222 | // to Handle the unknown exception.
223 | // Since this only for first guide, if any error appears, just don't show the guide
224 | try {
225 | ShowCaseDialog.this.dismiss();
226 | } catch (Exception e2) {
227 | // no op
228 | }
229 | }
230 | }
231 |
232 | public void showLayout(FragmentActivity activity, ShowCaseObject showCaseObject) {
233 | if (activity == null || activity.isFinishing()) {
234 | return;
235 | }
236 |
237 |
238 | FragmentManager fm = activity.getSupportFragmentManager();
239 | if (!isVisible()) {
240 | try {
241 | if (!isAdded()) {
242 | show(fm, TAG);
243 | } else if (isHidden()) {
244 | FragmentTransaction ft = fm.beginTransaction();
245 | ft.show(ShowCaseDialog.this);
246 | ft.commit();
247 | }
248 | } catch (IllegalStateException e) {
249 | // called in illegal state. just return.
250 | return;
251 | }
252 | }
253 |
254 | final View view = showCaseObject.getView();
255 | final String title = showCaseObject.getTitle();
256 | final String text = showCaseObject.getText();
257 | final ShowCaseContentPosition showCaseContentPosition = showCaseObject.getShowCaseContentPosition();
258 | final int tintBackgroundColor = showCaseObject.getTintBackgroundColor();
259 | final int[] location = showCaseObject.getLocation();
260 | final int radius = showCaseObject.getRadius();
261 |
262 | if (view == null) {
263 | layoutShowTutorial(null, title, text, showCaseContentPosition,
264 | tintBackgroundColor, location, radius);
265 | } else {
266 | view.post(() -> layoutShowTutorial(view, title, text, showCaseContentPosition,
267 | tintBackgroundColor, location, radius));
268 | }
269 | }
270 |
271 | public void hideLayout() {
272 | final ShowCaseLayout layout = (ShowCaseLayout) ShowCaseDialog.this.getView();
273 | if (layout == null) {
274 | return;
275 | }
276 | layout.hideTutorial();
277 | }
278 |
279 | private void layoutShowTutorial(final View view, final String title, final String text,
280 | final ShowCaseContentPosition showCaseContentPosition,
281 | final int tintBackgroundColor, final int[] customTarget, final int radius) {
282 |
283 | try {
284 | final ShowCaseLayout layout = (ShowCaseLayout) ShowCaseDialog.this.getView();
285 | if (layout == null) {
286 | if (retryCounter >= MAX_RETRY_LAYOUT) {
287 | retryCounter = 0;
288 | return;
289 | }
290 | // wait until the layout is ready, and call itself
291 | new Handler().postDelayed(new Runnable() {
292 | @Override
293 | public void run() {
294 | retryCounter++;
295 | layoutShowTutorial(view, title, text,
296 | showCaseContentPosition, tintBackgroundColor, customTarget, radius);
297 | }
298 | }, 1000);
299 | return;
300 | }
301 | retryCounter = 0;
302 | layout.showTutorial(view, title, text, currentTutorIndex, tutorsList.size(),
303 | showCaseContentPosition, tintBackgroundColor, customTarget, radius);
304 | } catch (Throwable t) {
305 | // do nothing
306 | }
307 |
308 | }
309 |
310 | public void close() {
311 | try {
312 | dismiss();
313 | final ShowCaseLayout layout = (ShowCaseLayout) ShowCaseDialog.this.getView();
314 | if (layout == null) {
315 | return;
316 | }
317 | layout.closeTutorial();
318 | } catch (Exception e) {
319 | // no op
320 | }
321 | }
322 |
323 | }
324 |
325 |
--------------------------------------------------------------------------------
/showcase/src/main/java/com/dizcoding/showcase/ShowCaseLayout.java:
--------------------------------------------------------------------------------
1 | package com.dizcoding.showcase;
2 |
3 | import android.content.Context;
4 | import android.graphics.Bitmap;
5 | import android.graphics.Canvas;
6 | import android.graphics.Color;
7 | import android.graphics.Paint;
8 | import android.graphics.Path;
9 | import android.graphics.PorterDuff;
10 | import android.graphics.PorterDuffXfermode;
11 | import android.graphics.drawable.Drawable;
12 | import android.os.Build;
13 | import android.text.Html;
14 | import android.text.Spanned;
15 | import android.text.TextUtils;
16 | import android.util.AttributeSet;
17 | import android.util.TypedValue;
18 | import android.view.Gravity;
19 | import android.view.LayoutInflater;
20 | import android.view.View;
21 | import android.view.ViewGroup;
22 | import android.view.ViewTreeObserver;
23 | import android.widget.FrameLayout;
24 | import android.widget.ImageView;
25 | import android.widget.TextView;
26 |
27 | import androidx.annotation.Nullable;
28 | import androidx.annotation.RequiresApi;
29 | import androidx.core.content.ContextCompat;
30 |
31 | public class ShowCaseLayout extends FrameLayout {
32 |
33 | // customized attribute
34 | private int layoutRes;
35 | private int textColor;
36 | private int titleTextColor;
37 | private int shadowColor;
38 | private float textSize;
39 | private float textTitleSize;
40 | private int spacing;
41 | private int arrowMargin;
42 | private int arrowWidth;
43 | private int lineColorRes;
44 | private boolean useCircleIndicator;
45 |
46 | private boolean isCancelable;
47 | private boolean hasSkipWord;
48 |
49 | private Drawable prevDrawable;
50 | private Drawable nextDrawable;
51 | private Drawable finishDrawable;
52 | private Drawable skipDrawable;
53 |
54 | private int backgroundContentColor;
55 | private int circleBackgroundDrawableRes;
56 |
57 | // View
58 | private ViewGroup viewGroup;
59 | private Bitmap bitmap;
60 | private View lastTutorialView;
61 | private Paint viewPaint;
62 |
63 | // listener
64 | private ShowCaseListener showCaseListener;
65 |
66 | ShowCaseContentPosition showCaseContentPosition;
67 |
68 | private int highlightLocX;
69 | private int highlightLocY;
70 |
71 | // determined if this is last chain
72 | private boolean isStart;
73 | private boolean isLast;
74 |
75 | // path for arrow
76 | private Path path;
77 | private Paint arrowPaint;
78 | private TextView textViewTitle;
79 | private TextView textViewDesc;
80 | private ImageView prevButton;
81 | private ImageView nextButton;
82 | private ImageView skipButton;
83 | private ViewGroup viewGroupIndicator;
84 |
85 | public ShowCaseLayout(Context context, @Nullable ShowCaseBuilder builder) {
86 | super(context);
87 | init(context, builder);
88 | }
89 |
90 | public ShowCaseLayout(Context context) {
91 | super(context);
92 | init(context, null);
93 | }
94 |
95 | public ShowCaseLayout(Context context, AttributeSet attrs) {
96 | super(context, attrs);
97 | init(context, null);
98 | }
99 |
100 | public ShowCaseLayout(Context context, AttributeSet attrs, int defStyleAttr) {
101 | super(context, attrs, defStyleAttr);
102 | init(context, null);
103 | }
104 |
105 | @RequiresApi(Build.VERSION_CODES.LOLLIPOP)
106 | public ShowCaseLayout(Context context,
107 | AttributeSet attrs,
108 | int defStyleAttr,
109 | int defStyleRes) {
110 | super(context, attrs, defStyleAttr, defStyleRes);
111 | init(context, null);
112 | }
113 |
114 | private void init(Context context, @Nullable ShowCaseBuilder builder) {
115 | setVisibility(View.GONE);
116 |
117 | if (isInEditMode()) {
118 | return;
119 | }
120 |
121 | applyAttrs(context, builder);
122 |
123 | //setBackground, color
124 | initFrame();
125 |
126 | // setContentView
127 | initContent(context, builder);
128 |
129 | setClickable(this.isCancelable);
130 | setFocusable(this.isCancelable);
131 |
132 | if (this.isCancelable) {
133 | this.setOnClickListener(new OnClickListener() {
134 | @Override
135 | public void onClick(View v) {
136 | onNextClicked();
137 | }
138 | });
139 | }
140 | }
141 |
142 | private void onNextClicked() {
143 | if (showCaseListener != null) {
144 | if (this.isLast) {
145 | ShowCaseLayout.this.showCaseListener.onComplete();
146 | } else {
147 | ShowCaseLayout.this.showCaseListener.onNext();
148 | }
149 | }
150 | }
151 |
152 | private void initFrame() {
153 | setWillNotDraw(false);
154 |
155 | viewPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
156 | viewPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
157 |
158 | arrowPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
159 | arrowPaint.setColor(backgroundContentColor);
160 | arrowPaint.setStyle(Paint.Style.FILL);
161 |
162 | setBackgroundColor(shadowColor);
163 | }
164 |
165 | public void setShowCaseListener(ShowCaseListener showCaseListener) {
166 | this.showCaseListener = showCaseListener;
167 | }
168 |
169 | public void showTutorial(View view,
170 | String title,
171 | String text,
172 | int currentTutorIndex,
173 | int tutorsListSize,
174 | ShowCaseContentPosition showCaseContentPosition,
175 | int tintBackgroundColor,
176 | final int[] customTarget, final int radius) throws Throwable {
177 |
178 | this.isStart = currentTutorIndex == 0;
179 |
180 | this.isLast = currentTutorIndex == tutorsListSize - 1;
181 | this.showCaseContentPosition = showCaseContentPosition;
182 |
183 | if (this.bitmap != null) {
184 | this.bitmap.recycle();
185 | }
186 | if (this.lastTutorialView != null) {
187 | this.lastTutorialView.setDrawingCacheEnabled(false);
188 | }
189 |
190 | if (TextUtils.isEmpty(title)) {
191 | textViewTitle.setVisibility(View.GONE);
192 | } else {
193 | textViewTitle.setText(fromHtml(title));
194 | textViewTitle.setVisibility(View.VISIBLE);
195 | }
196 |
197 | textViewDesc.setText(fromHtml(text));
198 |
199 | if (skipButton != null){
200 | if (hasSkipWord){
201 | skipButton.setImageDrawable(skipDrawable);
202 | skipButton.setVisibility(VISIBLE);
203 | }else {
204 | skipButton.setImageDrawable(skipDrawable);
205 | skipButton.setVisibility(GONE);
206 | }
207 | }
208 |
209 | if (prevButton != null) {
210 | if (isStart) {
211 | prevButton.setVisibility(View.GONE);
212 | } else {
213 | prevButton.setImageDrawable(prevDrawable);
214 | prevButton.setVisibility(View.VISIBLE);
215 | }
216 | }
217 |
218 | if (nextButton != null) {
219 | if (isLast) {
220 | nextButton.setImageDrawable(finishDrawable);
221 | } else if (currentTutorIndex < tutorsListSize - 1) { // has next
222 | nextButton.setImageDrawable(nextDrawable);
223 | }
224 | }
225 |
226 | makeCircleIndicator(!isStart || !isLast, currentTutorIndex, tutorsListSize);
227 |
228 | if (view == null) {
229 | this.lastTutorialView = null;
230 | this.bitmap = null;
231 | this.highlightLocX = 0;
232 | this.highlightLocY = 0;
233 | moveViewToCenter();
234 | } else {
235 | this.lastTutorialView = view;
236 | if (view.willNotCacheDrawing()) {
237 | view.setWillNotCacheDrawing(false);
238 | }
239 | view.setDrawingCacheEnabled(true);
240 | view.setDrawingCacheQuality(View.DRAWING_CACHE_QUALITY_LOW);
241 | if (tintBackgroundColor == 0) {
242 | this.bitmap = view.getDrawingCache();
243 | } else {
244 | Bitmap bitmapTemp = view.getDrawingCache();
245 |
246 | Bitmap bigBitmap = Bitmap.createBitmap(view.getMeasuredWidth(),
247 | view.getMeasuredHeight(), Bitmap.Config.ARGB_8888);
248 | Canvas bigCanvas = new Canvas(bigBitmap);
249 | bigCanvas.drawColor(tintBackgroundColor);
250 | Paint paint = new Paint();
251 | bigCanvas.drawBitmap(bitmapTemp, 0f, 0f, paint);
252 |
253 | this.bitmap = bigBitmap;
254 | }
255 |
256 | //set custom target to view
257 | if (customTarget != null) {
258 | if (customTarget.length == 2) {
259 | this.bitmap = ViewHelper.getCroppedBitmap(bitmap, customTarget, radius);
260 | } else if (customTarget.length == 4) {
261 | this.bitmap = ViewHelper.getCroppedBitmap(bitmap, customTarget);
262 | }
263 |
264 | this.highlightLocX = customTarget[0] - radius;
265 | this.highlightLocY = customTarget[1] - radius;
266 | } else { // use view location as target
267 | final int[] location = new int[2];
268 | view.getLocationInWindow(location);
269 |
270 | this.highlightLocX = location[0];
271 | this.highlightLocY = location[1] - ViewHelper.getStatusBarHeight(getContext());
272 | }
273 |
274 | this.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
275 | @Override
276 | public void onGlobalLayout() {
277 | if (ShowCaseLayout.this.bitmap != null) {
278 | moveViewBasedHighlight(ShowCaseLayout.this.highlightLocX,
279 | ShowCaseLayout.this.highlightLocY,
280 | ShowCaseLayout.this.highlightLocX + ShowCaseLayout.this.bitmap.getWidth(),
281 | ShowCaseLayout.this.highlightLocY + ShowCaseLayout.this.bitmap.getHeight());
282 |
283 | if (Build.VERSION.SDK_INT < 16) {
284 | ShowCaseLayout.this.getViewTreeObserver().removeGlobalOnLayoutListener(this);
285 | } else {
286 | ShowCaseLayout.this.getViewTreeObserver().removeOnGlobalLayoutListener(this);
287 | }
288 | invalidate();
289 | }
290 | }
291 | });
292 | }
293 |
294 | this.setVisibility(View.VISIBLE);
295 | }
296 |
297 | public void hideTutorial() {
298 | this.setVisibility(View.INVISIBLE);
299 | }
300 |
301 | private void makeCircleIndicator(boolean hasMoreOneCircle,
302 | int currentTutorIndex,
303 | int tutorsListSize) {
304 | if (useCircleIndicator && this.viewGroupIndicator != null) {
305 | if (hasMoreOneCircle) { // has more than 1 circle
306 | // already has circle indicator
307 | if (this.viewGroupIndicator.getChildCount() == tutorsListSize) {
308 | for (int i = 0; i < tutorsListSize; i++) {
309 | View viewCircle = this.viewGroupIndicator.getChildAt(i);
310 | if (i == currentTutorIndex) {
311 | viewCircle.setSelected(true);
312 | } else {
313 | viewCircle.setSelected(false);
314 | }
315 | }
316 | } else { //reinitialize, the size is different
317 | this.viewGroupIndicator.removeAllViews();
318 | LayoutInflater inflater = LayoutInflater.from(getContext());
319 | for (int i = 0; i < tutorsListSize; i++) {
320 | View viewCircle = inflater.inflate(R.layout.circle_green_view,
321 | viewGroupIndicator,
322 | false);
323 | viewCircle.setBackgroundResource(this.circleBackgroundDrawableRes);
324 | if (i == currentTutorIndex) {
325 | viewCircle.setSelected(true);
326 | }
327 | this.viewGroupIndicator.addView(viewCircle);
328 | }
329 | }
330 | } else {
331 | this.viewGroupIndicator.removeAllViews();
332 | }
333 | }
334 | }
335 |
336 | public void closeTutorial() {
337 | setVisibility(View.GONE);
338 | if (this.bitmap != null) {
339 | this.bitmap.recycle();
340 | this.bitmap = null;
341 | }
342 | if (lastTutorialView != null) {
343 | this.lastTutorialView.setDrawingCacheEnabled(false);
344 | this.lastTutorialView = null;
345 | }
346 | }
347 |
348 | @Override
349 | public void onDetachedFromWindow() {
350 | super.onDetachedFromWindow();
351 | recycleResources();
352 | }
353 |
354 | @Override
355 | public void onDraw(Canvas canvas) {
356 | if (bitmap == null || bitmap.isRecycled()) {
357 | return;
358 | }
359 | super.onDraw(canvas);
360 | canvas.drawBitmap(this.bitmap, this.highlightLocX, this.highlightLocY, viewPaint);
361 |
362 | // drawArrow
363 | if (path != null && this.viewGroup.getVisibility() == View.VISIBLE) {
364 | canvas.drawPath(path, arrowPaint);
365 | }
366 | }
367 |
368 | private void applyAttrs(Context context, @Nullable ShowCaseBuilder builder) {
369 | // set Default value before check builder (might be null)
370 | this.layoutRes = R.layout.tutorial_view;
371 |
372 | this.textColor = Color.WHITE;
373 | this.titleTextColor = Color.WHITE;
374 | this.textTitleSize = getResources().getDimension(R.dimen.text_title);
375 | this.textSize = getResources().getDimension(R.dimen.text_normal);
376 |
377 | this.shadowColor = ContextCompat.getColor(context, R.color.shadow);
378 | this.spacing = (int) getResources().getDimension(R.dimen.spacing_normal);
379 |
380 | this.arrowMargin = this.spacing / 3;
381 | this.arrowWidth = (int) (1.5 * this.spacing);
382 |
383 | this.backgroundContentColor = Color.BLACK;
384 | this.circleBackgroundDrawableRes = R.drawable.selector_circle_green;
385 |
386 | this.prevDrawable = ContextCompat.getDrawable(context, R.drawable.ic_button_prev);
387 | this.nextDrawable = ContextCompat.getDrawable(context, R.drawable.ic_button_next);
388 | this.finishDrawable = ContextCompat.getDrawable(context, R.drawable.ic_button_finish);
389 | this.skipDrawable = ContextCompat.getDrawable(context, R.drawable.ic_button_skip);
390 |
391 | this.lineColorRes = ContextCompat.getColor(context,R.color.blue);
392 |
393 | if (builder == null) {
394 | return;
395 | }
396 |
397 | this.layoutRes = builder.getLayoutRes() != 0 ?
398 | builder.getLayoutRes()
399 | : this.layoutRes;
400 |
401 | this.textColor = builder.getTextColorRes() != 0 ?
402 | ContextCompat.getColor(context, builder.getTextColorRes())
403 | : this.textColor;
404 |
405 | this.lineColorRes = builder.getLineColorRes() != 0 ?
406 | ContextCompat.getColor(context, builder.getLineColorRes())
407 | : this.lineColorRes;
408 |
409 | this.titleTextColor = builder.getTitleTextColorRes() != 0 ?
410 | ContextCompat.getColor(context, builder.getTitleTextColorRes())
411 | : this.titleTextColor;
412 |
413 | this.textTitleSize = builder.getTitleTextSizeRes() != 0 ?
414 | getResources().getDimension(builder.getTitleTextSizeRes())
415 | : this.textTitleSize;
416 |
417 | this.textSize = builder.getTextSizeRes() != 0 ?
418 | getResources().getDimension(builder.getTextSizeRes())
419 | : this.textSize;
420 |
421 | this.backgroundContentColor = builder.getBackgroundContentColorRes() != 0 ?
422 | ContextCompat.getColor(context, builder.getBackgroundContentColorRes())
423 | : this.backgroundContentColor;
424 |
425 | this.shadowColor = builder.getShadowColorRes() != 0 ?
426 | ContextCompat.getColor(context, builder.getShadowColorRes()) :
427 | this.shadowColor;
428 |
429 | this.spacing = builder.getSpacingRes() != 0 ?
430 | (int) getResources().getDimension(builder.getSpacingRes())
431 | : this.spacing;
432 |
433 | this.circleBackgroundDrawableRes = builder.getCircleIndicatorBackgroundDrawableRes() != 0 ?
434 | builder.getCircleIndicatorBackgroundDrawableRes()
435 | : this.circleBackgroundDrawableRes;
436 |
437 | this.prevDrawable = builder.getPrevDrawableRes() != 0 ?
438 | getResources().getDrawable(builder.getPrevDrawableRes())
439 | : this.prevDrawable;
440 |
441 | this.nextDrawable = builder.getNextDrawableRes() != 0 ?
442 | getResources().getDrawable(builder.getNextDrawableRes())
443 | : this.nextDrawable;
444 |
445 | this.finishDrawable = builder.getFinishDrawableRes() != 0 ?
446 | getResources().getDrawable(builder.getFinishDrawableRes())
447 | : this.finishDrawable;
448 |
449 | this.skipDrawable = builder.getSkipDrawableRes() != 0 ?
450 | getResources().getDrawable(builder.getSkipDrawableRes())
451 | : this.skipDrawable;
452 |
453 | this.useCircleIndicator = builder.useCircleIndicator();
454 | this.hasSkipWord = builder.isUseSkipWord();
455 |
456 | this.isCancelable = builder.isClickable();
457 |
458 | if (builder.isUseArrow()) {
459 | this.arrowMargin = this.spacing / 3;
460 | this.arrowWidth = builder.getArrowWidth() != 0 ?
461 | (int) getResources().getDimension(builder.getArrowWidth())
462 | : this.arrowWidth;
463 | } else {
464 | this.arrowMargin = 0;
465 | this.arrowWidth = 0;
466 | }
467 |
468 | }
469 |
470 | private void initContent(Context context, ShowCaseBuilder builder) {
471 | this.viewGroup = (ViewGroup)
472 | LayoutInflater.from(context).inflate(this.layoutRes, this, false);
473 |
474 | int view_group_tutor_content = getResources().getIdentifier("view_group_tutor_content", "id", (builder.getPackageName() != null) ? builder.getPackageName() : context.getPackageName());
475 | int text_title = getResources().getIdentifier("text_title", "id", (builder.getPackageName() != null) ? builder.getPackageName() : context.getPackageName());
476 | int text_description = getResources().getIdentifier("text_description", "id", (builder.getPackageName() != null) ? builder.getPackageName() : context.getPackageName());
477 | int view_line = getResources().getIdentifier("view_line", "id", (builder.getPackageName() != null) ? builder.getPackageName() : context.getPackageName());
478 | int iv_previous = getResources().getIdentifier("iv_prev", "id", (builder.getPackageName() != null) ? builder.getPackageName() : context.getPackageName());
479 | int iv_skip = getResources().getIdentifier("iv_skip", "id", (builder.getPackageName() != null) ? builder.getPackageName() : context.getPackageName());
480 | int iv_next = getResources().getIdentifier("iv_next", "id", (builder.getPackageName() != null) ? builder.getPackageName() : context.getPackageName());
481 | int view_group_indicator = getResources().getIdentifier("view_group_indicator", "id", (builder.getPackageName() != null) ? builder.getPackageName() : context.getPackageName());
482 |
483 | View viewGroupTutorContent = viewGroup.findViewById(view_group_tutor_content);
484 | ViewHelper.setBackgroundColor(viewGroupTutorContent, this.backgroundContentColor);
485 |
486 | textViewTitle = (TextView) viewGroupTutorContent.findViewById(text_title);
487 |
488 | textViewTitle = (TextView) viewGroupTutorContent.findViewById(text_title);
489 | textViewTitle.setTextColor(this.titleTextColor);
490 | textViewTitle.setTextSize(TypedValue.COMPLEX_UNIT_PX, this.textTitleSize);
491 |
492 | textViewDesc = (TextView) viewGroupTutorContent.findViewById(text_description);
493 | textViewDesc.setTextColor(this.textColor);
494 | textViewDesc.setTextSize(TypedValue.COMPLEX_UNIT_PX, this.textSize);
495 |
496 | View line = viewGroupTutorContent.findViewById(view_line);
497 | if (line != null) {
498 | line.setBackgroundColor(lineColorRes);
499 | }
500 |
501 | prevButton = (ImageView) viewGroupTutorContent.findViewById(iv_previous);
502 | nextButton = (ImageView) viewGroupTutorContent.findViewById(iv_next);
503 | skipButton = (ImageView) viewGroupTutorContent.findViewById(iv_skip);
504 |
505 | viewGroupIndicator = (ViewGroup) viewGroupTutorContent.findViewById(view_group_indicator);
506 |
507 | if (prevButton != null) {
508 | prevButton.setImageDrawable(prevDrawable);
509 | prevButton.setOnClickListener(v -> {
510 | if (showCaseListener != null) ShowCaseLayout.this.showCaseListener.onPrevious();
511 | });
512 | }
513 | if (nextButton != null) {
514 | nextButton.setImageDrawable(nextDrawable);
515 | nextButton.setOnClickListener(v -> onNextClicked());
516 | }
517 |
518 | if (skipButton != null){
519 | skipButton.setImageDrawable(skipDrawable);
520 | skipButton.setOnClickListener(view -> {
521 | if (showCaseListener != null) ShowCaseLayout.this.showCaseListener.onComplete();
522 | });
523 | }
524 |
525 | this.addView(viewGroup);
526 | }
527 |
528 | private void moveViewBasedHighlight(int highlightXstart,
529 | int highlightYstart,
530 | int highlightXend,
531 | int highlightYend) {
532 | if (showCaseContentPosition == ShowCaseContentPosition.UNDEFINED) {
533 | int widthCenter = this.getWidth() / 2;
534 | int heightCenter = this.getHeight() / 2;
535 | if (highlightYend <= heightCenter) {
536 | showCaseContentPosition = ShowCaseContentPosition.BOTTOM;
537 | } else if (highlightYstart >= heightCenter) {
538 | showCaseContentPosition = ShowCaseContentPosition.TOP;
539 | } /*
540 | TODO
541 | this blocked code is should be not commented
542 |
543 | else if (highlightXend <= widthCenter) {
544 | showCaseContentPosition = ShowCaseContentPosition.RIGHT;
545 | } else if (highlightXstart >= widthCenter) {
546 | showCaseContentPosition = ShowCaseContentPosition.LEFT;
547 | }*/ else { // not fit anywhere
548 | // if bottom is bigger, put to bottom, else put it on top
549 | if ((this.getHeight() - highlightYend) > highlightYstart) {
550 | showCaseContentPosition = ShowCaseContentPosition.BOTTOM;
551 | } else {
552 | showCaseContentPosition = ShowCaseContentPosition.TOP;
553 | }
554 | }
555 | }
556 |
557 | LayoutParams layoutParams;
558 | switch (showCaseContentPosition) {
559 | case RIGHT: {
560 | int expectedWidth = getWidth() - highlightXend - 2 * this.spacing;
561 |
562 | viewGroup.measure(MeasureSpec.makeMeasureSpec(expectedWidth, MeasureSpec.EXACTLY),
563 | MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
564 | int viewGroupHeight = viewGroup.getMeasuredHeight();
565 |
566 | layoutParams = new LayoutParams(
567 | expectedWidth,
568 | LayoutParams.WRAP_CONTENT,
569 | Gravity.RIGHT);
570 | layoutParams.rightMargin = this.spacing;
571 | layoutParams.leftMargin = this.spacing;
572 | layoutParams.bottomMargin = 0;
573 |
574 | // calculate diff top height between object and the content;
575 | int hightLightHeight = (highlightYend - highlightYstart);
576 | int diffHeight = hightLightHeight - viewGroupHeight;
577 |
578 | // check top margin. top should not out of window
579 | int expectedTopMargin = highlightYstart + diffHeight / 2;
580 | checkMarginTopBottom(expectedTopMargin, layoutParams, viewGroupHeight);
581 |
582 | setLayoutViewGroup(layoutParams);
583 |
584 | if (arrowWidth == 0) {
585 | path = null;
586 | } else {
587 | int highLightCenterY = (highlightYend + highlightYstart) / 2;
588 | int recalcArrowWidth = getRecalculateArrowWidth(highLightCenterY, getHeight());
589 | if (recalcArrowWidth == 0) {
590 | path = null;
591 | } else {
592 | path = new Path();
593 | path.moveTo(highlightXend + this.arrowMargin, highLightCenterY);
594 | path.lineTo(highlightXend + this.spacing,
595 | highLightCenterY - arrowWidth / 2);
596 | path.lineTo(highlightXend + this.spacing,
597 | highLightCenterY + arrowWidth / 2);
598 | path.close();
599 | }
600 | }
601 | }
602 | break;
603 | case LEFT: {
604 | int expectedWidth = highlightXstart - 2 * this.spacing;
605 |
606 | viewGroup.measure(MeasureSpec.makeMeasureSpec(expectedWidth, MeasureSpec.EXACTLY),
607 | MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
608 | int viewGroupHeight = viewGroup.getMeasuredHeight();
609 |
610 | layoutParams = new LayoutParams(
611 | expectedWidth,
612 | LayoutParams.WRAP_CONTENT,
613 | Gravity.LEFT);
614 | layoutParams.leftMargin = this.spacing;
615 | layoutParams.rightMargin = this.spacing;
616 | layoutParams.bottomMargin = 0;
617 |
618 | // calculate diff top height between object and the content;
619 | int hightLightHeight = (highlightYend - highlightYstart);
620 | int diffHeight = hightLightHeight - viewGroupHeight;
621 |
622 | // check top margin. top should not out of window
623 | int expectedTopMargin = highlightYstart + diffHeight / 2;
624 | checkMarginTopBottom(expectedTopMargin, layoutParams, viewGroupHeight);
625 |
626 | setLayoutViewGroup(layoutParams);
627 |
628 | if (arrowWidth == 0) {
629 | path = null;
630 | } else {
631 | int highLightCenterY = (highlightYend + highlightYstart) / 2;
632 | int recalcArrowWidth = getRecalculateArrowWidth(highLightCenterY, getHeight());
633 | if (recalcArrowWidth == 0) {
634 | path = null;
635 | } else {
636 | path = new Path();
637 | path.moveTo(highlightXstart - this.arrowMargin, highLightCenterY);
638 | path.lineTo(highlightXstart - this.spacing,
639 | highLightCenterY - arrowWidth / 2);
640 | path.lineTo(highlightXstart - this.spacing,
641 | highLightCenterY + arrowWidth / 2);
642 | path.close();
643 | }
644 | }
645 | }
646 | break;
647 | case BOTTOM: {
648 | layoutParams = new LayoutParams(LayoutParams.MATCH_PARENT,
649 | LayoutParams.WRAP_CONTENT,
650 | Gravity.TOP);
651 | layoutParams.topMargin = highlightYend + this.spacing;
652 | layoutParams.leftMargin = this.spacing;
653 | layoutParams.rightMargin = this.spacing;
654 | layoutParams.bottomMargin = 0;
655 |
656 | setLayoutViewGroup(layoutParams);
657 |
658 | if (arrowWidth == 0) {
659 | path = null;
660 | } else {
661 | int highLightCenterX = (highlightXend + highlightXstart) / 2;
662 | int recalcArrowWidth = getRecalculateArrowWidth(highLightCenterX, getWidth());
663 | if (recalcArrowWidth == 0) {
664 | path = null;
665 | } else {
666 | path = new Path();
667 | path.moveTo(highLightCenterX, highlightYend + this.arrowMargin);
668 | path.lineTo(highLightCenterX - recalcArrowWidth / 2,
669 | highlightYend + this.spacing);
670 | path.lineTo(highLightCenterX + recalcArrowWidth / 2,
671 | highlightYend + this.spacing);
672 | path.close();
673 | }
674 | }
675 | }
676 | break;
677 | case TOP: {
678 | layoutParams = new LayoutParams(LayoutParams.MATCH_PARENT,
679 | LayoutParams.WRAP_CONTENT,
680 | Gravity.BOTTOM);
681 | layoutParams.bottomMargin = getHeight() - highlightYstart + this.spacing;
682 | layoutParams.topMargin = 0;
683 | layoutParams.leftMargin = this.spacing;
684 | layoutParams.rightMargin = this.spacing;
685 |
686 | setLayoutViewGroup(layoutParams);
687 |
688 | if (arrowWidth == 0) {
689 | path = null;
690 | } else {
691 | int highLightCenterX = (highlightXend + highlightXstart) / 2;
692 | int recalcArrowWidth = getRecalculateArrowWidth(highLightCenterX, getWidth());
693 | if (recalcArrowWidth == 0) {
694 | path = null;
695 | } else {
696 | path = new Path();
697 | path.moveTo(highLightCenterX, highlightYstart - this.arrowMargin);
698 | path.lineTo(highLightCenterX - recalcArrowWidth / 2,
699 | highlightYstart - this.spacing);
700 | path.lineTo(highLightCenterX + recalcArrowWidth / 2,
701 | highlightYstart - this.spacing);
702 | path.close();
703 | }
704 | }
705 | }
706 | break;
707 | case UNDEFINED:
708 | moveViewToCenter();
709 | break;
710 | }
711 | }
712 |
713 | private void setLayoutViewGroup(LayoutParams params) {
714 | this.viewGroup.setVisibility(View.INVISIBLE);
715 |
716 | this.viewGroup.addOnLayoutChangeListener(new OnLayoutChangeListener() {
717 | @Override
718 | public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
719 | ShowCaseLayout.this.viewGroup.setVisibility(View.VISIBLE);
720 | ShowCaseLayout.this.viewGroup.removeOnLayoutChangeListener(this);
721 | }
722 | });
723 | this.viewGroup.setLayoutParams(params);
724 | invalidate();
725 | }
726 |
727 | private int getRecalculateArrowWidth(int highlightCenter, int maxWidthOrHeight) {
728 | int recalcArrowWidth = arrowWidth;
729 | int safeArrowWidth = this.spacing + (arrowWidth / 2);
730 | if (highlightCenter < safeArrowWidth ||
731 | highlightCenter > (maxWidthOrHeight - safeArrowWidth)) {
732 | recalcArrowWidth = 0;
733 | }
734 | return recalcArrowWidth;
735 | }
736 |
737 | private void moveViewToCenter() {
738 | showCaseContentPosition = ShowCaseContentPosition.UNDEFINED;
739 |
740 | LayoutParams layoutParams = new LayoutParams(
741 | LayoutParams.MATCH_PARENT,
742 | LayoutParams.WRAP_CONTENT,
743 | Gravity.CENTER);
744 | layoutParams.rightMargin = this.spacing;
745 | layoutParams.leftMargin = this.spacing;
746 | layoutParams.bottomMargin = this.spacing;
747 | layoutParams.topMargin = this.spacing;
748 |
749 | setLayoutViewGroup(layoutParams);
750 | this.path = null;
751 | }
752 |
753 | private void checkMarginTopBottom(int expectedTopMargin,
754 | LayoutParams layoutParams,
755 | int viewHeight) {
756 | if (expectedTopMargin < this.spacing) {
757 | layoutParams.topMargin = this.spacing;
758 | } else {
759 | // check bottom margin. bottom should not out of window
760 | int prevActualHeight = expectedTopMargin + viewHeight + this.spacing;
761 | if (prevActualHeight > getHeight()) {
762 | int diff = prevActualHeight - getHeight();
763 | layoutParams.topMargin = expectedTopMargin - diff;
764 | } else {
765 | layoutParams.topMargin = expectedTopMargin;
766 | }
767 | }
768 | }
769 |
770 | private void recycleResources() {
771 | if (this.bitmap != null) {
772 | this.bitmap.recycle();
773 | }
774 | this.bitmap = null;
775 | if (this.lastTutorialView != null) {
776 | this.lastTutorialView.setDrawingCacheEnabled(false);
777 | }
778 | this.lastTutorialView = null;
779 | this.viewPaint = null;
780 | }
781 |
782 | private Spanned fromHtml(String html) {
783 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
784 | return Html.fromHtml(html, Html.FROM_HTML_MODE_LEGACY);
785 | } else {
786 | return Html.fromHtml(html);
787 | }
788 | }
789 | }
790 |
--------------------------------------------------------------------------------