diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml
new file mode 100644
index 000000000..757c789ab
--- /dev/null
+++ b/.github/FUNDING.yml
@@ -0,0 +1,3 @@
+# These are supported funding model platforms
+
+github: AppIntro
diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md
deleted file mode 100644
index 1190f4acb..000000000
--- a/.github/ISSUE_TEMPLATE.md
+++ /dev/null
@@ -1,30 +0,0 @@
-
-
-
-**AppIntro Version**:
-
-
-**Device/Android Version**:
-
-
-**Issue details / Repro steps / Use case background**:
-
-
-**Your Code**:
-
-
-**Stack trace / LogCat**:
-```ruby
-paste stack trace and/or log here
-```
-
-
diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md
new file mode 100644
index 000000000..03f2ffb83
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/bug_report.md
@@ -0,0 +1,38 @@
+---
+name: Bug report
+about: Create a report for an issue or a bug with AppIntro
+---
+
+
+
+## 🐛 Describe the bug
+
+
+## ⚠️ Current behavior
+
+
+## ✅ Expected behavior
+
+
+## 💣 Steps to reproduce
+
+
+## 📷 Screenshots
+
+
+## 📑 Your Code
+
+
+
+
+
+
+## 📱 Tech info
+ - AppIntro Version:
+ - Device:
+ - Android OS Version:
\ No newline at end of file
diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml
new file mode 100644
index 000000000..6b1902e65
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/config.yml
@@ -0,0 +1,8 @@
+blank_issues_enabled: false
+contact_links:
+ - name: Questions
+ url: https://github.com/AppIntro/AppIntro/discussions/new
+ about: Ask questions about AppIntro and get support for your problems.
+ - name: Slack Chat
+ url: https://kotlinlang.slack.com/archives/C019SH1RMBN
+ about: Join the 'appintro' channel on KotlinLang Slack to chat with other AppIntro developers.
\ No newline at end of file
diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md
new file mode 100644
index 000000000..958d339f1
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/feature_request.md
@@ -0,0 +1,15 @@
+---
+name: Feature request
+about: Suggest an idea or a new feature for AppIntro
+---
+
+## ⚠️ Is your feature request related to a problem? Please describe
+
+
+## 💡 Describe the solution you'd like
+
+
+## 🤚 Do you want to develop this feature yourself?
+
+- [ ] Yes
+- [ ] No
diff --git a/.github/workflows/gradle-wrapper-validation.yml b/.github/workflows/gradle-wrapper-validation.yml
index c1f10567e..f12fd6368 100644
--- a/.github/workflows/gradle-wrapper-validation.yml
+++ b/.github/workflows/gradle-wrapper-validation.yml
@@ -13,6 +13,6 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout latest code
- uses: actions/checkout@v2
+ uses: actions/checkout@v4
- name: Validate Gradle Wrapper
- uses: gradle/wrapper-validation-action@v1
+ uses: gradle/wrapper-validation-action@v3
diff --git a/.github/workflows/pre-merge.yml b/.github/workflows/pre-merge.yml
index 22544204a..4095ac3f9 100644
--- a/.github/workflows/pre-merge.yml
+++ b/.github/workflows/pre-merge.yml
@@ -2,42 +2,105 @@ name: Pre Merge Checks
on:
push:
branches:
- - '*'
+ - 'main'
pull_request:
branches:
- '*'
jobs:
- gradle:
- runs-on: [ubuntu-latest]
+ ktlint:
+ runs-on: 'ubuntu-latest'
+ env:
+ GRADLE_OPTS: -Dorg.gradle.daemon=false
steps:
- name: Checkout Repo
- uses: actions/checkout@v2
+ uses: actions/checkout@v4
- - name: Cache Gradle Caches
- uses: actions/cache@v1
- with:
- path: ~/.gradle/caches/
- key: cache-clean-gradle-${{ matrix.os }}-${{ matrix.jdk }}
- - name: Cache Gradle Wrapper
- uses: actions/cache@v1
+ - name: Setup Java
+ uses: actions/setup-java@v4
with:
- path: ~/.gradle/wrapper/
- key: cache-clean-wrapper-${{ matrix.os }}-${{ matrix.jdk }}
+ distribution: 'zulu'
+ java-version: '17'
+ cache: 'gradle'
- name: Run ktlint
run: ./gradlew ktlintCheck
+ detekt:
+ runs-on: 'ubuntu-latest'
+ env:
+ GRADLE_OPTS: -Dorg.gradle.daemon=false
+ steps:
+ - name: Checkout Repo
+ uses: actions/checkout@v4
+
+ - name: Setup Java
+ uses: actions/setup-java@v4
+ with:
+ distribution: 'zulu'
+ java-version: '17'
+ cache: 'gradle'
+
- name: Run detekt
run: ./gradlew detekt
+ lint:
+ runs-on: 'ubuntu-latest'
+ env:
+ GRADLE_OPTS: -Dorg.gradle.daemon=false
+ steps:
+ - name: Checkout Repo
+ uses: actions/checkout@v4
+
+ - name: Setup Java
+ uses: actions/setup-java@v4
+ with:
+ distribution: 'zulu'
+ java-version: '17'
+ cache: 'gradle'
+
+ - name: Run lint
+ run: ./gradlew lint
+
+ test:
+ runs-on: 'ubuntu-latest'
+ env:
+ GRADLE_OPTS: -Dorg.gradle.daemon=false
+ steps:
+ - name: Checkout Repo
+ uses: actions/checkout@v4
+
+ - name: Setup Java
+ uses: actions/setup-java@v4
+ with:
+ distribution: 'zulu'
+ java-version: '17'
+ cache: 'gradle'
+
- name: Run all the tests
run: ./gradlew test
- - name: Build everything
- run: ./gradlew build
+ build-debug-apk:
+ runs-on: 'ubuntu-latest'
+ name: Build Debug APK
+ env:
+ GRADLE_OPTS: -Dorg.gradle.daemon=false
+
+ steps:
+ - name: Checkout Repo
+ uses: actions/checkout@v4
+
+ - name: Setup Java
+ uses: actions/setup-java@v4
+ with:
+ distribution: 'zulu'
+ java-version: '17'
+ cache: 'gradle'
+
+ - name: Build the Debug APK
+ run: ./gradlew assembleDebug
- # We stop gradle at the end to make sure the cache folders
- # don't contain any lock files and are free to be cached.
- - name: Stop Gradle
- run: ./gradlew --stop
+ - uses: actions/upload-artifact@v4
+ with:
+ name: appintro-sample-app-${{ matrix.name }}.apk
+ path: example/build/outputs/apk/debug/example-debug.apk
diff --git a/.gitignore b/.gitignore
index 7b4b2e364..3f428279e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -52,3 +52,8 @@ captures/
!/example/example-release-v7.apk
!/example/proguard-rules.pro
.idea/
+
+.DS_Store
+
+# Kotlin
+.kotlin
\ No newline at end of file
diff --git a/CHANGELOG.md b/CHANGELOG.md
index a281c7870..ee32fd3a6 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,142 @@
# Change Log
+## Version 7.0.0 *(YYYY-MM-DD)*
+This is a new major release of AppIntro. Please note that this release contains multiple new features (see below), several bugfixes, as well as multiple breaking changes.
+
+### Summary of Changes
+* [#1112] We migrated AppIntro from ViewPager to ViewPager2
+
+### Breaking Changes
+* We removed `setScrollDurationFactor` since customizing scroll duration is supported anymore on ViewPager2
+* `setCustomTransformer` accepts a `ViewPager2.PageTransformer` instead of `ViewPager.PageTransformer`
+
+## Version 6.3.1 *(2023-07-24)*
+This release of AppIntro is identical to 6.3.0. An outdated JitPack configuration caused 6.3.0 not to build correctly.
+
+## Version 6.3.0 *(2023-07-23)*
+This is a new minor release of AppIntro. This library comes with several new features (see below) and bugfixes.
+
+### Summary of Changes
+* We deprecated `setScrollDurationFactor` since customizing scroll duration will not be supported anymore in upcoming releases of AppIntro based on ViewPager2
+* Target SDK is now 33
+
+### Enhancements 🎁
+* [#1030] AppIntro now internally uses Gradle KTS and Version Catalog
+* [#1080] Add ability to change done button background color
+* [#1049] Handle onBackPressed deprecation
+* [#1051] Register callback on onBackPressedDispatcher
+
+### Bugfixes 🐛
+* [#1002] Fix RTL bug on wrongly retained currentItem
+* [#1108] Fix RTL detection
+* [#1109] Fix unexpected crash when using custom layouts with wrong ids with Parallax effect
+
+### Dependency updates 📦
+* Kotlin to 1.9.0
+* AGP to 8.0.2
+* AppCompat to 1.6.1
+* ConstraintLayout to 2.1.4
+
+## Version 6.2.0 *(2022-01-17)*
+
+This is a new minor release of AppIntro. This library comes with several new features (see below) and bugfixes.
+
+### Summary of Changes
+
+* We deprecated `AppIntroFragment.newInstance` in favor of `AppIntroFragment.createInstance`. This was needed in order
+to support passing color resources instead of color int, to tackle scenarios such as configuration changes and dark mode [#979].
+* Target SDK is now 31
+* We exposed a couple of properties/methods on the public API that were requested by the community ([#960] and [#959])
+* We added some Java examples for our Java users [#953]
+
+### Enhancements 🎁
+
+* [#959] Add @JvmOverloads on goToNextSlide
+* [#960] Expose a protected property for slidesNumber
+* [#979] Fix #926: Add color resource parameters
+* [#993] Make description scrollable
+
+### Bugfixes 🐛
+
+* [#934] Fix ProgressIndicatorController in RTL
+
+### Dependency updates 📦
+
+* Java version to 11
+* Kotlin to 1.6.10
+* AGP to 7.0.3
+* Androidx Appcompat to 1.4.0
+* ConstraintLayout to 2.1.2
+
+## Version 6.1.0 *(2021-02-03)*
+
+This is a new minor release of AppIntro. This library comes with several new features (see below) and several bugfixes.
+
+### Summary of Changes
+
+* Target SDK is now 30.
+* Text visualization has been improved with Autosizing TextViews and URL autolinking.
+* AppIntro now offers better support for tablets (`sw600dp`).
+* Slide indicator has been improved with better color blending and it won't be shown if you have only one slide.
+* The AppIntro sample app has been completely rewritten with more examples (for Java, SlidePolicy and more).
+* 14 translations have been added or improved.
+
+### Enhancements 🎁
+
+* [#922] Add support for Autosizing TextViews
+* [#887] Added start/stop animation for `Animatable` images
+* [#883] Changing Visibility of Progress Bar when count of slide is one
+* [#878] Add sw600dp support
+* [#876] Single slide indicator condition
+* [#870] added URL autolinking in appintro_fragment_intro.xml
+* [#869] Allow to change skip and next arrow button color
+* [#857] Function for setting text appearance of Done and Skip buttons
+* [#825] Added ability to color back arrow and done button
+* [#796] Extend visibility of goToPreviousSlide to protected
+
+### Bugfixes 🐛
+
+* [#903] Deprecate the setNextPageSwipeLock method
+* [#896] Fix for crash on PagerAdapter when using SlidePolicy
+* [#891] Do not show back arrow on wizard mode on first slide
+* [#856] Fix blending of Indicator Colors
+* [#821] Fix bug with SetSwipeLock being reset to false
+* [#817] enabling backward sliding when slide policy not met
+* [#808] Fix isButtonsEnabled being reset to true after onCreate
+* [#802] Fix goToNextSlide default param for RTL
+* [#792] Remove reference to mainView in AppIntroBaseFragment
+* [#791] Clearing main layout in onDestroyView
+
+### Translations 🌍
+
+* [#919] Romanian (RO) translation
+* [#908] Added Bulgarian translation
+* [#895] Azerbaijan language support
+* [#880] add Ukrainian translation
+* [#877] Tamil translation added
+* [#874] added translation for bengali
+* [#872] added translation for yoruba
+* [#868] Translation for Gujarati language
+* [#862] Add Japanese translation
+* [#839] Complete the Chinese (Simplified) translation
+* [#834] Russian localization
+* [#831] Finish the French translation
+* [#829] Update Persian localization
+* [#812] Update strings.xml Spanish
+
+### Dependency updates 📦
+
+* Kotlin to 1.4.21
+* AGP to 4.1.1
+* Androidx Appcompat to 1.2.0
+* Constraintlayout to 2.0.4
+
+### Credits
+
+This release was possible thanks to the contribution of:
+
+[@CristianCardosoA](https://github.com/CristianCardosoA) [@Debanshu777](https://github.com/Debanshu777) [@Debanshu777](https://github.com/Debanshu777) [@Hoossayn](https://github.com/Hoossayn) [@Hoossayn](https://github.com/Hoossayn) [@JekRock](https://github.com/JekRock) [@LinX64](https://github.com/LinX64) [@NikolaGanchev](https://github.com/NikolaGanchev) [@RobertPal95](https://github.com/RobertPal95) [@WWCheng02](https://github.com/WWCheng02) [@ZakCodes](https://github.com/ZakCodes) [@beefsausage](https://github.com/beefsausage) [@ch22843](https://github.com/ch22843) [@cortinico](https://github.com/cortinico) [@ghost](https://github.com/ghost) [@idish](https://github.com/idish) [@kunal-ch](https://github.com/kunal-ch) [@manimaran96](https://github.com/manimaran96) [@paolorotolo](https://github.com/paolorotolo) [@siper](https://github.com/siper) [@sr01](https://github.com/sr01) [@tsumuchan](https://github.com/tsumuchan)
+
## Version 6.0.0 *(2020-05-04)*
This is a new major release of AppIntro. Please note that this release contains multiple new features (see below), several bugfixes, as well as multiple breaking changes.
@@ -58,7 +195,7 @@ To get a deeper overview of the breaking changes, please read the [migration doc
### Library Internals ⚙️
-* [#774] Move from Travis to Github Actions
+* [#774] Move from Travis to GitHub Actions
* [#768] Refactor Transformers to use a sealed class
* [#766] Add missing @Res annotations
* [#765] Remove `I` prefix from interface names
@@ -128,10 +265,9 @@ This release was possible thanks to the contribution of:
Previous release notes can be found here: [releases]
-[releases]: https://github.com/AppIntro/AppIntro/releases?after=v5.0.0
-[#597]: https://github.com/AppIntro/AppIntro/pull/597
-[#590]: https://github.com/AppIntro/AppIntro/pull/590
[#574]: https://github.com/AppIntro/AppIntro/pull/574
+[#590]: https://github.com/AppIntro/AppIntro/pull/590
+[#597]: https://github.com/AppIntro/AppIntro/pull/597
[#600]: https://github.com/AppIntro/AppIntro/pull/600
[#601]: https://github.com/AppIntro/AppIntro/pull/601
[#602]: https://github.com/AppIntro/AppIntro/pull/602
@@ -202,4 +338,53 @@ Previous release notes can be found here: [releases]
[#770]: https://github.com/AppIntro/AppIntro/pull/770
[#773]: https://github.com/AppIntro/AppIntro/pull/773
[#774]: https://github.com/AppIntro/AppIntro/pull/774
+[#791]: https://github.com/AppIntro/AppIntro/pull/791
+[#792]: https://github.com/AppIntro/AppIntro/pull/792
+[#796]: https://github.com/AppIntro/AppIntro/pull/796
+[#802]: https://github.com/AppIntro/AppIntro/pull/802
+[#808]: https://github.com/AppIntro/AppIntro/pull/808
+[#812]: https://github.com/AppIntro/AppIntro/pull/812
+[#817]: https://github.com/AppIntro/AppIntro/pull/817
+[#821]: https://github.com/AppIntro/AppIntro/pull/821
+[#825]: https://github.com/AppIntro/AppIntro/pull/825
+[#829]: https://github.com/AppIntro/AppIntro/pull/829
+[#831]: https://github.com/AppIntro/AppIntro/pull/831
+[#834]: https://github.com/AppIntro/AppIntro/pull/834
+[#839]: https://github.com/AppIntro/AppIntro/pull/839
+[#856]: https://github.com/AppIntro/AppIntro/pull/856
+[#857]: https://github.com/AppIntro/AppIntro/pull/857
+[#862]: https://github.com/AppIntro/AppIntro/pull/862
+[#868]: https://github.com/AppIntro/AppIntro/pull/868
+[#869]: https://github.com/AppIntro/AppIntro/pull/869
+[#870]: https://github.com/AppIntro/AppIntro/pull/870
+[#872]: https://github.com/AppIntro/AppIntro/pull/872
+[#874]: https://github.com/AppIntro/AppIntro/pull/874
+[#876]: https://github.com/AppIntro/AppIntro/pull/876
+[#877]: https://github.com/AppIntro/AppIntro/pull/877
+[#878]: https://github.com/AppIntro/AppIntro/pull/878
+[#880]: https://github.com/AppIntro/AppIntro/pull/880
+[#883]: https://github.com/AppIntro/AppIntro/pull/883
+[#887]: https://github.com/AppIntro/AppIntro/pull/887
+[#891]: https://github.com/AppIntro/AppIntro/pull/891
+[#895]: https://github.com/AppIntro/AppIntro/pull/895
+[#896]: https://github.com/AppIntro/AppIntro/pull/896
+[#903]: https://github.com/AppIntro/AppIntro/pull/903
+[#908]: https://github.com/AppIntro/AppIntro/pull/908
+[#919]: https://github.com/AppIntro/AppIntro/pull/919
+[#922]: https://github.com/AppIntro/AppIntro/pull/922
+[#934]: https://github.com/AppIntro/AppIntro/pull/934
+[#953]: https://github.com/AppIntro/AppIntro/pull/953
+[#959]: https://github.com/AppIntro/AppIntro/pull/959
+[#960]: https://github.com/AppIntro/AppIntro/pull/960
+[#979]: https://github.com/AppIntro/AppIntro/pull/979
+[#993]: https://github.com/AppIntro/AppIntro/pull/993
+[#1030]: https://github.com/AppIntro/AppIntro/pull/1030
+[#1080]: https://github.com/AppIntro/AppIntro/pull/1080
+[#1049]: https://github.com/AppIntro/AppIntro/pull/1049
+[#1051]: https://github.com/AppIntro/AppIntro/pull/1051
+[#1002]: https://github.com/AppIntro/AppIntro/pull/1002
+[#1108]: https://github.com/AppIntro/AppIntro/pull/1108
+[#1109]: https://github.com/AppIntro/AppIntro/pull/1109
+[#1112]: https://github.com/AppIntro/AppIntro/pull/1112
+[releases]: https://github.com/AppIntro/AppIntro/releases?after=v5.0.0
diff --git a/README.md b/README.md
index 36ba0c4f2..5f2c98c88 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
# AppIntro
-[](https://jitpack.io/#AppIntro/appintro) [](https://github.com/AppIntro/AppIntro/actions?query=workflow%3A%22Pre+Merge+Checks%22) [](https://android-arsenal.com/details/1/1939) [](https://gitter.im/AppIntro/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [](https://github.com/KotlinBy/awesome-kotlin)
+[](https://jitpack.io/#AppIntro/appintro) [](https://kotlinlang.slack.com/archives/C019SH1RMBN) [](https://github.com/AppIntro/AppIntro/actions?query=workflow%3A%22Pre+Merge+Checks%22) [](https://github.com/KotlinBy/awesome-kotlin)
AppIntro is an Android Library that helps you build a **cool carousel intro** for your App. AppIntro has support for **requesting permissions** and helps you create a great onboarding experience in just a couple of minutes.
@@ -11,6 +11,7 @@ AppIntro is an Android Library that helps you build a **cool carousel intro** fo
* [Getting Started 👣 ](#getting-started-)
* [Adding a dependency](#adding-a-dependency)
* [Basic usage](#basic-usage)
+ * [Java users](#java-users)
* [Migrating 🚗 ](#migrating-)
* [Features 🧰 ](#features-)
* [Creating Slides 👩🎨 ](#creating-slides-)
@@ -27,6 +28,7 @@ AppIntro is an Android Library that helps you build a **cool carousel intro** fo
* [Immersive Mode](#immersive-mode)
* [System Back button](#system-back-button)
* [System UI (Status Bar and Navigation Bar)](#system-ui-status-bar-and-navigation-bar)
+ * [Bottom Bar Margin](#bottom-bar-margin)
* [Permission 🔒 ](#permission-)
* [Slide Policy](#slide-policy)
* [Example App 💡 ](#example-app-)
@@ -57,7 +59,7 @@ repositories {
```groovy
dependencies {
// AndroidX Capable version
- implementation 'com.github.AppIntro:AppIntro:6.0.0'
+ implementation 'com.github.AppIntro:AppIntro:6.3.1'
// *** OR ***
@@ -80,11 +82,11 @@ class MyCustomAppIntro : AppIntro() {
// Call addSlide passing your Fragments.
// You can use AppIntroFragment to use a pre-built fragment
- addSlide(AppIntroFragment.newInstance(
+ addSlide(AppIntroFragment.createInstance(
title = "Welcome...",
description = "This is the first slide of the example"
))
- addSlide(AppIntroFragment.newInstance(
+ addSlide(AppIntroFragment.createInstance(
title = "...Let's get started!",
description = "This is the last slide, I won't annoy you more :)"
))
@@ -106,6 +108,8 @@ class MyCustomAppIntro : AppIntro() {
Please note that you **must NOT call** setContentView. The `AppIntro` superclass is taking care of it for you.
+Also confirm that you're overriding `onCreate` with **a single parameter** (`Bundle`) and you're not using another override (like `onCreate(Bundle, PersistableBundle)`) instead.
+
Finally, declare the activity in your Manifest like so:
``` xml
@@ -115,6 +119,10 @@ Finally, declare the activity in your Manifest like so:
We suggest to don't declare `MyCustomAppIntro` as your first Activity unless you want the intro to launch every time your app starts. Ideally you should show the AppIntro activity only once to the user, and you should hide it once completed (you can use a flag in the `SharedPreferences`).
+### Java users
+
+You can find many examples in java language in the [examples directory](example/src/main/java/com/github/appintro/example/ui/java/JavaIntro.java)
+
## Migrating 🚗
If you're migrating **from AppIntro v5.x to v6.x**, please expect multiple breaking changes. You can find documentation on how to update your code on this other [migration guide](/docs/migrating-from-5.0.md).
@@ -143,14 +151,14 @@ You can use the `AppIntroFragment` if you just want to customize title, descript
That's the suggested approach if you want to create a quick intro:
```kotlin
-addSlide(AppIntroFragment.newInstance(
+addSlide(AppIntroFragment.createInstance(
title = "The title of your slide",
description = "A description that will be shown on the bottom",
imageDrawable = R.drawable.the_central_icon,
backgroundDrawable = R.drawable.the_background_image,
- titleColor = Color.YELLOW,
- descriptionColor = Color.RED,
- backgroundColor = Color.BLUE,
+ titleColorRes = R.color.yellow,
+ descriptionColorRes = R.color.red,
+ backgroundColorRes = R.color.blue,
titleTypefaceFontRes = R.font.opensans_regular,
descriptionTypefaceFontRes = R.font.opensans_regular,
))
@@ -159,7 +167,7 @@ addSlide(AppIntroFragment.newInstance(
All the parameters are optional, so you're free to customize your slide as you wish.
If you need to programmatically create several slides you can also use the `SliderPage` class.
-This class can be passed to `AppIntroFragment.newInstance(sliderPage: SliderPage)` that will create
+This class can be passed to `AppIntroFragment.createInstance(sliderPage: SliderPage)` that will create
a new slide starting from that instance.
### `AppIntroCustomLayoutFragment`
@@ -185,14 +193,11 @@ AppIntro offers several configuration option to help you customize your onboardi
AppIntro comes with a set of _Slide Transformer_ that you can use out of the box to animate your Slide transition.
-| Name | Preview |
-| ---: | :-----: |
-| Fade | |
-| Zoom | |
-| Flow | |
-| Slide Over | |
-| Depth | |
-| Parallax | |
+| Slide Transformers | Slide Transformers |
+| :---: | :---: |
+| Fade | Zoom |
+| Flow | Slide Over |
+| Depth | Parallax |
You can simply call `setTransformer()` and pass one of the subclass of the sealed class `AppIntroPageTransformerType`:
@@ -316,8 +321,8 @@ vibrateDuration = 50L
-AppIntro supports a _wizards_ mode where the Skip button will be replaced with the back arrow.
-This comes handy if you're presenting a Wizard to your user with a set of skip they need to do,
+AppIntro supports a _Wizard_ mode where the Skip button will be replaced with the back arrow.
+This comes handy if you're presenting a Wizard to your users with a set of steps they need to do,
and they might frequently go back and forth.
You can enable it with:
@@ -332,8 +337,8 @@ isWizardMode = true
-If you want to display your Intro with a fullscreen experience, you can enable the _Immersive mode_. This will
-hide both the Status Bar and the Navigation bar and the user will have to scroll from the top of the screen to
+If you want to display your Intro with a fullscreen experience, you can enable the _Immersive_ mode. This will
+hide both the _Status Bar_ and the _Navigation Bar_ and the user will have to scroll from the top of the screen to
show them again.
This allows you to have more space for your Intro content and graphics.
@@ -346,7 +351,7 @@ setImmersiveMode()
### System Back button
-You can lock the System Back button if you don't want your user go bo back from intro.
+You can lock the System Back button if you don't want your user to go back from intro.
This could be useful if you need to request permission and the Intro experience is not optional.
If this is the case, please set to true the following flag:
@@ -375,6 +380,17 @@ setNavBarColor(Color.RED)
setNavBarColorRes(R.color.red)
```
+### Bottom Bar Margin
+
+By default, the slides use the whole size available in the screen, so it might happen that the
+bottom bar overlaps the content of the slide if you have set the background color to a
+non-transparent one.
+If you want to make sure that the bar doesn't overlap the content, use the `setBarMargin` function
+as follows:
+```kotlin
+setBarMargin(true)
+```
+
## Permission 🔒
@@ -392,16 +408,19 @@ Please note that:
```kotlin
// Ask for required CAMERA permission on the second slide.
askForPermissions(
- permissions = arrayOf(Manifest.permission.CAMER),
+ permissions = arrayOf(Manifest.permission.CAMERA),
slideNumber = 2,
required = true)
// Ask for both optional ACCESS_FINE_LOCATION and WRITE_EXTERNAL_STORAGE
// permission on the third slide.
askForPermissions(
- permissions = arrayOf(Manifest.permission.CAMER),
- slideNumber = 2,
- required = true)
+ permissions = arrayOf(
+ Manifest.permission.ACCESS_FINE_LOCATION,
+ Manifest.permission.WRITE_EXTERNAL_STORAGE
+ ),
+ slideNumber = 3,
+ required = false)
```
Should you need further control on the permission request, you can override those two methods on the `AppIntro` class:
@@ -439,9 +458,13 @@ class MyFragment : Fragment(), SlidePolicy {
}
```
+You can find a full working example of `SlidePolicy` in the [example app - CustomSlidePolicyFragment.kt](example/src/main/java/com/github/appintro/example/ui/custom/fragments/CustomSlidePolicyFragment.kt)
+
## Example App 💡
-AppIntro comes with a **sample app** full of examples and use case that you can use as inspiration for your project. You can find it inside the [/example folder](https://github.com/AppIntro/AppIntro/tree/master/example).
+AppIntro comes with a **sample app** full of examples and use case that you can use as inspiration for your project. You can find it inside the [/example folder](https://github.com/AppIntro/AppIntro/tree/main/example).
+
+You can get a **debug APK** of the sample app from the **Pre Merge** GitHub Actions job as an [output artifact here](https://github.com/AppIntro/AppIntro/actions?query=workflow%3A%22Pre+Merge+Checks%22).
@@ -473,7 +496,7 @@ If a translation in your language is already available, please check it and even
## Snapshots 📦
-Development of AppIntro happens on the [master](https://github.com/AppIntro/AppIntro/tree/master) branch. You can get `SNAPSHOT` versions directly from JitPack if needed.
+Development of AppIntro happens on the [main](https://github.com/AppIntro/AppIntro/tree/main) branch. You can get `SNAPSHOT` versions directly from JitPack if needed.
```gradle
repositories {
@@ -483,7 +506,7 @@ repositories {
```gradle
dependencies {
- implementation "com.github.AppIntro:AppIntro:master-SNAPSHOT"
+ implementation "com.github.AppIntro:AppIntro:main-SNAPSHOT"
}
```
@@ -491,7 +514,7 @@ dependencies {
## Contributing 🤝
-We're offering support for [AppIntro on Gitter](https://gitter.im/AppIntro/Lobby). Come and join the conversation over there.
+We're offering support for AppIntro on the [#appintro channel on KotlinLang Slack](https://kotlinlang.slack.com/archives/C019SH1RMBN). Come and join the conversation over there. If you don't have access to KotlinLang Slack, you can [request access here](https://surveys.jetbrains.com/s3/kotlin-slack-sign-up).
**We're looking for contributors! Don't be shy.** 👍 Feel free to open issues/pull requests to help me improve this project.
@@ -502,7 +525,7 @@ We're offering support for [AppIntro on Gitter](https://gitter.im/AppIntro/Lobby
### Maintainers
-AppIntro is currently developed and maintained by the [AppIntro Github Org](https://github.com/AppIntro). When submitting a new PR, please ping one of:
+AppIntro is currently developed and maintained by the [AppIntro GitHub Org](https://github.com/AppIntro). When submitting a new PR, please ping one of:
- [@paolorotolo](https://github.com/paolorotolo)
- [@cortinico](https://github.com/cortinico)
@@ -540,30 +563,18 @@ If you are using AppIntro in your app and would like to be listed here, please o
List of Apps using AppIntro
-* [Audio Reminder Pro](https://play.google.com/store/apps/details?id=com.brandon.audioreminderpro)
-* [Wizr Daily Quotes](https://play.google.com/store/apps/details?id=com.wizrapp)
-* [Planets](https://play.google.com/store/apps/details?id=com.andrewq.planets)
-* [PDF Me](https://play.google.com/store/apps/details?id=com.pdfme)
* [Smoothie Recipes](https://play.google.com/store/apps/details?id=com.skykonig.smoothierecipes)
* [neutriNote](https://play.google.com/store/apps/details?id=com.appmindlab.nano)
* [Handwriting Note](https://play.google.com/store/apps/details?id=com.lyk.immersivenote)
-* [Friends Roulette](https://play.google.com/store/apps/details?id=com.crioltech.roulette)
* [ChineseDictionary (粵韻漢典離線粵語普通話發聲中文字典)](https://play.google.com/store/apps/details?id=com.jonasng.chinesedictionary)
-* [Sifter](https://play.google.com/store/apps/details?id=sifter.social.network.archaeologist)
-* [Service Notes](https://play.google.com/store/apps/details?id=notes.service.com.servicenotes)
* [Salary Barometer](https://play.google.com/store/apps/details?id=anaware.salarybarometer)
* [Best Business Idea!](https://play.google.com/store/apps/details?id=anaware.bestidea)
* [Wi-Fi password reminder](https://play.google.com/store/apps/details?id=com.rusdelphi.wifipassword)
-* [Xpaper - Moto X Wallpapers](https://play.google.com/store/apps/details?id=com.dunrite.xpaper)
* [Find My Parked Car](https://play.google.com/store/apps/details?id=com.ofirmiron.findmycarandroidwear)
* [Vape Tool Pro](https://play.google.com/store/apps/details?id=com.stasbar.vapetoolpro)
-* [sdiwi | Win your purchase!](https://play.google.com/store/apps/details?id=com.sdiwi.app)
-* [Schematiskt Skolschema](https://play.google.com/store/apps/details?id=se.zinokader.schematiskt)
* [Third Eye](https://play.google.com/store/apps/details?id=com.miragestacks.thirdeye)
* [Web Video Cast](https://play.google.com/store/apps/details?id=com.instantbits.cast.webvideo)
-* [SchoolBox](https://play.google.com/store/apps/details?id=com.deenysoft.schoolbox)
* [Fitness Challenge](https://play.google.com/store/apps/details?id=com.isidroid.fitchallenge)
-* [Crunch (ICE)](https://play.google.com/store/apps/details?id=com.figsandolives.ice.free)
* [Filmy - Your Movie Guide](https://play.google.com/store/apps/details?id=tech.salroid.filmy)
* [HEBF Optimizer ▪ Root](https://play.google.com/store/apps/details?id=com.androidvip.hebf)
* [IIFYM](https://play.google.com/store/apps/details?id=com.javierd.iifym)
@@ -572,7 +583,21 @@ If you are using AppIntro in your app and would like to be listed here, please o
* [Boo Music Player](https://play.google.com/store/apps/details?id=cdn.BooPlayer)
* [BeatPrompter](https://play.google.com/store/apps/details?id=com.stevenfrew.beatprompter)
* [BlueWords](https://play.google.com/store/apps/details?id=com.thesrb.bluewords&referrer=utm_source%3Dappintro%26utm_medium%3Dgithub%26utm_campaign%3Dreadme)
-* [Best Quotes & Status 2019 (99000+ Collection)](https://play.google.com/store/apps/details?id=com.swastik.quotesandstatus&hl=en_IN)
* [Orbot](https://play.google.com/store/apps/details?id=org.torproject.android)
-
+* [PhotoGuard Photo Vault](https://play.google.com/store/apps/details?id=com.photovault.photoguard)
+* [Ride My Park - Best Spots, Skateparks Map](https://play.google.com/store/apps/details?id=com.andrieutom.rmp)
+* [Shopping list](https://play.google.com/store/apps/details?id=de.vanse.shoppinglist) and [Shopping list pro](https://play.google.com/store/apps/details?id=de.vanse.shoppinglist.pro)
+* [PZPIC - Pan & Zoom Effect Video from Still Picture](https://play.google.com/store/apps/details?id=com.photo3dlab.pzpic)
+* [PrezziBenzina](https://play.google.com/store/apps/details?id=org.vernazza.androidfuel)
+* [LogViewer for openHAB](https://github.com/cyb3rko/logviewer-for-openhab-app)
+* [Firmo con CIE](https://play.google.com/store/apps/details?id=com.cyberneid.disigoncie)
+* [iC-YOURLS](https://play.google.com/store/apps/details?id=net.inscomers.yourls)
+* [Noon Happen](https://play.google.com/store/apps/details?id=com.noonhappen.noonhappen)
+* [Weather Forecast](https://play.google.com/store/apps/details?id=com.ehlb.weatherapp)
+* [Zoned Pomodoro Timer](https://play.google.com/store/apps/details?id=com.just.five)
+* [PCAPdroid Network Monitor](https://play.google.com/store/apps/details?id=com.emanuelef.remote_capture)
+* [EQtive](https://play.google.com/store/apps/details?id=com.vekanto.eqtive)
+* [PocketMark - MarkDown Editor](https://play.google.com/store/apps/details?id=com.ZetaDev.PocketMark)
+* [AIRSOFT SPOTTER](https://play.google.com/store/apps/details?id=io.github.precisionmarks.spotter)
+
diff --git a/appintro/build.gradle b/appintro/build.gradle
deleted file mode 100644
index c05af0723..000000000
--- a/appintro/build.gradle
+++ /dev/null
@@ -1,65 +0,0 @@
-apply plugin: 'com.android.library'
-apply plugin: 'kotlin-android'
-apply plugin: 'com.github.dcendents.android-maven'
-apply plugin: 'io.gitlab.arturbosch.detekt'
-apply plugin: 'org.jlleitschuh.gradle.ktlint'
-
-group = 'com.github.AppIntro'
-
-android {
- compileSdkVersion 29
-
- buildToolsVersion "29.0.2"
- defaultConfig {
- minSdkVersion 14
- targetSdkVersion 29
- versionCode 20
- versionName "6.0.0"
-
- consumerProguardFiles 'consumer-proguard-rules.pro'
- vectorDrawables.useSupportLibrary = true
- }
-}
-
-dependencies {
- implementation 'androidx.appcompat:appcompat:1.1.0'
- implementation 'androidx.annotation:annotation:1.1.0'
- implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
-
- testImplementation 'junit:junit:4.13'
- testImplementation 'org.mockito:mockito-core:2.28.2'
- implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
-}
-
-repositories {
- mavenCentral()
-}
-
-ktlint {
- version = "0.36.0"
- debug = false
- verbose = true
- android = false
- outputToConsole = true
- reporters {
- reporter "json"
- }
- ignoreFailures = false
- enableExperimentalRules = true
- kotlinScriptAdditionalPaths {
- include fileTree("scripts/")
- }
- filter {
- exclude("**/generated/**")
- include("**/kotlin/**")
- }
-}
-
-detekt {
- config = files("./detekt-config.yml")
- input = files("src/main/java")
-}
-
-tasks.withType(io.gitlab.arturbosch.detekt.Detekt) {
- exclude("**/resources/**,**/build/**")
-}
diff --git a/appintro/build.gradle.kts b/appintro/build.gradle.kts
new file mode 100644
index 000000000..6910f8cf3
--- /dev/null
+++ b/appintro/build.gradle.kts
@@ -0,0 +1,116 @@
+plugins {
+ alias(libs.plugins.library)
+ alias(libs.plugins.kotlin.android)
+ alias(libs.plugins.detekt)
+ alias(libs.plugins.ktlint)
+ id("maven-publish")
+}
+
+group = "com.github.AppIntro"
+version = "7.0.0-beta02"
+
+android {
+ namespace = "com.github.appintro"
+ compileSdk = libs.versions.compile.sdk.version.get().toInt()
+
+ defaultConfig {
+ minSdk = libs.versions.min.sdk.version.get().toInt()
+
+ consumerProguardFiles("consumer-proguard-rules.pro")
+ vectorDrawables.useSupportLibrary = true
+ }
+ publishing {
+ singleVariant("release") {
+ withSourcesJar()
+ withJavadocJar()
+ }
+ }
+ lint {
+ warningsAsErrors = true
+ abortOnError = true
+ lint {
+ disable.addAll(
+ listOf(
+ "MissingTranslation",
+ "OldTargetApi",
+ "GradleDependency",
+ ),
+ )
+ }
+ }
+ compileOptions {
+ sourceCompatibility = JavaVersion.VERSION_11
+ targetCompatibility = JavaVersion.VERSION_11
+ }
+ kotlinOptions {
+ jvmTarget = JavaVersion.VERSION_11.toString()
+ }
+}
+
+dependencies {
+ implementation(libs.androidx.appcompat)
+ implementation(libs.androidx.annotation)
+ implementation(libs.androidx.constraintlayout)
+ implementation(libs.androidx.viewpager2)
+ implementation(libs.androidx.fragment)
+
+ testImplementation(libs.junit)
+ testImplementation(libs.mockito.core)
+}
+
+ktlint {
+ debug.set(false)
+ verbose.set(true)
+ android.set(false)
+ outputToConsole.set(true)
+ ignoreFailures.set(false)
+}
+
+detekt {
+ config.setFrom(files("./detekt-config.yml"))
+}
+
+val pomName: String by project
+val pomDescription: String by project
+val pomLicenseName: String by project
+val pomLicenseUrl: String by project
+val pomScmConnection: String by project
+val pomUrl: String by project
+
+publishing {
+ publications {
+ register("release") {
+ afterEvaluate {
+ from(components["release"])
+ }
+ pom {
+ name.set(pomName)
+ description.set(pomDescription)
+ url.set(pomUrl)
+ licenses {
+ license {
+ name.set(pomLicenseName)
+ url.set(pomLicenseUrl)
+ }
+ }
+ scm {
+ connection.set(pomScmConnection)
+ developerConnection.set(pomScmConnection)
+ url.set(pomUrl)
+ }
+ developers {
+ developer {
+ id.set("paolorotolo")
+ name.set("Paolo Rotolo")
+ email.set("paolo@rotolo.dev")
+ }
+ developer {
+ id.set("cortinico")
+ name.set("Nicola Corti")
+ email.set("corti.nico@gmail.com")
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/appintro/detekt-config.yml b/appintro/detekt-config.yml
index 93588c81d..cae1c2b9e 100644
--- a/appintro/detekt-config.yml
+++ b/appintro/detekt-config.yml
@@ -1,5 +1,5 @@
build:
- maxIssues: 1
+ maxIssues: 0
excludeCorrectable: false
weights:
# complexity: 2
@@ -9,52 +9,92 @@ build:
config:
validation: true
- # when writing own rules with new properties, exclude the property path e.g.: "my_rule_set,.*>.*>[my_property]"
- excludes: ""
+ warningsAsErrors: false
+ checkExhaustiveness: false
+ # when writing own rules with new properties, exclude the property path e.g.: 'my_rule_set,.*>.*>[my_property]'
+ excludes: ''
processors:
active: true
exclude:
- # - 'DetektProgressListener'
+ - 'DetektProgressListener'
+ # - 'KtFileCountProcessor'
+ # - 'PackageCountProcessor'
+ # - 'ClassCountProcessor'
# - 'FunctionCountProcessor'
# - 'PropertyCountProcessor'
- # - 'ClassCountProcessor'
- # - 'PackageCountProcessor'
- # - 'KtFileCountProcessor'
+ # - 'ProjectComplexityProcessor'
+ # - 'ProjectCognitiveComplexityProcessor'
+ # - 'ProjectLLOCProcessor'
+ # - 'ProjectCLOCProcessor'
+ # - 'ProjectLOCProcessor'
+ # - 'ProjectSLOCProcessor'
+ # - 'LicenseHeaderLoaderExtension'
console-reports:
active: true
exclude:
- # - 'ProjectStatisticsReport'
- # - 'ComplexityReport'
- # - 'NotificationReport'
- # - 'FindingsReport'
+ - 'ProjectStatisticsReport'
+ - 'ComplexityReport'
+ - 'NotificationReport'
+ - 'FindingsReport'
- 'FileBasedFindingsReport'
- # - 'BuildFailureReport'
+ # - 'LiteFindingsReport'
+
+output-reports:
+ active: true
+ exclude:
+ # - 'TxtOutputReport'
+ # - 'XmlOutputReport'
+ # - 'HtmlOutputReport'
+ # - 'MdOutputReport'
+ # - 'SarifOutputReport'
comments:
active: true
- excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt"
+ AbsentOrWrongFileLicense:
+ active: false
+ licenseTemplateFile: 'license.template'
+ licenseTemplateIsRegex: false
CommentOverPrivateFunction:
active: false
CommentOverPrivateProperty:
active: false
+ DeprecatedBlockTag:
+ active: false
EndOfSentenceFormat:
active: false
- endOfSentenceFormat: ([.?!][ \t\n\r\f<])|([.?!:]$)
+ endOfSentenceFormat: '([.?!][ \t\n\r\f<])|([.?!:]$)'
+ KDocReferencesNonPublicProperty:
+ active: false
+ excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**']
+ OutdatedDocumentation:
+ active: false
+ matchTypeParameters: true
+ matchDeclarationsOrder: true
+ allowParamOnConstructorProperties: false
UndocumentedPublicClass:
active: false
+ excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**']
searchInNestedClass: true
searchInInnerClass: true
searchInInnerObject: true
searchInInnerInterface: true
+ searchInProtectedClass: false
UndocumentedPublicFunction:
active: false
+ excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**']
+ searchProtectedFunction: false
UndocumentedPublicProperty:
active: false
+ excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**']
+ searchProtectedProperty: false
complexity:
active: true
+ CognitiveComplexMethod:
+ active: false
+ threshold: 15
ComplexCondition:
active: true
threshold: 4
@@ -62,16 +102,27 @@ complexity:
active: false
threshold: 10
includeStaticDeclarations: false
- ComplexMethod:
+ includePrivateDeclarations: false
+ ignoreOverloaded: false
+ CyclomaticComplexMethod:
active: true
threshold: 15
ignoreSingleWhenExpression: false
ignoreSimpleWhenEntries: false
ignoreNestingFunctions: false
- nestingFunctions: run,let,apply,with,also,use,forEach,isNotNull,ifNull
+ nestingFunctions:
+ - 'also'
+ - 'apply'
+ - 'forEach'
+ - 'isNotNull'
+ - 'ifNull'
+ - 'let'
+ - 'run'
+ - 'use'
+ - 'with'
LabeledExpression:
active: false
- ignoredLabels: ""
+ ignoredLabels: []
LargeClass:
active: true
threshold: 600
@@ -80,29 +131,47 @@ complexity:
threshold: 60
LongParameterList:
active: true
- threshold: 6
+ functionThreshold: 6
+ constructorThreshold: 7
ignoreDefaultParameters: false
+ ignoreDataClasses: true
+ ignoreAnnotatedParameter: []
MethodOverloading:
active: false
threshold: 6
+ NamedArguments:
+ active: false
+ threshold: 3
+ ignoreArgumentsMatchingNames: false
NestedBlockDepth:
active: true
threshold: 4
+ NestedScopeFunctions:
+ active: false
+ threshold: 1
+ functions:
+ - 'kotlin.apply'
+ - 'kotlin.run'
+ - 'kotlin.with'
+ - 'kotlin.let'
+ - 'kotlin.also'
+ ReplaceSafeCallChainWithRun:
+ active: false
StringLiteralDuplication:
active: false
- excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt"
+ excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**']
threshold: 3
ignoreAnnotation: true
excludeStringsWithLessThan5Characters: true
ignoreStringsRegex: '$^'
TooManyFunctions:
active: true
- excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt"
+ excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**']
thresholdInFiles: 21
thresholdInClasses: 21
- thresholdInInterfaces: 11
- thresholdInObjects: 11
- thresholdInEnums: 11
+ thresholdInInterfaces: 21
+ thresholdInObjects: 21
+ thresholdInEnums: 21
ignoreDeprecated: false
ignorePrivate: false
ignoreOverridden: false
@@ -111,14 +180,28 @@ coroutines:
active: true
GlobalCoroutineUsage:
active: false
+ InjectDispatcher:
+ active: true
+ dispatcherNames:
+ - 'IO'
+ - 'Default'
+ - 'Unconfined'
RedundantSuspendModifier:
+ active: true
+ SleepInsteadOfDelay:
+ active: true
+ SuspendFunSwallowedCancellation:
+ active: false
+ SuspendFunWithCoroutineScopeReceiver:
active: false
+ SuspendFunWithFlowReturnType:
+ active: true
empty-blocks:
active: true
EmptyCatchBlock:
active: true
- allowedExceptionNameRegex: "^(_|(ignore|expected).*)"
+ allowedExceptionNameRegex: '_|(ignore|expected).*'
EmptyClassBlock:
active: true
EmptyDefaultConstructor:
@@ -133,7 +216,7 @@ empty-blocks:
active: true
EmptyFunctionBlock:
active: true
- ignoreOverridden: true
+ ignoreOverridden: false
EmptyIfBlock:
active: true
EmptyInitBlock:
@@ -142,6 +225,8 @@ empty-blocks:
active: true
EmptySecondaryConstructor:
active: true
+ EmptyTryBlock:
+ active: true
EmptyWhenBlock:
active: true
EmptyWhileBlock:
@@ -150,272 +235,227 @@ empty-blocks:
exceptions:
active: true
ExceptionRaisedInUnexpectedLocation:
- active: false
- methodNames: 'toString,hashCode,equals,finalize'
+ active: true
+ methodNames:
+ - 'equals'
+ - 'finalize'
+ - 'hashCode'
+ - 'toString'
InstanceOfCheckForException:
- active: false
- excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt"
+ active: true
+ excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**']
NotImplementedDeclaration:
active: false
- PrintStackTrace:
+ ObjectExtendsThrowable:
active: false
+ PrintStackTrace:
+ active: true
RethrowCaughtException:
- active: false
+ active: true
ReturnFromFinally:
- active: false
+ active: true
ignoreLabeled: false
SwallowedException:
- active: false
- ignoredExceptionTypes: 'InterruptedException,NumberFormatException,ParseException,MalformedURLException'
- allowedExceptionNameRegex: "^(_|(ignore|expected).*)"
+ active: true
+ ignoredExceptionTypes:
+ - 'InterruptedException'
+ - 'MalformedURLException'
+ - 'NumberFormatException'
+ - 'ParseException'
+ allowedExceptionNameRegex: '_|(ignore|expected).*'
ThrowingExceptionFromFinally:
- active: false
+ active: true
ThrowingExceptionInMain:
active: false
ThrowingExceptionsWithoutMessageOrCause:
- active: false
- exceptions: 'IllegalArgumentException,IllegalStateException,IOException'
+ active: true
+ excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**']
+ exceptions:
+ - 'ArrayIndexOutOfBoundsException'
+ - 'Exception'
+ - 'IllegalArgumentException'
+ - 'IllegalMonitorStateException'
+ - 'IllegalStateException'
+ - 'IndexOutOfBoundsException'
+ - 'NullPointerException'
+ - 'RuntimeException'
+ - 'Throwable'
ThrowingNewInstanceOfSameException:
- active: false
+ active: true
TooGenericExceptionCaught:
active: true
- excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt"
+ excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**']
exceptionNames:
- - ArrayIndexOutOfBoundsException
- - Error
- - Exception
- - IllegalMonitorStateException
- - NullPointerException
- - IndexOutOfBoundsException
- - RuntimeException
- - Throwable
- allowedExceptionNameRegex: "^(_|(ignore|expected).*)"
+ - 'ArrayIndexOutOfBoundsException'
+ - 'Error'
+ - 'Exception'
+ - 'IllegalMonitorStateException'
+ - 'IndexOutOfBoundsException'
+ - 'NullPointerException'
+ - 'RuntimeException'
+ - 'Throwable'
+ allowedExceptionNameRegex: '_|(ignore|expected).*'
TooGenericExceptionThrown:
active: true
exceptionNames:
- - Error
- - Exception
- - Throwable
- - RuntimeException
-
-formatting:
- active: true
- android: false
- autoCorrect: true
- AnnotationOnSeparateLine:
- active: false
- autoCorrect: true
- ChainWrapping:
- active: true
- autoCorrect: true
- CommentSpacing:
- active: true
- autoCorrect: true
- EnumEntryNameCase:
- active: false
- autoCorrect: true
- Filename:
- active: true
- FinalNewline:
- active: true
- autoCorrect: true
- ImportOrdering:
- active: false
- autoCorrect: true
- Indentation:
- active: false
- autoCorrect: true
- indentSize: 4
- continuationIndentSize: 4
- MaximumLineLength:
- active: true
- maxLineLength: 120
- ModifierOrdering:
- active: true
- autoCorrect: true
- MultiLineIfElse:
- active: true
- autoCorrect: true
- NoBlankLineBeforeRbrace:
- active: true
- autoCorrect: true
- NoConsecutiveBlankLines:
- active: true
- autoCorrect: true
- NoEmptyClassBody:
- active: true
- autoCorrect: true
- NoEmptyFirstLineInMethodBlock:
- active: false
- autoCorrect: true
- NoLineBreakAfterElse:
- active: true
- autoCorrect: true
- NoLineBreakBeforeAssignment:
- active: true
- autoCorrect: true
- NoMultipleSpaces:
- active: true
- autoCorrect: true
- NoSemicolons:
- active: true
- autoCorrect: true
- NoTrailingSpaces:
- active: true
- autoCorrect: true
- NoUnitReturn:
- active: true
- autoCorrect: true
- NoUnusedImports:
- active: true
- autoCorrect: true
- NoWildcardImports:
- active: true
- PackageName:
- active: true
- autoCorrect: true
- ParameterListWrapping:
- active: true
- autoCorrect: true
- indentSize: 4
- SpacingAroundColon:
- active: true
- autoCorrect: true
- SpacingAroundComma:
- active: true
- autoCorrect: true
- SpacingAroundCurly:
- active: true
- autoCorrect: true
- SpacingAroundDot:
- active: true
- autoCorrect: true
- SpacingAroundKeyword:
- active: true
- autoCorrect: true
- SpacingAroundOperators:
- active: true
- autoCorrect: true
- SpacingAroundParens:
- active: true
- autoCorrect: true
- SpacingAroundRangeOperator:
- active: true
- autoCorrect: true
- StringTemplate:
- active: true
- autoCorrect: true
+ - 'Error'
+ - 'Exception'
+ - 'RuntimeException'
+ - 'Throwable'
naming:
active: true
+ BooleanPropertyNaming:
+ active: false
+ allowedPattern: '^(is|has|are)'
ClassNaming:
active: true
- excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt"
- classPattern: '[A-Z$][a-zA-Z0-9$]*'
+ classPattern: '[A-Z][a-zA-Z0-9]*'
ConstructorParameterNaming:
active: true
- excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt"
parameterPattern: '[a-z][A-Za-z0-9]*'
privateParameterPattern: '[a-z][A-Za-z0-9]*'
excludeClassPattern: '$^'
- ignoreOverridden: true
EnumNaming:
active: true
- excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt"
- enumEntryPattern: '^[A-Z][_a-zA-Z0-9]*'
+ enumEntryPattern: '[A-Z][_a-zA-Z0-9]*'
ForbiddenClassName:
active: false
- excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt"
- forbiddenName: ''
+ forbiddenName: []
FunctionMaxLength:
active: false
- excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt"
maximumFunctionNameLength: 30
FunctionMinLength:
active: false
- excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt"
minimumFunctionNameLength: 3
FunctionNaming:
active: true
- excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt"
- functionPattern: '^([a-z$][a-zA-Z$0-9]*)|(`.*`)$'
+ excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**']
+ functionPattern: '[a-z][a-zA-Z0-9]*'
excludeClassPattern: '$^'
- ignoreOverridden: true
FunctionParameterNaming:
active: true
- excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt"
parameterPattern: '[a-z][A-Za-z0-9]*'
excludeClassPattern: '$^'
- ignoreOverridden: true
InvalidPackageDeclaration:
- active: false
+ active: true
rootPackage: ''
+ requireRootInDeclaration: false
+ LambdaParameterNaming:
+ active: false
+ parameterPattern: '[a-z][A-Za-z0-9]*|_'
MatchingDeclarationName:
active: true
+ mustBeFirst: true
MemberNameEqualsClassName:
active: true
ignoreOverridden: true
+ NoNameShadowing:
+ active: true
+ NonBooleanPropertyPrefixedWithIs:
+ active: false
ObjectPropertyNaming:
active: true
- excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt"
constantPattern: '[A-Za-z][_A-Za-z0-9]*'
propertyPattern: '[A-Za-z][_A-Za-z0-9]*'
privatePropertyPattern: '(_)?[A-Za-z][_A-Za-z0-9]*'
PackageNaming:
active: true
- excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt"
- packagePattern: '^[a-z]+(\.[a-z][A-Za-z0-9]*)*$'
+ packagePattern: '[a-z]+(\.[a-z][A-Za-z0-9]*)*'
TopLevelPropertyNaming:
active: true
- excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt"
constantPattern: '[A-Z][_A-Z0-9]*'
propertyPattern: '[A-Za-z][_A-Za-z0-9]*'
privatePropertyPattern: '_?[A-Za-z][_A-Za-z0-9]*'
VariableMaxLength:
active: false
- excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt"
maximumVariableNameLength: 64
VariableMinLength:
active: false
- excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt"
minimumVariableNameLength: 1
VariableNaming:
active: true
- excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt"
variablePattern: '[a-z][A-Za-z0-9]*'
privateVariablePattern: '(_)?[a-z][A-Za-z0-9]*'
excludeClassPattern: '$^'
- ignoreOverridden: true
performance:
active: true
ArrayPrimitive:
active: true
+ CouldBeSequence:
+ active: false
+ threshold: 3
ForEachOnRange:
active: true
- excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt"
+ excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**']
SpreadOperator:
active: true
- excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt"
+ excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**']
+ UnnecessaryPartOfBinaryExpression:
+ active: false
UnnecessaryTemporaryInstantiation:
active: true
potential-bugs:
active: true
+ AvoidReferentialEquality:
+ active: true
+ forbiddenTypePatterns:
+ - 'kotlin.String'
+ CastNullableToNonNullableType:
+ active: false
+ CastToNullableType:
+ active: false
Deprecation:
active: false
- DuplicateCaseInWhenExpression:
+ DontDowncastCollectionTypes:
+ active: false
+ DoubleMutabilityForCollection:
active: true
+ mutableTypes:
+ - 'kotlin.collections.MutableList'
+ - 'kotlin.collections.MutableMap'
+ - 'kotlin.collections.MutableSet'
+ - 'java.util.ArrayList'
+ - 'java.util.LinkedHashSet'
+ - 'java.util.HashSet'
+ - 'java.util.LinkedHashMap'
+ - 'java.util.HashMap'
+ ElseCaseInsteadOfExhaustiveWhen:
+ active: false
+ ignoredSubjectTypes: []
EqualsAlwaysReturnsTrueOrFalse:
active: true
EqualsWithHashCodeExist:
active: true
+ ExitOutsideMain:
+ active: false
ExplicitGarbageCollectionCall:
active: true
HasPlatformType:
- active: false
+ active: true
+ IgnoredReturnValue:
+ active: true
+ restrictToConfig: true
+ returnValueAnnotations:
+ - 'CheckResult'
+ - '*.CheckResult'
+ - 'CheckReturnValue'
+ - '*.CheckReturnValue'
+ ignoreReturnValueAnnotations:
+ - 'CanIgnoreReturnValue'
+ - '*.CanIgnoreReturnValue'
+ returnValueTypes:
+ - 'kotlin.sequences.Sequence'
+ - 'kotlinx.coroutines.flow.*Flow'
+ - 'java.util.stream.*Stream'
+ ignoreFunctionCall: []
ImplicitDefaultLocale:
+ active: true
+ ImplicitUnitReturnType:
active: false
+ allowExplicitReturnType: true
InvalidRange:
active: true
IteratorHasNextCallsNextMethod:
@@ -424,80 +464,156 @@ potential-bugs:
active: true
LateinitUsage:
active: false
- excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt"
- excludeAnnotatedProperties: ""
- ignoreOnClassesPattern: ""
+ excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**']
+ ignoreOnClassesPattern: ''
MapGetWithNotNullAssertionOperator:
- active: false
- MissingWhenCase:
- active: true
- RedundantElseInWhen:
active: true
+ MissingPackageDeclaration:
+ active: false
+ excludes: ['**/*.kts']
+ NullCheckOnMutableProperty:
+ active: false
+ NullableToStringCall:
+ active: false
+ PropertyUsedBeforeDeclaration:
+ active: false
UnconditionalJumpStatementInLoop:
active: false
+ UnnecessaryNotNullCheck:
+ active: false
+ UnnecessaryNotNullOperator:
+ active: true
+ UnnecessarySafeCall:
+ active: true
+ UnreachableCatchBlock:
+ active: true
UnreachableCode:
active: true
UnsafeCallOnNullableType:
active: true
+ excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**']
UnsafeCast:
- active: false
+ active: true
+ UnusedUnaryOperator:
+ active: true
UselessPostfixExpression:
- active: false
+ active: true
WrongEqualsTypeParameter:
active: true
style:
active: true
+ AlsoCouldBeApply:
+ active: false
+ BracesOnIfStatements:
+ active: false
+ singleLine: 'never'
+ multiLine: 'always'
+ BracesOnWhenStatements:
+ active: false
+ singleLine: 'necessary'
+ multiLine: 'consistent'
+ CanBeNonNullable:
+ active: false
+ CascadingCallWrapping:
+ active: false
+ includeElvis: true
+ ClassOrdering:
+ active: false
CollapsibleIfStatements:
active: false
DataClassContainsFunctions:
active: false
- conversionFunctionPrefix: 'to'
+ conversionFunctionPrefix:
+ - 'to'
+ allowOperators: false
DataClassShouldBeImmutable:
active: false
+ DestructuringDeclarationWithTooManyEntries:
+ active: true
+ maxDestructuringEntries: 3
+ DoubleNegativeLambda:
+ active: false
+ negativeFunctions:
+ - reason: 'Use `takeIf` instead.'
+ value: 'takeUnless'
+ - reason: 'Use `all` instead.'
+ value: 'none'
+ negativeFunctionNameParts:
+ - 'not'
+ - 'non'
EqualsNullCall:
active: true
EqualsOnSignatureLine:
active: false
-# ExplicitCollectionElementAccessMethod:
-# active: false
- ExplicitItLambdaParameter:
+ ExplicitCollectionElementAccessMethod:
active: false
+ ExplicitItLambdaParameter:
+ active: true
ExpressionBodySyntax:
active: false
includeLineWrapping: false
+ ForbiddenAnnotation:
+ active: false
+ annotations:
+ - reason: 'it is a java annotation. Use `Suppress` instead.'
+ value: 'java.lang.SuppressWarnings'
+ - reason: 'it is a java annotation. Use `kotlin.Deprecated` instead.'
+ value: 'java.lang.Deprecated'
+ - reason: 'it is a java annotation. Use `kotlin.annotation.MustBeDocumented` instead.'
+ value: 'java.lang.annotation.Documented'
+ - reason: 'it is a java annotation. Use `kotlin.annotation.Target` instead.'
+ value: 'java.lang.annotation.Target'
+ - reason: 'it is a java annotation. Use `kotlin.annotation.Retention` instead.'
+ value: 'java.lang.annotation.Retention'
+ - reason: 'it is a java annotation. Use `kotlin.annotation.Repeatable` instead.'
+ value: 'java.lang.annotation.Repeatable'
+ - reason: 'Kotlin does not support @Inherited annotation, see https://youtrack.jetbrains.com/issue/KT-22265'
+ value: 'java.lang.annotation.Inherited'
ForbiddenComment:
active: true
- values: 'TODO:,FIXME:,STOPSHIP:'
- allowedPatterns: ""
+ comments:
+ - reason: 'Forbidden FIXME todo marker in comment, please fix the problem.'
+ value: 'FIXME:'
+ - reason: 'Forbidden STOPSHIP todo marker in comment, please address the problem before shipping the code.'
+ value: 'STOPSHIP:'
+ - reason: 'Forbidden TODO todo marker in comment, please do the changes.'
+ value: 'TODO:'
+ allowedPatterns: ''
ForbiddenImport:
active: false
- imports: ''
- forbiddenPatterns: ""
+ imports: []
+ forbiddenPatterns: ''
ForbiddenMethodCall:
active: false
- methods: ''
- ForbiddenPublicDataClass:
+ methods:
+ - reason: 'print does not allow you to configure the output stream. Use a logger instead.'
+ value: 'kotlin.io.print'
+ - reason: 'println does not allow you to configure the output stream. Use a logger instead.'
+ value: 'kotlin.io.println'
+ ForbiddenSuppress:
active: false
- ignorePackages: '*.internal,*.internal.*'
+ rules: []
ForbiddenVoid:
- active: false
+ active: true
ignoreOverridden: false
ignoreUsageInGenerics: false
FunctionOnlyReturningConstant:
active: true
ignoreOverridableFunction: true
- excludedFunctions: 'describeContents'
- excludeAnnotatedFunction: "dagger.Provides"
- LibraryCodeMustSpecifyReturnType:
- active: true
+ ignoreActualFunction: true
+ excludedFunctions: []
LoopWithTooManyJumpStatements:
active: true
maxJumpCount: 1
MagicNumber:
active: true
- excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt"
- ignoreNumbers: '-1,0,1,2'
+ excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**', '**/*.kts']
+ ignoreNumbers:
+ - '-1'
+ - '0'
+ - '1'
+ - '2'
ignoreHashCodeFunction: true
ignorePropertyDeclaration: false
ignoreLocalVariableDeclaration: false
@@ -507,24 +623,41 @@ style:
ignoreNamedArgument: true
ignoreEnums: false
ignoreRanges: false
- MandatoryBracesIfStatements:
+ ignoreExtensionFunctions: true
+ MandatoryBracesLoops:
+ active: false
+ MaxChainedCallsOnSameLine:
active: false
+ maxChainedCalls: 5
MaxLineLength:
active: true
maxLineLength: 120
excludePackageStatements: true
excludeImportStatements: true
excludeCommentStatements: false
+ excludeRawStrings: true
MayBeConst:
active: true
ModifierOrder:
active: true
- NestedClassesVisibility:
+ MultilineLambdaItParameter:
+ active: false
+ MultilineRawStringIndentation:
active: false
+ indentSize: 4
+ trimmingMethods:
+ - 'trimIndent'
+ - 'trimMargin'
+ NestedClassesVisibility:
+ active: true
NewLineAtEndOfFile:
active: true
NoTabs:
active: false
+ NullableBooleanCheck:
+ active: false
+ ObjectLiteralToLambda:
+ active: true
OptionalAbstractKeyword:
active: true
OptionalUnit:
@@ -537,71 +670,117 @@ style:
active: true
RedundantExplicitType:
active: false
+ RedundantHigherOrderMapUsage:
+ active: true
RedundantVisibilityModifierRule:
active: false
ReturnCount:
active: true
max: 5
- excludedFunctions: "equals"
+ excludedFunctions:
+ - 'equals'
excludeLabeled: false
excludeReturnFromLambda: true
excludeGuardClauses: false
SafeCast:
active: true
SerialVersionUIDInSerializableClass:
- active: false
+ active: true
SpacingBetweenPackageAndImports:
active: false
+ StringShouldBeRawString:
+ active: false
+ maxEscapedCharacterCount: 2
+ ignoredCharacters: []
ThrowsCount:
active: true
max: 2
+ excludeGuardClauses: false
TrailingWhitespace:
active: false
+ TrimMultilineRawString:
+ active: false
+ trimmingMethods:
+ - 'trimIndent'
+ - 'trimMargin'
UnderscoresInNumericLiterals:
active: false
- acceptableDecimalLength: 5
+ acceptableLength: 4
+ allowNonStandardGrouping: false
UnnecessaryAbstractClass:
active: true
- excludeAnnotatedClasses: "dagger.Module"
UnnecessaryAnnotationUseSiteTarget:
active: false
UnnecessaryApply:
+ active: true
+ UnnecessaryBackticks:
active: false
+ UnnecessaryBracesAroundTrailingLambda:
+ active: false
+ UnnecessaryFilter:
+ active: true
UnnecessaryInheritance:
active: true
+ UnnecessaryInnerClass:
+ active: false
UnnecessaryLet:
active: false
UnnecessaryParentheses:
active: false
+ allowForUnclearPrecedence: false
UntilInsteadOfRangeTo:
active: false
UnusedImports:
active: false
+ UnusedParameter:
+ active: true
+ allowedNames: 'ignored|expected'
UnusedPrivateClass:
active: true
UnusedPrivateMember:
- active: false
- allowedNames: "(_|ignored|expected|serialVersionUID)"
+ active: true
+ allowedNames: ''
+ UnusedPrivateProperty:
+ active: true
+ allowedNames: '_|ignored|expected|serialVersionUID'
+ UseAnyOrNoneInsteadOfFind:
+ active: true
UseArrayLiteralsInAnnotations:
- active: false
+ active: true
+ UseCheckNotNull:
+ active: true
UseCheckOrError:
- active: false
+ active: true
UseDataClass:
active: false
- excludeAnnotatedClasses: ""
allowVars: false
+ UseEmptyCounterpart:
+ active: false
+ UseIfEmptyOrIfBlank:
+ active: false
UseIfInsteadOfWhen:
active: false
+ ignoreWhenContainingVariableDeclaration: false
+ UseIsNullOrEmpty:
+ active: true
+ UseLet:
+ active: false
+ UseOrEmpty:
+ active: true
UseRequire:
+ active: true
+ UseRequireNotNull:
+ active: true
+ UseSumOfInsteadOfFlatMapSize:
active: false
UselessCallOnNotNull:
active: true
UtilityClassWithPublicConstructor:
active: true
VarCouldBeVal:
- active: false
+ active: true
+ ignoreLateinitVar: false
WildcardImport:
active: true
- excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt"
- excludeImports: 'java.util.*,kotlinx.android.synthetic.*'
-
+ excludeImports:
+ - 'java.util.*'
diff --git a/appintro/gradle.properties b/appintro/gradle.properties
index 51a4032e7..3944b82e4 100644
--- a/appintro/gradle.properties
+++ b/appintro/gradle.properties
@@ -1,17 +1,9 @@
-POM_NAME=AppIntro
-POM_ARTIFACT_ID=appintro
-POM_PACKAGING=aar
-VERSION_NAME=4.1.0
-VERSION_CODE=13
-GROUP=com.github.appintro
-
-POM_DESCRIPTION=AppIntro library
-POM_URL=https://github.com/PaoloRotolo/AppIntro
-POM_SCM_URL=https://github.com/PaoloRotolo/AppIntro
-POM_SCM_CONNECTION=git@github.com:PaoloRotolo/AppIntro.git
-POM_SCM_DEV_CONNECTION=git@github.com:PaoloRotolo/AppIntro.git
-POM_LICENCE_NAME=The Apache Software License, Version 2.0
-POM_LICENCE_URL=http://www.apache.org/licenses/LICENSE-2.0.txt
-POM_LICENCE_DIST=repo
-POM_DEVELOPER_ID=paolorotolo
-POM_DEVELOPER_NAME=paolorotolo
+pomArtifactId=appintro
+pomName=AppIntro
+pomDescription=Make a cool intro for your Android app.
+pomUrl=git://github.com/AppIntro/AppIntro.git
+pomScmUrl=https://github.com/AppIntro/AppIntro
+pomScmConnection=scm:git://github.com/AppIntro/AppIntro.git
+pomScmDevConnection=scm:git://github.com/AppIntro/AppIntro.git
+pomLicenseName=The Apache Software License, Version 2.0
+pomLicenseUrl=http://www.apache.org/licenses/LICENSE-2.0.txt
diff --git a/appintro/src/main/AndroidManifest.xml b/appintro/src/main/AndroidManifest.xml
deleted file mode 100644
index 37e06c299..000000000
--- a/appintro/src/main/AndroidManifest.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
-
diff --git a/appintro/src/main/java/com/github/appintro/AppIntro.kt b/appintro/src/main/java/com/github/appintro/AppIntro.kt
index 559b31002..9b116ede5 100644
--- a/appintro/src/main/java/com/github/appintro/AppIntro.kt
+++ b/appintro/src/main/java/com/github/appintro/AppIntro.kt
@@ -2,23 +2,27 @@ package com.github.appintro
import android.graphics.drawable.Drawable
import android.view.View
+import android.view.ViewGroup
import android.widget.ImageButton
import android.widget.ImageView
import android.widget.TextView
import androidx.annotation.ColorInt
import androidx.annotation.FontRes
import androidx.annotation.StringRes
+import androidx.annotation.StyleRes
+import androidx.core.widget.TextViewCompat
import com.github.appintro.internal.TypefaceContainer
abstract class AppIntro : AppIntroBase() {
-
override val layoutId = R.layout.appintro_intro_layout
/**
* Override viewpager bar color
* @param color your color resource
*/
- fun setBarColor(@ColorInt color: Int) {
+ fun setBarColor(
+ @ColorInt color: Int,
+ ) {
val bottomBar = findViewById(R.id.bottom)
bottomBar.setBackgroundColor(color)
}
@@ -28,17 +32,33 @@ abstract class AppIntro : AppIntroBase() {
*
* @param color your color
*/
- fun setNextArrowColor(@ColorInt color: Int) {
+ fun setNextArrowColor(
+ @ColorInt color: Int,
+ ) {
val nextButton = findViewById(R.id.next)
nextButton.setColorFilter(color)
}
+ /**
+ * Override back button arrow color
+ *
+ * @param color your color
+ */
+ fun setBackArrowColor(
+ @ColorInt color: Int,
+ ) {
+ val backButton = findViewById(R.id.back)
+ backButton.setColorFilter(color)
+ }
+
/**
* Override separator color
*
* @param color your color resource
*/
- fun setSeparatorColor(@ColorInt color: Int) {
+ fun setSeparatorColor(
+ @ColorInt color: Int,
+ ) {
val separator = findViewById(R.id.bottom_separator)
separator.setBackgroundColor(color)
}
@@ -58,7 +78,9 @@ abstract class AppIntro : AppIntroBase() {
*
* @param skipResId your text resource Id
*/
- fun setSkipText(@StringRes skipResId: Int) {
+ fun setSkipText(
+ @StringRes skipResId: Int,
+ ) {
val skipText = findViewById(R.id.skip)
skipText.setText(skipResId)
}
@@ -68,7 +90,9 @@ abstract class AppIntro : AppIntroBase() {
*
* @param typeface the typeface to apply to Skip button
*/
- fun setSkipTextTypeface(@FontRes typeface: Int) {
+ fun setSkipTextTypeface(
+ @FontRes typeface: Int,
+ ) {
val view = findViewById(R.id.skip)
TypefaceContainer(null, typeface).applyTo(view)
}
@@ -98,7 +122,9 @@ abstract class AppIntro : AppIntroBase() {
*
* @param doneResId your text resource Id
*/
- fun setDoneText(@StringRes doneResId: Int) {
+ fun setDoneText(
+ @StringRes doneResId: Int,
+ ) {
val doneText = findViewById(R.id.done)
doneText.setText(doneResId)
}
@@ -118,7 +144,9 @@ abstract class AppIntro : AppIntroBase() {
*
* @param typeface the typeface to apply to Done button
*/
- fun setDoneTextTypeface(@FontRes typeface: Int) {
+ fun setDoneTextTypeface(
+ @FontRes typeface: Int,
+ ) {
val view = findViewById(R.id.done)
TypefaceContainer(null, typeface).applyTo(view)
}
@@ -128,21 +156,49 @@ abstract class AppIntro : AppIntroBase() {
*
* @param colorDoneText your color resource
*/
- fun setColorDoneText(@ColorInt colorDoneText: Int) {
+ fun setColorDoneText(
+ @ColorInt colorDoneText: Int,
+ ) {
val doneText = findViewById(R.id.done)
doneText.setTextColor(colorDoneText)
}
+ /**
+ * Override done button text overall style
+ *
+ * @param textAppearance your text style from resource
+ */
+ fun setDoneTextAppearance(
+ @StyleRes textAppearance: Int,
+ ) {
+ val doneText = findViewById(R.id.done)
+ TextViewCompat.setTextAppearance(doneText, textAppearance)
+ }
+
/**
* Override skip button color
*
* @param colorSkipButton your color resource
*/
- fun setColorSkipButton(@ColorInt colorSkipButton: Int) {
+ fun setColorSkipButton(
+ @ColorInt colorSkipButton: Int,
+ ) {
val skip = findViewById(R.id.skip)
skip.setTextColor(colorSkipButton)
}
+ /**
+ * Override skip button text overall style
+ *
+ * @param textAppearance your text style from resource
+ */
+ fun setSkipTextAppearance(
+ @StyleRes textAppearance: Int,
+ ) {
+ val skip = findViewById(R.id.skip)
+ TextViewCompat.setTextAppearance(skip, textAppearance)
+ }
+
/**
* Override Next button
*
@@ -168,4 +224,24 @@ abstract class AppIntro : AppIntroBase() {
bottomSeparator.visibility = View.INVISIBLE
}
}
+
+ /**
+ * Adds a margin to the bottom of the content of the slide equal to the bottom bar.
+ * For situations where the bar is not transparent, and you want the content of the slide to
+ * be intractable.
+ *
+ * @param setBarMargin Set : true for margin. false for no margin.
+ */
+ fun setBarMargin(setBarMargin: Boolean) {
+ val bottomBar = findViewById(R.id.pager_gesture_overlay)
+ val margin =
+ if (setBarMargin) {
+ resources.getDimension(R.dimen.appintro_bottombar_height).toInt()
+ } else {
+ 0
+ }
+ (bottomBar.layoutParams as? ViewGroup.MarginLayoutParams)
+ ?.setMargins(0, 0, 0, margin)
+ ?.also { bottomBar.requestLayout() }
+ }
}
diff --git a/appintro/src/main/java/com/github/appintro/AppIntro2.kt b/appintro/src/main/java/com/github/appintro/AppIntro2.kt
index 7f13d40ab..93474404f 100644
--- a/appintro/src/main/java/com/github/appintro/AppIntro2.kt
+++ b/appintro/src/main/java/com/github/appintro/AppIntro2.kt
@@ -1,19 +1,17 @@
package com.github.appintro
import android.graphics.drawable.Drawable
-import android.os.Build
import android.os.Bundle
import android.view.View
import android.widget.ImageButton
import androidx.annotation.ColorInt
-import androidx.annotation.IdRes
+import androidx.annotation.DrawableRes
import androidx.constraintlayout.widget.ConstraintLayout
abstract class AppIntro2 : AppIntroBase() {
-
override val layoutId = R.layout.appintro_intro_layout2
- @IdRes
+ @DrawableRes
var backgroundResource: Int? = null
set(value) {
field = value
@@ -26,9 +24,7 @@ abstract class AppIntro2 : AppIntroBase() {
set(value) {
field = value
if (field != null) {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
- backgroundFrame.background = field
- }
+ backgroundFrame.background = field
}
}
@@ -50,7 +46,9 @@ abstract class AppIntro2 : AppIntroBase() {
* Override viewpager bar color
* @param color your color resource
*/
- fun setBarColor(@ColorInt color: Int) {
+ fun setBarColor(
+ @ColorInt color: Int,
+ ) {
bottomBar.setBackgroundColor(color)
}
@@ -61,4 +59,38 @@ abstract class AppIntro2 : AppIntroBase() {
fun setImageSkipButton(imageSkipButton: Drawable) {
skipImageButton.setImageDrawable(imageSkipButton)
}
+
+ /**
+ * Override next button arrow color
+ *
+ * @param color your color
+ */
+ fun setNextArrowColor(
+ @ColorInt color: Int,
+ ) {
+ val nextButton = findViewById(R.id.next)
+ nextButton.setColorFilter(color)
+ }
+
+ /**
+ * Override skip button color
+ *
+ * @param colorSkipButton your color resource
+ */
+ fun setSkipArrowColor(
+ @ColorInt colorSkipButton: Int,
+ ) {
+ val skip = findViewById(R.id.skip)
+ skip.setColorFilter(colorSkipButton)
+ }
+
+ /**
+ * Override done button drawable resource
+ *
+ * @param imageDoneButton your drawable resource
+ */
+ fun setImageDoneButton(imageDoneButton: Drawable) {
+ val done = findViewById(R.id.done)
+ done.setImageDrawable(imageDoneButton)
+ }
}
diff --git a/appintro/src/main/java/com/github/appintro/AppIntroBase.kt b/appintro/src/main/java/com/github/appintro/AppIntroBase.kt
index 093a9f502..f26134bd5 100644
--- a/appintro/src/main/java/com/github/appintro/AppIntroBase.kt
+++ b/appintro/src/main/java/com/github/appintro/AppIntroBase.kt
@@ -3,18 +3,15 @@
package com.github.appintro
import android.animation.ArgbEvaluator
-import android.annotation.SuppressLint
-import android.content.Context
import android.content.pm.PackageManager
import android.os.Build
import android.os.Bundle
-import android.os.Vibrator
import android.view.KeyEvent
import android.view.View
import android.view.ViewGroup
import android.view.Window
-import android.view.WindowManager
import android.widget.ImageButton
+import androidx.activity.OnBackPressedCallback
import androidx.annotation.ColorInt
import androidx.annotation.ColorRes
import androidx.annotation.LayoutRes
@@ -23,15 +20,19 @@ import androidx.appcompat.app.AppCompatDelegate
import androidx.appcompat.widget.TooltipCompat.setTooltipText
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
+import androidx.core.view.WindowCompat
+import androidx.core.view.WindowInsetsCompat.Type.systemBars
+import androidx.core.view.WindowInsetsControllerCompat
import androidx.fragment.app.Fragment
-import androidx.viewpager.widget.ViewPager
+import androidx.viewpager2.widget.ViewPager2
import com.github.appintro.indicator.DotIndicatorController
import com.github.appintro.indicator.IndicatorController
import com.github.appintro.indicator.ProgressIndicatorController
-import com.github.appintro.internal.AppIntroViewPager
+import com.github.appintro.internal.AppIntroViewPagerController
import com.github.appintro.internal.LayoutUtil
import com.github.appintro.internal.LogHelper
import com.github.appintro.internal.PermissionWrapper
+import com.github.appintro.internal.VibrationHelper
import com.github.appintro.internal.viewpager.PagerAdapter
import com.github.appintro.internal.viewpager.ViewPagerTransformer
@@ -40,7 +41,6 @@ import com.github.appintro.internal.viewpager.ViewPagerTransformer
* the lifecycle and all the event callbacks for AppIntro.
*/
abstract class AppIntroBase : AppCompatActivity(), AppIntroViewPagerListener {
-
/** The layout ID that will be used during inflation. */
@get:LayoutRes
protected abstract val layoutId: Int
@@ -99,10 +99,15 @@ abstract class AppIntroBase : AppCompatActivity(), AppIntroViewPagerListener {
* */
protected var isVibrate = false
+ /**
+ * Read-only property with the total number of slides for this AppIntro.
+ */
+ protected val totalSlidesNumber: Int get() = slidesNumber
+
// Private Fields
private lateinit var pagerAdapter: PagerAdapter
- private lateinit var pager: AppIntroViewPager
+ private lateinit var pagerController: AppIntroViewPagerController
private var slidesNumber: Int = 0
private var savedCurrentItem: Int = 0
private var currentlySelectedItem = -1
@@ -116,19 +121,17 @@ abstract class AppIntroBase : AppCompatActivity(), AppIntroViewPagerListener {
// Asks the ViewPager for the current slide number. Useful to query the [permissionsMap]
private val currentSlideNumber: Int
- get() = pager.getCurrentSlideNumber(fragments.size)
+ get() = pagerController.getCurrentSlideNumber(fragments.size)
/** HashMap that contains the [PermissionWrapper] objects */
private var permissionsMap = HashMap()
- private var retainIsButtonEnabled = true
+ private var retainIsButtonsEnabled = true
- // Android SDK
- private lateinit var vibrator: Vibrator
private val argbEvaluator = ArgbEvaluator()
internal val isRtl: Boolean
- get() = LayoutUtil.isRtl(applicationContext)
+ get() = LayoutUtil.isRtl(this)
/*
PUBLIC API
@@ -141,13 +144,14 @@ abstract class AppIntroBase : AppCompatActivity(), AppIntroViewPagerListener {
protected fun addSlide(fragment: Fragment) {
if (isRtl) {
fragments.add(0, fragment)
+ pagerAdapter.notifyItemInserted(0)
} else {
fragments.add(fragment)
+ pagerAdapter.notifyItemInserted(fragments.size)
}
if (isWizardMode) {
- pager.offscreenPageLimit = fragments.size
+ pagerController.setOffscreenPageLimit(fragments.size)
}
- pagerAdapter.notifyDataSetChanged()
}
/**
@@ -158,7 +162,11 @@ abstract class AppIntroBase : AppCompatActivity(), AppIntroViewPagerListener {
* @param required - Whether the user can change this slide without granting the permissions.
*/
@JvmOverloads
- protected fun askForPermissions(permissions: Array, slideNumber: Int, required: Boolean = true) {
+ protected fun askForPermissions(
+ permissions: Array,
+ slideNumber: Int,
+ required: Boolean = true,
+ ) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (slideNumber <= 0) {
error("Invalid Slide Number: $slideNumber")
@@ -169,91 +177,86 @@ abstract class AppIntroBase : AppCompatActivity(), AppIntroViewPagerListener {
}
/** Moves the AppIntro to the previous slide */
- private fun goToPreviousSlide() {
- pager.goToPreviousSlide()
+ protected fun goToPreviousSlide() {
+ pagerController.goToPreviousSlide()
}
/** Moves the AppIntro to the next slide */
- protected fun goToNextSlide(isLastSlide: Boolean = pager.currentItem + 1 == slidesNumber) {
+ @JvmOverloads
+ protected fun goToNextSlide(isLastSlide: Boolean = pagerController.isLastSlide(fragments.size)) {
if (isLastSlide) {
onIntroFinished()
} else {
- pager.goToNextSlide()
+ pagerController.goToNextSlide()
onNextSlide()
}
}
/** Enable the Immersive Sticky Mode */
protected fun setImmersiveMode() {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
- window.decorView.systemUiVisibility = (
- View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
- or View.SYSTEM_UI_FLAG_LAYOUT_STABLE
- or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
- or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
- or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
- or View.SYSTEM_UI_FLAG_FULLSCREEN
- )
+ WindowCompat.getInsetsController(window, window.decorView).apply {
+ systemBarsBehavior =
+ WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
+ hide(systemBars())
}
}
/** Customize the color of the Status Bar */
- protected fun setStatusBarColor(@ColorInt color: Int) {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
- window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS)
- window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS)
- window.statusBarColor = color
- }
+ protected fun setStatusBarColor(
+ @ColorInt color: Int,
+ ) {
+ // We set the light status bar/translucent first via the WindowInsetsControllerCompat
+ WindowInsetsControllerCompat(window, window.decorView).isAppearanceLightStatusBars = true
+ window.statusBarColor = color
}
/** Customize the color of the Status Bar */
- protected fun setStatusBarColorRes(@ColorRes color: Int) {
+ protected fun setStatusBarColorRes(
+ @ColorRes color: Int,
+ ) {
setStatusBarColor(ContextCompat.getColor(this, color))
}
/** Customize the color of the Navigation Bar */
- protected fun setNavBarColor(@ColorInt color: Int) {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
- window.navigationBarColor = color
- }
+ protected fun setNavBarColor(
+ @ColorInt color: Int,
+ ) {
+ window.navigationBarColor = color
}
/** Customize the color of the Navigation Bar */
- protected fun setNavBarColorRes(@ColorRes color: Int) {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
- window.navigationBarColor = ContextCompat.getColor(this, color)
- }
+ protected fun setNavBarColorRes(
+ @ColorRes color: Int,
+ ) {
+ window.navigationBarColor = ContextCompat.getColor(this, color)
}
/** Toggle the Status Bar visibility */
protected fun showStatusBar(show: Boolean) {
+ val controller = WindowCompat.getInsetsController(window, window.decorView)
if (show) {
- window.clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN)
+ controller.show(systemBars())
} else {
- window.setFlags(
- WindowManager.LayoutParams.FLAG_FULLSCREEN,
- WindowManager.LayoutParams.FLAG_FULLSCREEN
- )
+ controller.systemBarsBehavior = WindowInsetsControllerCompat.BEHAVIOR_DEFAULT
+ controller.hide(systemBars())
}
}
/**
- * Setting to disable forward swiping right on current page and allow swiping left. If a swipe
- * left occurs, the lock state is reset and swiping is re-enabled. (one shot disable) This also
- * hides/shows the Next and Done buttons accordingly.
- *
* @param lock Set true to disable forward swiping. False to enable.
+ * @deprecated setNextPageSwipeLock has been deprecated in favor of setSwipeLock or SlidePolicy
*/
+ @Deprecated(
+ "setNextPageSwipeLock has been deprecated in favor of setSwipeLock or SlidePolicy",
+ ReplaceWith("setSwipeLock"),
+ DeprecationLevel.ERROR,
+ )
+ @Suppress("UnusedPrivateMember", "UNUSED_PARAMETER")
protected fun setNextPageSwipeLock(lock: Boolean) {
- // We retain the button state in order to be able to restore
- // it properly afterwards.
- if (lock) {
- retainIsButtonEnabled = this.isButtonsEnabled
- this.isButtonsEnabled = true
- } else {
- this.isButtonsEnabled = retainIsButtonEnabled
- }
- pager.isNextPagingEnabled = !lock
+ LogHelper.w(
+ TAG,
+ "Calling setNextPageSwipeLock has not effect here. Please switch to setSwipeLock or SlidePolicy",
+ )
}
/**
@@ -266,12 +269,12 @@ abstract class AppIntroBase : AppCompatActivity(), AppIntroViewPagerListener {
// We retain the button state in order to be able to restore
// it properly afterwards.
if (lock) {
- retainIsButtonEnabled = this.isButtonsEnabled
+ retainIsButtonsEnabled = this.isButtonsEnabled
this.isButtonsEnabled = true
} else {
- this.isButtonsEnabled = retainIsButtonEnabled
+ this.isButtonsEnabled = retainIsButtonsEnabled
}
- pager.isFullPagingEnabled = !lock
+ pagerController.isFullPagingEnabled = !lock
}
/**
@@ -290,7 +293,7 @@ abstract class AppIntroBase : AppCompatActivity(), AppIntroViewPagerListener {
*/
protected fun setIndicatorColor(
@ColorInt selectedIndicatorColor: Int,
- @ColorInt unselectedIndicatorColor: Int
+ @ColorInt unselectedIndicatorColor: Int,
) {
indicatorController?.selectedIndicatorColor = selectedIndicatorColor
indicatorController?.unselectedIndicatorColor = unselectedIndicatorColor
@@ -300,23 +303,14 @@ abstract class AppIntroBase : AppCompatActivity(), AppIntroViewPagerListener {
TRANSFORMERS
=================================== */
- /**
- * Sets the scroll duration factor - by default it is 1. This factor will
- * multiply duration
- * @param factor the new factor that will be applied to the scroll - default: 1
- */
- protected fun setScrollDurationFactor(factor: Int) {
- pager.setScrollDurationFactor(factor.toDouble())
- }
-
/** Allows to specify one of the [AppIntroPageTransformerType] for the ViewPager */
protected fun setTransformer(appIntroTransformer: AppIntroPageTransformerType) {
- pager.setAppIntroPageTransformer(appIntroTransformer)
+ pagerController.setAppIntroPageTransformer(appIntroTransformer)
}
/** Overrides viewpager transformer with you custom [ViewPagerTransformer] */
- protected fun setCustomTransformer(transformer: ViewPager.PageTransformer?) {
- pager.setPageTransformer(true, transformer)
+ protected fun setCustomTransformer(transformer: ViewPager2.PageTransformer?) {
+ pagerController.setPageTransformer(transformer)
}
/*
@@ -379,17 +373,24 @@ abstract class AppIntroBase : AppCompatActivity(), AppIntroViewPagerListener {
* @param newFragment Instance of the fragment which is displayed now.
* This might be null if the intro has finished
*/
- protected open fun onSlideChanged(oldFragment: Fragment?, newFragment: Fragment?) {}
+ protected open fun onSlideChanged(
+ oldFragment: Fragment?,
+ newFragment: Fragment?,
+ ) {}
/*
LIFECYCLE
=================================== */
+ @Suppress("DEPRECATION")
override fun onCreate(savedInstanceState: Bundle?) {
- requestWindowFeature(Window.FEATURE_NO_TITLE)
+ supportRequestWindowFeature(Window.FEATURE_NO_TITLE)
AppCompatDelegate.setCompatVectorFromResourcesEnabled(true)
super.onCreate(savedInstanceState)
+ // Add back handler
+ addBackHandler()
+
// We default the indicator controller to the Dotted one.
indicatorController = DotIndicatorController(this)
@@ -421,24 +422,24 @@ abstract class AppIntroBase : AppCompatActivity(), AppIntroViewPagerListener {
backButton.scaleX = -1f
}
- vibrator = this.getSystemService(Context.VIBRATOR_SERVICE) as Vibrator
-
- pagerAdapter = PagerAdapter(supportFragmentManager, fragments)
- pager = findViewById(R.id.view_pager)
+ pagerAdapter = PagerAdapter(this@AppIntroBase, fragments)
+ pagerController =
+ AppIntroViewPagerController(
+ viewPager = findViewById(R.id.view_pager),
+ viewPagerGestureOverlay = findViewById(R.id.pager_gesture_overlay),
+ )
doneButton.setOnClickListener(NextSlideOnClickListener(isLastSlide = true))
nextButton.setOnClickListener(NextSlideOnClickListener(isLastSlide = false))
- backButton.setOnClickListener { pager.goToPreviousSlide() }
+ backButton.setOnClickListener { pagerController.goToPreviousSlide() }
skipButton.setOnClickListener {
dispatchVibration()
- onSkipPressed(pagerAdapter.getItem(pager.currentItem))
+ onSkipPressed(getPagerItem(pagerController.getCurrentItem()))
}
- pager.adapter = this.pagerAdapter
- pager.addOnPageChangeListener(OnPageChangeListener())
- pager.onNextPageRequestedListener = this
-
- setScrollDurationFactor(DEFAULT_SCROLL_DURATION_FACTOR)
+ pagerController.setAdapter(this.pagerAdapter)
+ pagerController.registerOnPageChangeCallback(OnPageChangeCallback())
+ pagerController.onNextPageRequestedListener = this
}
override fun onPostCreate(savedInstanceState: Bundle?) {
@@ -447,21 +448,21 @@ abstract class AppIntroBase : AppCompatActivity(), AppIntroViewPagerListener {
slidesNumber = fragments.size
initializeIndicator()
+ // Makes sure we correctly retain the `isButtonsEnabled` just after onCreate
+ retainIsButtonsEnabled = isButtonsEnabled
+
// Required for triggering onPageSelected and onSlideChanged for the first page.
if (isRtl) {
- pager.currentItem = fragments.size - savedCurrentItem
+ pagerController.setCurrentViewPagerItem(fragments.size - savedCurrentItem)
} else {
- pager.currentItem = savedCurrentItem
+ pagerController.setCurrentViewPagerItem(savedCurrentItem)
}
- pager.post {
- val fragment = pagerAdapter.getItem(pager.currentItem)
- // Fragment is null when no slides are passed to AppIntro
- if (fragment != null) {
+ pagerController.post {
+ if (pagerController.getCurrentItem() < pagerAdapter.itemCount) {
dispatchSlideChangedCallbacks(
null,
- pagerAdapter
- .getItem(pager.currentItem)
+ getPagerItem(pagerController.getCurrentItem()),
)
} else {
// Close the intro if there are no slides to show
@@ -474,15 +475,20 @@ abstract class AppIntroBase : AppCompatActivity(), AppIntroViewPagerListener {
super.onSaveInstanceState(outState)
outState.apply {
putInt(ARG_BUNDLE_SLIDES_NUMBER, slidesNumber)
- putBoolean(ARG_BUNDLE_RETAIN_IS_BUTTON_ENABLED, retainIsButtonEnabled)
- putBoolean(ARG_BUNDLE_IS_BUTTON_ENABLED, isButtonsEnabled)
+ putBoolean(ARG_BUNDLE_RETAIN_IS_BUTTONS_ENABLED, retainIsButtonsEnabled)
+ putBoolean(ARG_BUNDLE_IS_BUTTONS_ENABLED, isButtonsEnabled)
putBoolean(ARG_BUNDLE_IS_SKIP_BUTTON_ENABLED, isSkipButtonEnabled)
putBoolean(ARG_BUNDLE_IS_INDICATOR_ENABLED, isIndicatorEnabled)
- putInt(ARG_BUNDLE_LOCK_PAGE, pager.lockPage)
- putInt(ARG_BUNDLE_CURRENT_ITEM, pager.currentItem)
- putBoolean(ARG_BUNDLE_IS_FULL_PAGING_ENABLED, pager.isFullPagingEnabled)
- putBoolean(ARG_BUNDLE_IS_NEXT_PAGING_ENABLED, pager.isNextPagingEnabled)
+ // We can't use pager.currentItem as we need the current item that is RTL-invariant.
+ val currentItem =
+ if (isRtl) {
+ fragments.size - pagerController.getCurrentItem()
+ } else {
+ pagerController.getCurrentItem()
+ }
+ putInt(ARG_BUNDLE_CURRENT_ITEM, currentItem)
+ putBoolean(ARG_BUNDLE_IS_FULL_PAGING_ENABLED, pagerController.isFullPagingEnabled)
putSerializable(ARG_BUNDLE_PERMISSION_MAP, permissionsMap)
@@ -494,20 +500,26 @@ abstract class AppIntroBase : AppCompatActivity(), AppIntroViewPagerListener {
super.onRestoreInstanceState(savedInstanceState)
with(savedInstanceState) {
slidesNumber = getInt(ARG_BUNDLE_SLIDES_NUMBER)
- retainIsButtonEnabled = getBoolean(ARG_BUNDLE_RETAIN_IS_BUTTON_ENABLED)
- isButtonsEnabled = getBoolean(ARG_BUNDLE_IS_BUTTON_ENABLED)
+ retainIsButtonsEnabled = getBoolean(ARG_BUNDLE_RETAIN_IS_BUTTONS_ENABLED)
+ isButtonsEnabled = getBoolean(ARG_BUNDLE_IS_BUTTONS_ENABLED)
isSkipButtonEnabled = getBoolean(ARG_BUNDLE_IS_SKIP_BUTTON_ENABLED)
isIndicatorEnabled = getBoolean(ARG_BUNDLE_IS_INDICATOR_ENABLED)
- pager.lockPage = getInt(ARG_BUNDLE_LOCK_PAGE)
savedCurrentItem = getInt(ARG_BUNDLE_CURRENT_ITEM)
- pager.isFullPagingEnabled = getBoolean(ARG_BUNDLE_IS_FULL_PAGING_ENABLED)
- pager.isNextPagingEnabled = getBoolean(ARG_BUNDLE_IS_NEXT_PAGING_ENABLED)
+ pagerController.isFullPagingEnabled = getBoolean(ARG_BUNDLE_IS_FULL_PAGING_ENABLED)
+
+ @Suppress("UNCHECKED_CAST", "DEPRECATION")
+ permissionsMap =
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+ (
+ getSerializable(ARG_BUNDLE_PERMISSION_MAP, HashMap::class.java)
+ as HashMap?
+ ) ?: hashMapOf()
+ } else {
+ (getSerializable(ARG_BUNDLE_PERMISSION_MAP) as HashMap?)
+ ?: hashMapOf()
+ }
- permissionsMap = (
- (getSerializable(ARG_BUNDLE_PERMISSION_MAP) as HashMap?)
- ?: hashMapOf()
- )
isColorTransitionsEnabled = getBoolean(ARG_BUNDLE_COLOR_TRANSITIONS_ENABLED)
}
}
@@ -518,35 +530,49 @@ abstract class AppIntroBase : AppCompatActivity(), AppIntroViewPagerListener {
indicatorController?.selectPosition(currentlySelectedItem)
}
- override fun onKeyDown(code: Int, event: KeyEvent): Boolean {
+ override fun onKeyDown(
+ code: Int,
+ event: KeyEvent,
+ ): Boolean {
// Handle the navigation with 'Enter' or Dpad events.
if (code == KeyEvent.KEYCODE_ENTER ||
code == KeyEvent.KEYCODE_BUTTON_A ||
code == KeyEvent.KEYCODE_DPAD_CENTER
) {
- val isLastSlide = pager.currentItem == pagerAdapter.count - 1
+ val isLastSlide = pagerController.isLastSlide(fragments.size)
goToNextSlide(isLastSlide)
if (isLastSlide) {
// We emulate the onDonePressed here to keep backward compatibility
// with the previous API (users expect an onDonePressed to kill the Activity).
// Ideally we should get rid of this extra callback in one of the future release.
- onDonePressed(pagerAdapter.getItem(pager.currentItem))
+ onDonePressed(getPagerItem(pagerController.getCurrentItem()))
}
return false
}
return super.onKeyDown(code, event)
}
- override fun onBackPressed() {
- // Do nothing if go back lock is enabled or slide has custom policy.
- if (isSystemBackButtonLocked) {
- return
- }
- if (pager.isFirstSlide(fragments.size)) {
- super.onBackPressed()
- } else {
- pager.goToPreviousSlide()
- }
+ /*
+ BACK HANDLER
+ =================================== */
+
+ private fun addBackHandler() {
+ onBackPressedDispatcher.addCallback(
+ this,
+ object : OnBackPressedCallback(true) {
+ override fun handleOnBackPressed() {
+ // Do nothing if go back lock is enabled or slide has custom policy.
+ if (isSystemBackButtonLocked) {
+ return
+ }
+ if (pagerController.isFirstSlide(fragments.size)) {
+ finish()
+ } else {
+ pagerController.goToPreviousSlide()
+ }
+ }
+ },
+ )
}
/*
@@ -555,13 +581,12 @@ abstract class AppIntroBase : AppCompatActivity(), AppIntroViewPagerListener {
private fun updateButtonsVisibility() {
if (isButtonsEnabled) {
- val isLastSlide =
- !isRtl && pager.currentItem == slidesNumber - 1 ||
- isRtl && pager.currentItem == 0
+ val isLastSlide = pagerController.isLastSlide(fragments.size)
+ val isFirstSlide = pagerController.isFirstSlide(fragments.size)
nextButton.isVisible = !isLastSlide
doneButton.isVisible = isLastSlide
skipButton.isVisible = isSkipButtonEnabled && !isLastSlide
- backButton.isVisible = isWizardMode
+ backButton.isVisible = isWizardMode && !isFirstSlide
} else {
nextButton.isVisible = false
doneButton.isVisible = false
@@ -581,7 +606,7 @@ abstract class AppIntroBase : AppCompatActivity(), AppIntroViewPagerListener {
* @return true, if the slide change should be allowed, else false
*/
override fun onCanRequestNextPage(): Boolean {
- val currentFragment = pagerAdapter.getItem(pager.currentItem)
+ val currentFragment = getPagerItem(pagerController.getCurrentItem())
// Check if the current fragment implements SlidePolicy, else a change is always allowed.
return if (currentFragment is SlidePolicy && !currentFragment.isPolicyRespected) {
@@ -594,7 +619,7 @@ abstract class AppIntroBase : AppCompatActivity(), AppIntroViewPagerListener {
}
override fun onIllegallyRequestedNextPage() {
- val currentFragment = pagerAdapter.getItem(pager.currentItem)
+ val currentFragment = getPagerItem(pagerController.getCurrentItem())
if (currentFragment is SlidePolicy) {
if (!currentFragment.isPolicyRespected) {
currentFragment.onUserIllegallyRequestedNextPage()
@@ -616,7 +641,7 @@ abstract class AppIntroBase : AppCompatActivity(), AppIntroViewPagerListener {
ActivityCompat.requestPermissions(
this,
it.permissions,
- PERMISSIONS_REQUEST_ALL_PERMISSIONS
+ PERMISSIONS_REQUEST_ALL_PERMISSIONS,
)
}
}
@@ -627,7 +652,7 @@ abstract class AppIntroBase : AppCompatActivity(), AppIntroViewPagerListener {
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array,
- grantResults: IntArray
+ grantResults: IntArray,
) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
setSwipeLock(false)
@@ -636,10 +661,11 @@ abstract class AppIntroBase : AppCompatActivity(), AppIntroViewPagerListener {
return
}
- val deniedPermissions = grantResults
- .mapIndexed { index, result -> (permissions[index] to result) }
- .filter { (_, result) -> result == PackageManager.PERMISSION_DENIED }
- .map { (permission, _) -> permission }
+ val deniedPermissions =
+ grantResults
+ .mapIndexed { index, result -> (permissions[index] to result) }
+ .filter { (_, result) -> result == PackageManager.PERMISSION_DENIED }
+ .map { (permission, _) -> permission }
// Check if all permissions are granted.
if (deniedPermissions.isEmpty()) {
@@ -651,7 +677,7 @@ abstract class AppIntroBase : AppCompatActivity(), AppIntroViewPagerListener {
// At least one or all of the permissions have been denied.
deniedPermissions.forEach(::handleDeniedPermission)
// Let's force a recenter of the current slide.
- pager.reCenterCurrentSlide()
+ pagerController.reCenterCurrentSlide()
}
}
@@ -664,46 +690,39 @@ abstract class AppIntroBase : AppCompatActivity(), AppIntroViewPagerListener {
// Permission is denied for the first time (never ask again box is not checked).
// Ask again explaining the usage of the permission (Show an AlertDialog or Snackbar)
onUserDeniedPermission(permission)
-
- // If the permission was not required, we can remove it from the App and let the user proceed.
- permissionsMap[currentSlideNumber]?.let { requestedPermission ->
- if (!requestedPermission.required) {
- permissionsMap.remove(requestedPermission.position)
- goToNextSlide()
- }
- }
} else {
// Permission is disabled (never ask again is checked)
// Ask the user to go to settings to enable permission.
onUserDisabledPermission(permission)
}
+
+ // If the permission was not required, we can remove it from the App and let the user proceed.
+ permissionsMap[currentSlideNumber]?.let { requestedPermission ->
+ if (!requestedPermission.required) {
+ permissionsMap.remove(requestedPermission.position)
+ goToNextSlide()
+ }
+ }
}
- // You must grant vibration permissions on your AndroidManifest.xml file
- @SuppressLint("MissingPermission")
private fun dispatchVibration() {
if (isVibrate) {
- vibrator.vibrate(vibrateDuration)
+ VibrationHelper.vibrate(this, vibrateDuration)
}
}
/**
- * Called by [ViewPager.OnPageChangeListener.onPageSelected] to tell [AppIntroViewPager]
- * to request permissions on swipe.
- * This method notifies [AppIntroViewPager] that the currently selected slide
+ * Getter used to notify [AppIntroViewPager] if the currently selected slide
* has permissions attached to it.
*/
- private fun setPermissionSlide() {
- if (pager.getCurrentSlideNumber(fragments.size) in permissionsMap) {
- pager.isPermissionSlide = true
- } else {
- pager.isPermissionSlide = false
- setSwipeLock(false)
- }
- }
+ private val isPermissionSlide: Boolean
+ get() = pagerController.getCurrentSlideNumber(fragments.size) in permissionsMap
/** Takes care of calling all the necessary callbacks on Slide Changing. */
- private fun dispatchSlideChangedCallbacks(oldFragment: Fragment?, newFragment: Fragment?) {
+ private fun dispatchSlideChangedCallbacks(
+ oldFragment: Fragment?,
+ newFragment: Fragment?,
+ ) {
if (oldFragment is SlideSelectionListener) {
oldFragment.onSlideDeselected()
}
@@ -714,18 +733,25 @@ abstract class AppIntroBase : AppCompatActivity(), AppIntroViewPagerListener {
}
/** Performs color interpolation between two slides.. */
- private fun performColorTransition(currentSlide: Fragment?, nextSlide: Fragment?, positionOffset: Float) {
+ private fun performColorTransition(
+ currentSlide: Fragment?,
+ nextSlide: Fragment?,
+ positionOffset: Float,
+ ) {
+ if (nextSlide == null) return
+
if (currentSlide is SlideBackgroundColorHolder &&
nextSlide is SlideBackgroundColorHolder
) {
// Check if both fragments are attached to an activity,
// otherwise getDefaultBackgroundColor may fail.
if (currentSlide.isAdded && nextSlide.isAdded) {
- val newColor = argbEvaluator.evaluate(
- positionOffset,
- currentSlide.defaultBackgroundColor,
- nextSlide.defaultBackgroundColor
- ) as Int
+ val newColor =
+ argbEvaluator.evaluate(
+ positionOffset,
+ getSlideColor(currentSlide),
+ getSlideColor(nextSlide),
+ ) as Int
currentSlide.setBackgroundColor(newColor)
nextSlide.setBackgroundColor(newColor)
}
@@ -734,6 +760,18 @@ abstract class AppIntroBase : AppCompatActivity(), AppIntroViewPagerListener {
}
}
+ @ColorInt
+ @Suppress("DEPRECATION")
+ private fun getSlideColor(slide: SlideBackgroundColorHolder): Int {
+ if (slide.defaultBackgroundColorRes != 0) {
+ return ContextCompat.getColor(this, slide.defaultBackgroundColorRes)
+ }
+
+ return slide.defaultBackgroundColor
+ }
+
+ private fun getPagerItem(position: Int): Fragment? = pagerAdapter.getItem(position, supportFragmentManager)
+
/**
* Onclick listener for the Next/Done button.
* @param isLastSlide True if you're using this for the DONE button.
@@ -752,7 +790,7 @@ abstract class AppIntroBase : AppCompatActivity(), AppIntroViewPagerListener {
}
// We can successfully change slide, let's do it.
- val currentFragment = pagerAdapter.getItem(pager.currentItem)
+ val currentFragment = getPagerItem(pagerController.getCurrentItem())
if (isLastSlide) {
onDonePressed(currentFragment)
} else {
@@ -763,15 +801,18 @@ abstract class AppIntroBase : AppCompatActivity(), AppIntroViewPagerListener {
}
/**
- * [OnPageChangeListener] used to handle all the callbacks coming from the ViewPager.
+ * [OnPageChangeCallback] used to handle all the callbacks coming from the ViewPager.
* Moreover, if [isColorTransitionsEnabled] a color interpolation will happen in the [onPageScrolled]
*/
- internal inner class OnPageChangeListener : ViewPager.OnPageChangeListener {
-
- override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {
- if (isColorTransitionsEnabled && position < pagerAdapter.count - 1) {
- val currentSlide = pagerAdapter.getItem(position)
- val nextSlide = pagerAdapter.getItem(position + 1)
+ internal inner class OnPageChangeCallback : ViewPager2.OnPageChangeCallback() {
+ override fun onPageScrolled(
+ position: Int,
+ positionOffset: Float,
+ positionOffsetPixels: Int,
+ ) {
+ if (isColorTransitionsEnabled && position < pagerAdapter.itemCount - 1) {
+ val currentSlide = getPagerItem(position)
+ val nextSlide = getPagerItem(position + 1)
performColorTransition(currentSlide, nextSlide, positionOffset)
}
}
@@ -780,54 +821,43 @@ abstract class AppIntroBase : AppCompatActivity(), AppIntroViewPagerListener {
if (slidesNumber >= 1) {
indicatorController?.selectPosition(position)
}
-
- // Allow the swipe to be re-enabled if a user swipes to a previous slide. Restore
- // state of progress button depending on global progress button setting
- if (!pager.isNextPagingEnabled) {
- if (pager.currentItem != pager.lockPage) {
- isButtonsEnabled = retainIsButtonEnabled
- pager.isNextPagingEnabled = true
- }
- }
updateButtonsVisibility()
- setPermissionSlide()
+ pagerController.isPermissionSlide = this@AppIntroBase.isPermissionSlide
// Firing all the necessary Callbacks
this@AppIntroBase.onPageSelected(position)
if (slidesNumber > 0) {
if (currentlySelectedItem == -1) {
- dispatchSlideChangedCallbacks(null, pagerAdapter.getItem(position))
+ dispatchSlideChangedCallbacks(
+ null,
+ getPagerItem(position),
+ )
} else {
dispatchSlideChangedCallbacks(
- pagerAdapter.getItem(currentlySelectedItem),
- pagerAdapter.getItem(pager.currentItem)
+ getPagerItem(currentlySelectedItem),
+ getPagerItem(pagerController.getCurrentItem()),
)
}
}
currentlySelectedItem = position
}
-
- override fun onPageScrollStateChanged(state: Int) {}
}
private companion object {
private val TAG = LogHelper.makeLogTag(AppIntroBase::class.java)
- private const val DEFAULT_SCROLL_DURATION_FACTOR = 1
private const val DEFAULT_VIBRATE_DURATION = 20L
private const val PERMISSIONS_REQUEST_ALL_PERMISSIONS = 1
private const val ARG_BUNDLE_COLOR_TRANSITIONS_ENABLED = "colorTransitionEnabled"
private const val ARG_BUNDLE_CURRENT_ITEM = "currentItem"
- private const val ARG_BUNDLE_IS_BUTTON_ENABLED = "isButtonsEnabled"
+ private const val ARG_BUNDLE_IS_BUTTONS_ENABLED = "isButtonsEnabled"
private const val ARG_BUNDLE_IS_FULL_PAGING_ENABLED = "isFullPagingEnabled"
private const val ARG_BUNDLE_IS_INDICATOR_ENABLED = "isIndicatorEnabled"
- private const val ARG_BUNDLE_IS_NEXT_PAGING_ENABLED = "isNextPagingEnabled"
private const val ARG_BUNDLE_IS_SKIP_BUTTON_ENABLED = "isSkipButtonsEnabled"
- private const val ARG_BUNDLE_LOCK_PAGE = "lockPage"
private const val ARG_BUNDLE_PERMISSION_MAP = "permissionMap"
- private const val ARG_BUNDLE_RETAIN_IS_BUTTON_ENABLED = "retainIsButtonEnabled"
+ private const val ARG_BUNDLE_RETAIN_IS_BUTTONS_ENABLED = "retainIsButtonsEnabled"
private const val ARG_BUNDLE_SLIDES_NUMBER = "slidesNumber"
}
}
diff --git a/appintro/src/main/java/com/github/appintro/AppIntroBaseFragment.kt b/appintro/src/main/java/com/github/appintro/AppIntroBaseFragment.kt
index b61cb2985..1996274fe 100644
--- a/appintro/src/main/java/com/github/appintro/AppIntroBaseFragment.kt
+++ b/appintro/src/main/java/com/github/appintro/AppIntroBaseFragment.kt
@@ -1,160 +1,186 @@
package com.github.appintro
+import android.graphics.drawable.Animatable
import android.os.Bundle
+import android.text.method.ScrollingMovementMethod
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import androidx.annotation.ColorInt
+import androidx.annotation.ColorRes
import androidx.annotation.LayoutRes
import androidx.constraintlayout.widget.ConstraintLayout
+import androidx.core.content.ContextCompat
import androidx.fragment.app.Fragment
+import androidx.fragment.app.viewModels
import com.github.appintro.internal.LogHelper
import com.github.appintro.internal.TypefaceContainer
-internal const val ARG_TITLE = "title"
-internal const val ARG_TITLE_TYPEFACE = "title_typeface"
-internal const val ARG_TITLE_TYPEFACE_RES = "title_typeface_res"
-internal const val ARG_DESC = "desc"
-internal const val ARG_DESC_TYPEFACE = "desc_typeface"
-internal const val ARG_DESC_TYPEFACE_RES = "desc_typeface_res"
-internal const val ARG_DRAWABLE = "drawable"
-internal const val ARG_BG_COLOR = "bg_color"
-internal const val ARG_TITLE_COLOR = "title_color"
-internal const val ARG_DESC_COLOR = "desc_color"
-internal const val ARG_BG_DRAWABLE = "bg_drawable"
-
abstract class AppIntroBaseFragment : Fragment(), SlideSelectionListener, SlideBackgroundColorHolder {
+ private val viewModel: AppIntroFragmentViewModel by viewModels()
private val logTAG = LogHelper.makeLogTag(AppIntroBaseFragment::class.java)
@get:LayoutRes
protected abstract val layoutId: Int
- private var drawable: Int = 0
- private var bgDrawable: Int = 0
-
- private var titleColor: Int = 0
- private var descColor: Int = 0
+ @ColorInt
+ @Deprecated(
+ "`defaultBackgroundColor` has been deprecated to support configuration changes",
+ ReplaceWith("defaultBackgroundColorRes"),
+ )
final override var defaultBackgroundColor: Int = 0
private set
- private var title: String? = null
- private var description: String? = null
- private var titleTypeface: TypefaceContainer? = null
- private var descTypeface: TypefaceContainer? = null
-
- private var mainLayout: ConstraintLayout? = null
+ @ColorRes
+ final override var defaultBackgroundColorRes: Int = 0
+ private set
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
- retainInstance = true
+
+ @Suppress("DEPRECATION")
+ defaultBackgroundColor = viewModel.defaultBackgroundColor ?: 0
+ defaultBackgroundColorRes = viewModel.defaultBackgroundColorRes ?: 0
val args = arguments
if (args != null && args.size() != 0) {
- drawable = args.getInt(ARG_DRAWABLE)
- title = args.getString(ARG_TITLE)
- description = args.getString(ARG_DESC)
- bgDrawable = args.getInt(ARG_BG_DRAWABLE)
-
- val argsTitleTypeface = args.getString(ARG_TITLE_TYPEFACE)
- val argsDescTypeface = args.getString(ARG_DESC_TYPEFACE)
- val argsTitleTypefaceRes = args.getInt(ARG_TITLE_TYPEFACE_RES)
- val argsDescTypefaceRes = args.getInt(ARG_DESC_TYPEFACE_RES)
- titleTypeface = TypefaceContainer(argsTitleTypeface, argsTitleTypefaceRes)
- descTypeface = TypefaceContainer(argsDescTypeface, argsDescTypefaceRes)
-
- defaultBackgroundColor = args.getInt(ARG_BG_COLOR)
- titleColor = args.getInt(ARG_TITLE_COLOR, 0)
- descColor = args.getInt(ARG_DESC_COLOR, 0)
- }
- }
+ viewModel.drawable = args.getInt(ARG_DRAWABLE)
+ viewModel.title = args.getCharSequence(ARG_TITLE)
+ viewModel.description = args.getCharSequence(ARG_DESC)
+
+ if (args.containsKey(ARG_BG_DRAWABLE)) {
+ viewModel.bgDrawable = args.getInt(ARG_BG_DRAWABLE)
+ }
+
+ viewModel.titleTypefaceUrl = args.getString(ARG_TITLE_TYPEFACE_URL)
+ viewModel.titleTypefaceRes = args.getInt(ARG_TITLE_TYPEFACE_RES)
+
+ viewModel.descTypefaceUrl = args.getString(ARG_DESC_TYPEFACE_URL)
+ viewModel.descTypefaceRes = args.getInt(ARG_DESC_TYPEFACE_RES)
+
+ @Suppress("DEPRECATION")
+ viewModel.defaultBackgroundColor = args.getInt(ARG_BG_COLOR)
+ viewModel.defaultBackgroundColorRes = args.getInt(ARG_BG_COLOR_RES)
+
+ if (args.containsKey(ARG_TITLE_COLOR)) {
+ viewModel.titleColor = args.getInt(ARG_TITLE_COLOR)
+ }
+
+ if (args.containsKey(ARG_TITLE_COLOR_RES)) {
+ viewModel.titleColorRes = args.getInt(ARG_TITLE_COLOR_RES)
+ }
+
+ if (args.containsKey(ARG_DESC_COLOR)) {
+ viewModel.descColor = args.getInt(ARG_DESC_COLOR)
+ }
- override fun onActivityCreated(savedInstanceState: Bundle?) {
- super.onActivityCreated(savedInstanceState)
-
- if (savedInstanceState != null) {
- drawable = savedInstanceState.getInt(ARG_DRAWABLE)
- title = savedInstanceState.getString(ARG_TITLE)
- description = savedInstanceState.getString(ARG_DESC)
-
- titleTypeface = TypefaceContainer(
- savedInstanceState.getString(ARG_TITLE_TYPEFACE),
- savedInstanceState.getInt(ARG_TITLE_TYPEFACE_RES, 0)
- )
- descTypeface = TypefaceContainer(
- savedInstanceState.getString(ARG_DESC_TYPEFACE),
- savedInstanceState.getInt(ARG_DESC_TYPEFACE_RES, 0)
- )
-
- defaultBackgroundColor = savedInstanceState.getInt(ARG_BG_COLOR)
- bgDrawable = savedInstanceState.getInt(ARG_BG_DRAWABLE)
- titleColor = savedInstanceState.getInt(ARG_TITLE_COLOR)
- descColor = savedInstanceState.getInt(ARG_DESC_COLOR)
+ if (args.containsKey(ARG_DESC_COLOR_RES)) {
+ viewModel.descColorRes = args.getInt(ARG_DESC_COLOR_RES)
+ }
}
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
- savedInstanceState: Bundle?
+ savedInstanceState: Bundle?,
): View? {
val view = inflater.inflate(layoutId, container, false)
val titleText = view.findViewById(R.id.title)
val descriptionText = view.findViewById(R.id.description)
val slideImage = view.findViewById(R.id.image)
- mainLayout = view.findViewById(R.id.main)
+ val mainLayout = view.findViewById(R.id.main)
- titleText.text = title
- descriptionText.text = description
- if (titleColor != 0) {
+ titleText.text = viewModel.title
+ descriptionText.text = viewModel.description
+
+ val titleColorRes = viewModel.titleColorRes
+ val titleColor = viewModel.titleColor
+
+ if (titleColorRes != null) {
+ titleText.setTextColor(ContextCompat.getColor(requireContext(), titleColorRes))
+ } else if (titleColor != null) { // Fallback to deprecated static color
titleText.setTextColor(titleColor)
}
- if (descColor != 0) {
+
+ val descColorRes = viewModel.descColorRes
+ val descColor = viewModel.descColor
+
+ if (descColorRes != null) {
+ descriptionText.setTextColor(ContextCompat.getColor(requireContext(), descColorRes))
+ } else if (descColor != null) { // Fallback to deprecated static color
descriptionText.setTextColor(descColor)
}
- titleTypeface?.applyTo(titleText)
- descTypeface?.applyTo(descriptionText)
-
- slideImage.setImageResource(drawable)
- if (bgDrawable != 0) {
- mainLayout?.setBackgroundResource(bgDrawable)
- } else {
- mainLayout?.setBackgroundColor(defaultBackgroundColor)
+
+ TypefaceContainer(
+ typeFaceUrl = viewModel.titleTypefaceUrl,
+ typeFaceResource = viewModel.titleTypefaceRes ?: 0,
+ ).applyTo(titleText)
+
+ TypefaceContainer(
+ typeFaceUrl = viewModel.descTypefaceUrl,
+ typeFaceResource = viewModel.descTypefaceRes ?: 0,
+ ).applyTo(descriptionText)
+
+ viewModel.drawable?.let {
+ slideImage.setImageResource(it)
+ }
+
+ val bgDrawable = viewModel.bgDrawable
+
+ when {
+ bgDrawable != null -> {
+ mainLayout?.setBackgroundResource(bgDrawable)
+ }
+ defaultBackgroundColorRes != 0 -> {
+ mainLayout?.setBackgroundColor(ContextCompat.getColor(requireContext(), defaultBackgroundColorRes))
+ }
+ else -> {
+ @Suppress("DEPRECATION")
+ mainLayout?.setBackgroundColor(defaultBackgroundColor)
+ }
}
+ titleText.movementMethod = ScrollingMovementMethod()
+ descriptionText.movementMethod = ScrollingMovementMethod()
+
return view
}
- override fun onSaveInstanceState(outState: Bundle) {
- outState.putInt(ARG_DRAWABLE, drawable)
- outState.putInt(ARG_BG_DRAWABLE, bgDrawable)
- outState.putString(ARG_TITLE, title)
- outState.putString(ARG_DESC, description)
- outState.putInt(ARG_BG_COLOR, defaultBackgroundColor)
- outState.putInt(ARG_TITLE_COLOR, titleColor)
- outState.putInt(ARG_DESC_COLOR, descColor)
- if (titleTypeface != null) {
- outState.putString(ARG_TITLE_TYPEFACE, titleTypeface?.typeFaceUrl)
- outState.putInt(ARG_TITLE_TYPEFACE_RES, titleTypeface?.typeFaceResource ?: 0)
+ override fun onResume() {
+ super.onResume()
+
+ view?.findViewById(R.id.image).let {
+ if (it is Animatable) {
+ it.start()
+ }
}
- if (descTypeface != null) {
- outState.putString(ARG_DESC_TYPEFACE, descTypeface?.typeFaceUrl)
- outState.putInt(ARG_DESC_TYPEFACE_RES, descTypeface?.typeFaceResource ?: 0)
+ }
+
+ override fun onPause() {
+ super.onPause()
+
+ view?.findViewById(R.id.image).let {
+ if (it is Animatable) {
+ it.start()
+ }
}
- super.onSaveInstanceState(outState)
}
override fun onSlideDeselected() {
- LogHelper.d(logTAG, "Slide $title has been deselected.")
+ LogHelper.d(logTAG, "Slide ${viewModel.title} has been deselected.")
}
override fun onSlideSelected() {
- LogHelper.d(logTAG, "Slide $title has been selected.")
+ LogHelper.d(logTAG, "Slide ${viewModel.title} has been selected.")
}
- override fun setBackgroundColor(@ColorInt backgroundColor: Int) {
- mainLayout?.setBackgroundColor(backgroundColor)
+ override fun setBackgroundColor(
+ @ColorInt backgroundColor: Int,
+ ) {
+ view?.findViewById(R.id.main)?.setBackgroundColor(backgroundColor)
}
}
diff --git a/appintro/src/main/java/com/github/appintro/AppIntroCustomLayoutFragment.kt b/appintro/src/main/java/com/github/appintro/AppIntroCustomLayoutFragment.kt
index 80a04d02c..75a82b3e7 100644
--- a/appintro/src/main/java/com/github/appintro/AppIntroCustomLayoutFragment.kt
+++ b/appintro/src/main/java/com/github/appintro/AppIntroCustomLayoutFragment.kt
@@ -14,7 +14,6 @@ import androidx.fragment.app.Fragment
* to your AppIntro.
*/
class AppIntroCustomLayoutFragment : Fragment() {
-
private var layoutResId = 0
override fun onCreate(savedInstanceState: Bundle?) {
@@ -25,11 +24,12 @@ class AppIntroCustomLayoutFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
- savedInstanceState: Bundle?
+ savedInstanceState: Bundle?,
): View? = inflater.inflate(layoutResId, container, false)
companion object {
private const val ARG_LAYOUT_RES_ID = "layoutResId"
+
@JvmStatic
fun newInstance(layoutResId: Int): AppIntroCustomLayoutFragment {
val customSlide = AppIntroCustomLayoutFragment()
diff --git a/appintro/src/main/java/com/github/appintro/AppIntroFragment.kt b/appintro/src/main/java/com/github/appintro/AppIntroFragment.kt
index d83a5eeb8..3bfcf7c18 100644
--- a/appintro/src/main/java/com/github/appintro/AppIntroFragment.kt
+++ b/appintro/src/main/java/com/github/appintro/AppIntroFragment.kt
@@ -1,17 +1,16 @@
package com.github.appintro
import androidx.annotation.ColorInt
+import androidx.annotation.ColorRes
import androidx.annotation.DrawableRes
import androidx.annotation.FontRes
import com.github.appintro.model.SliderPage
@Suppress("LongParameterList")
class AppIntroFragment : AppIntroBaseFragment() {
-
override val layoutId: Int get() = R.layout.appintro_fragment_intro
companion object {
-
/**
* Generates a new instance for [AppIntroFragment]
*
@@ -32,18 +31,27 @@ class AppIntroFragment : AppIntroBaseFragment() {
*/
@JvmOverloads
@JvmStatic
+ @Deprecated(
+ "`newInstance` is deprecated to support color resources instead of color int " +
+ "for configuration changes and dark theme support",
+ ReplaceWith(
+ "createInstance(title, description, imageDrawable, backgroundColor, " +
+ "titleColor, descriptionColor, titleTypefaceFontRes, descriptionTypefaceFontRes, " +
+ "backgroundDrawable)",
+ ),
+ )
fun newInstance(
title: CharSequence? = null,
description: CharSequence? = null,
- @DrawableRes imageDrawable: Int = 0,
- @ColorInt backgroundColor: Int = 0,
- @ColorInt titleColor: Int = 0,
- @ColorInt descriptionColor: Int = 0,
- @FontRes titleTypefaceFontRes: Int = 0,
- @FontRes descriptionTypefaceFontRes: Int = 0,
- @DrawableRes backgroundDrawable: Int = 0
+ @DrawableRes imageDrawable: Int? = null,
+ @ColorInt backgroundColor: Int? = null,
+ @ColorInt titleColor: Int? = null,
+ @ColorInt descriptionColor: Int? = null,
+ @FontRes titleTypefaceFontRes: Int? = null,
+ @FontRes descriptionTypefaceFontRes: Int? = null,
+ @DrawableRes backgroundDrawable: Int? = null,
): AppIntroFragment {
- return newInstance(
+ return createInstance(
SliderPage(
title = title,
description = description,
@@ -53,11 +61,75 @@ class AppIntroFragment : AppIntroBaseFragment() {
descriptionColor = descriptionColor,
titleTypefaceFontRes = titleTypefaceFontRes,
descriptionTypefaceFontRes = descriptionTypefaceFontRes,
- backgroundDrawable = backgroundDrawable
- )
+ backgroundDrawable = backgroundDrawable,
+ ),
)
}
+ /**
+ * Generates a new instance for [AppIntroFragment]
+ *
+ * @param title CharSequence which will be the slide title
+ * @param description CharSequence which will be the slide description
+ * @param imageDrawable @DrawableRes (Integer) the image that will be
+ * displayed, obtained from Resources
+ * @param backgroundColorRes @ColorRes (Integer) custom background color
+ * @param titleColorRes @ColorRes (Integer) custom title color
+ * @param descriptionColorRes @ColorRes (Integer) custom description color
+ * @param titleTypefaceFontRes @FontRes (Integer) custom title typeface obtained
+ * from Resources
+ * @param descriptionTypefaceFontRes @FontRes (Integer) custom description typeface obtained
+ * from Resources
+ * @param backgroundDrawable @DrawableRes (Integer) custom background drawable
+ *
+ * @return An [AppIntroFragment] created instance
+ */
+ @JvmOverloads
+ @JvmStatic
+ fun createInstance(
+ title: CharSequence? = null,
+ description: CharSequence? = null,
+ @DrawableRes imageDrawable: Int? = null,
+ @ColorRes backgroundColorRes: Int? = null,
+ @ColorRes titleColorRes: Int? = null,
+ @ColorRes descriptionColorRes: Int? = null,
+ @FontRes titleTypefaceFontRes: Int? = null,
+ @FontRes descriptionTypefaceFontRes: Int? = null,
+ @DrawableRes backgroundDrawable: Int? = null,
+ ): AppIntroFragment {
+ return createInstance(
+ SliderPage(
+ title = title,
+ description = description,
+ imageDrawable = imageDrawable,
+ backgroundColorRes = backgroundColorRes,
+ titleColorRes = titleColorRes,
+ descriptionColorRes = descriptionColorRes,
+ titleTypefaceFontRes = titleTypefaceFontRes,
+ descriptionTypefaceFontRes = descriptionTypefaceFontRes,
+ backgroundDrawable = backgroundDrawable,
+ ),
+ )
+ }
+
+ /**
+ * Generates an [AppIntroFragment] from a given [SliderPage]
+ *
+ * @param sliderPage the [SliderPage] object which contains all attributes for
+ * the current slide
+ *
+ * @return An [AppIntroFragment] created instance
+ */
+ @JvmStatic
+ @Deprecated(
+ "`newInstance` is deprecated to support color resources instead of color int " +
+ "for configuration changes and dark theme support",
+ ReplaceWith(
+ "createInstance(sliderPage)",
+ ),
+ )
+ fun newInstance(sliderPage: SliderPage) = createInstance(sliderPage)
+
/**
* Generates an [AppIntroFragment] from a given [SliderPage]
*
@@ -67,7 +139,7 @@ class AppIntroFragment : AppIntroBaseFragment() {
* @return An [AppIntroFragment] created instance
*/
@JvmStatic
- fun newInstance(sliderPage: SliderPage): AppIntroFragment {
+ fun createInstance(sliderPage: SliderPage): AppIntroFragment {
val slide = AppIntroFragment()
slide.arguments = sliderPage.toBundle()
return slide
diff --git a/appintro/src/main/java/com/github/appintro/AppIntroFragmentViewModel.kt b/appintro/src/main/java/com/github/appintro/AppIntroFragmentViewModel.kt
new file mode 100644
index 000000000..b89b89510
--- /dev/null
+++ b/appintro/src/main/java/com/github/appintro/AppIntroFragmentViewModel.kt
@@ -0,0 +1,56 @@
+package com.github.appintro
+
+import androidx.annotation.ColorInt
+import androidx.annotation.ColorRes
+import androidx.annotation.FontRes
+import androidx.lifecycle.SavedStateHandle
+import androidx.lifecycle.ViewModel
+import com.github.appintro.internal.delegate
+
+internal const val ARG_TITLE = "title"
+internal const val ARG_TITLE_TYPEFACE_URL = "title_typeface"
+internal const val ARG_TITLE_TYPEFACE_RES = "title_typeface_res"
+internal const val ARG_DESC = "desc"
+internal const val ARG_DESC_TYPEFACE_URL = "desc_typeface"
+internal const val ARG_DESC_TYPEFACE_RES = "desc_typeface_res"
+internal const val ARG_DRAWABLE = "drawable"
+internal const val ARG_BG_COLOR = "bg_color"
+internal const val ARG_BG_COLOR_RES = "bg_color_res"
+internal const val ARG_TITLE_COLOR = "title_color"
+internal const val ARG_TITLE_COLOR_RES = "title_color_res"
+internal const val ARG_DESC_COLOR = "desc_color"
+internal const val ARG_DESC_COLOR_RES = "desc_color_res"
+internal const val ARG_BG_DRAWABLE = "bg_drawable"
+
+internal class AppIntroFragmentViewModel(state: SavedStateHandle) : ViewModel() {
+ internal var title by state.delegate(ARG_TITLE)
+ internal var description by state.delegate(ARG_DESC)
+ internal var drawable by state.delegate(ARG_DRAWABLE)
+ internal var bgDrawable by state.delegate(ARG_BG_DRAWABLE)
+
+ @get:ColorInt
+ internal var titleColor by state.delegate(ARG_TITLE_COLOR)
+
+ @get:ColorRes
+ internal var titleColorRes by state.delegate(ARG_TITLE_COLOR_RES)
+
+ @get:ColorInt
+ internal var descColor by state.delegate(ARG_DESC_COLOR)
+
+ @get:ColorRes
+ internal var descColorRes by state.delegate(ARG_DESC_COLOR_RES)
+
+ @get:ColorRes
+ internal var defaultBackgroundColorRes by state.delegate(ARG_BG_COLOR_RES)
+
+ @get:ColorInt
+ internal var defaultBackgroundColor by state.delegate(ARG_BG_COLOR)
+
+ @get:FontRes
+ internal var titleTypefaceRes by state.delegate(ARG_TITLE_TYPEFACE_RES)
+ internal var titleTypefaceUrl by state.delegate(ARG_TITLE_TYPEFACE_URL)
+
+ @get:FontRes
+ internal var descTypefaceRes by state.delegate(ARG_DESC_TYPEFACE_RES)
+ internal var descTypefaceUrl by state.delegate(ARG_DESC_TYPEFACE_URL)
+}
diff --git a/appintro/src/main/java/com/github/appintro/AppIntroPageTransformerType.kt b/appintro/src/main/java/com/github/appintro/AppIntroPageTransformerType.kt
index 5e1ea68a6..150a268e2 100644
--- a/appintro/src/main/java/com/github/appintro/AppIntroPageTransformerType.kt
+++ b/appintro/src/main/java/com/github/appintro/AppIntroPageTransformerType.kt
@@ -1,11 +1,12 @@
package com.github.appintro
+import androidx.annotation.IdRes
+
/**
* Sealed class to represent all the possible Page Transformers
* offered by AppIntro.
*/
sealed class AppIntroPageTransformerType {
-
/** Sets the animation of the intro to a flow animation */
object Flow : AppIntroPageTransformerType()
@@ -26,10 +27,16 @@ sealed class AppIntroPageTransformerType {
* @property titleParallaxFactor Parallax factor of title
* @property imageParallaxFactor Parallax factor of image
* @property descriptionParallaxFactor Parallax factor of description
+ * @property titleViewId The ID to use for the title view to animate
+ * @property imageViewId The ID to use for the image view to animate
+ * @property descriptionViewId The ID to use for the description view to animate
*/
class Parallax(
val titleParallaxFactor: Double = 1.0,
val imageParallaxFactor: Double = -1.0,
- val descriptionParallaxFactor: Double = 2.0
+ val descriptionParallaxFactor: Double = 2.0,
+ @IdRes val titleViewId: Int = R.id.title,
+ @IdRes val imageViewId: Int = R.id.image,
+ @IdRes val descriptionViewId: Int = R.id.description,
) : AppIntroPageTransformerType()
}
diff --git a/appintro/src/main/java/com/github/appintro/SlideBackgroundColorHolder.kt b/appintro/src/main/java/com/github/appintro/SlideBackgroundColorHolder.kt
index 0e35f3f42..d41aeda6a 100644
--- a/appintro/src/main/java/com/github/appintro/SlideBackgroundColorHolder.kt
+++ b/appintro/src/main/java/com/github/appintro/SlideBackgroundColorHolder.kt
@@ -1,22 +1,36 @@
package com.github.appintro
import androidx.annotation.ColorInt
+import androidx.annotation.ColorRes
interface SlideBackgroundColorHolder {
-
/**
* Returns the default background color of the slide
*
* @return The default background color of the slide
*/
@get:ColorInt
+ @Deprecated(
+ "`defaultBackgroundColor` has been deprecated to support configuration changes",
+ ReplaceWith("defaultBackgroundColorRes"),
+ )
val defaultBackgroundColor: Int
+ /**
+ * Returns the default background color of the slide
+ *
+ * @return The default background color of the slide
+ */
+ @get:ColorRes
+ val defaultBackgroundColorRes: Int
+
/**
* Sets the actual background color of the slide. This does not affect the default background color.
* This method should change the background color of the slide's root layout element (e.g. LinearLayout).
*
* @param backgroundColor New actual background color.
*/
- fun setBackgroundColor(@ColorInt backgroundColor: Int)
+ fun setBackgroundColor(
+ @ColorInt backgroundColor: Int,
+ )
}
diff --git a/appintro/src/main/java/com/github/appintro/SlidePolicy.kt b/appintro/src/main/java/com/github/appintro/SlidePolicy.kt
index 0ca97be30..02fb1b643 100644
--- a/appintro/src/main/java/com/github/appintro/SlidePolicy.kt
+++ b/appintro/src/main/java/com/github/appintro/SlidePolicy.kt
@@ -1,7 +1,6 @@
package com.github.appintro
interface SlidePolicy {
-
/**
* Whether the user has fulfilled the slides policy and should be allowed to navigate through the intro further.
* If false is returned, [.onUserIllegallyRequestedNextPage] will be called.
diff --git a/appintro/src/main/java/com/github/appintro/indicator/DotIndicatorController.kt b/appintro/src/main/java/com/github/appintro/indicator/DotIndicatorController.kt
index b910ca15a..57c815363 100644
--- a/appintro/src/main/java/com/github/appintro/indicator/DotIndicatorController.kt
+++ b/appintro/src/main/java/com/github/appintro/indicator/DotIndicatorController.kt
@@ -1,13 +1,13 @@
package com.github.appintro.indicator
import android.content.Context
-import android.graphics.PorterDuff
import android.view.Gravity
import android.view.Gravity.CENTER
import android.view.View
import android.widget.ImageView
import android.widget.LinearLayout
import androidx.core.content.ContextCompat
+import androidx.core.graphics.drawable.DrawableCompat
import com.github.appintro.R
/**
@@ -15,14 +15,13 @@ import com.github.appintro.R
* Use this when the number of page you're dealing with is not too high.
*/
class DotIndicatorController(context: Context) : IndicatorController, LinearLayout(context) {
-
- override var selectedIndicatorColor = -1
+ override var selectedIndicatorColor = ContextCompat.getColor(context, R.color.appintro_default_selected_color)
set(value) {
field = value
selectPosition(currentPosition)
}
- override var unselectedIndicatorColor = -1
+ override var unselectedIndicatorColor = ContextCompat.getColor(context, R.color.appintro_default_unselected_color)
set(value) {
field = value
selectPosition(currentPosition)
@@ -32,9 +31,11 @@ class DotIndicatorController(context: Context) : IndicatorController, LinearLayo
private var slideCount = 0
override fun newInstance(context: Context): View {
- val newLayoutParams = LayoutParams(
- LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT
- )
+ val newLayoutParams =
+ LayoutParams(
+ LayoutParams.WRAP_CONTENT,
+ LayoutParams.MATCH_PARENT,
+ )
newLayoutParams.gravity = Gravity.CENTER_VERTICAL
layoutParams = newLayoutParams
orientation = HORIZONTAL
@@ -44,20 +45,19 @@ class DotIndicatorController(context: Context) : IndicatorController, LinearLayo
override fun initialize(slideCount: Int) {
this.slideCount = slideCount
-
for (i in 0 until slideCount) {
val dot = ImageView(this.context)
dot.setImageDrawable(
- ContextCompat.getDrawable(
- this.context,
- R.drawable.ic_appintro_indicator_unselected
- )
- )
-
- val params = LayoutParams(
- LayoutParams.WRAP_CONTENT,
- LayoutParams.WRAP_CONTENT
+ ContextCompat.getDrawable(this.context, R.drawable.ic_appintro_indicator),
)
+ val params =
+ LayoutParams(
+ LayoutParams.WRAP_CONTENT,
+ LayoutParams.WRAP_CONTENT,
+ )
+ if (slideCount == 1) {
+ dot.visibility = View.INVISIBLE
+ }
this.addView(dot, params)
}
selectPosition(0)
@@ -66,24 +66,13 @@ class DotIndicatorController(context: Context) : IndicatorController, LinearLayo
override fun selectPosition(index: Int) {
currentPosition = index
for (i in 0 until slideCount) {
- val drawableId = if (i == index) {
- R.drawable.ic_appintro_indicator_selected
- } else { R.drawable.ic_appintro_indicator_unselected }
- val drawable = ContextCompat.getDrawable(this.context, drawableId)
-
- if (selectedIndicatorColor != DEFAULT_COLOR && i == index) {
- drawable!!.mutate().setColorFilter(
- selectedIndicatorColor,
- PorterDuff.Mode.SRC_IN
- )
- }
- if (unselectedIndicatorColor != DEFAULT_COLOR && i != index) {
- drawable!!.mutate().setColorFilter(
- unselectedIndicatorColor,
- PorterDuff.Mode.SRC_IN
- )
- }
- (getChildAt(i) as ImageView).setImageDrawable(drawable)
+ val tint =
+ if (i == index) {
+ selectedIndicatorColor
+ } else {
+ unselectedIndicatorColor
+ }
+ (getChildAt(i) as ImageView).let { DrawableCompat.setTint(it.drawable, tint) }
}
}
}
diff --git a/appintro/src/main/java/com/github/appintro/indicator/IndicatorController.kt b/appintro/src/main/java/com/github/appintro/indicator/IndicatorController.kt
index f2b250039..a5d1006d3 100644
--- a/appintro/src/main/java/com/github/appintro/indicator/IndicatorController.kt
+++ b/appintro/src/main/java/com/github/appintro/indicator/IndicatorController.kt
@@ -4,15 +4,12 @@ import android.content.Context
import android.view.View
import androidx.annotation.ColorInt
-internal const val DEFAULT_COLOR = 1
-
/**
* A controller that is used to provide custom indicator implementations and to control
* their behaviour. This is used for [AppIntro.setCustomIndicator] and
* [AppIntro2.setCustomIndicator]
*/
interface IndicatorController {
-
@get:ColorInt
var selectedIndicatorColor: Int
diff --git a/appintro/src/main/java/com/github/appintro/indicator/ProgressIndicatorController.kt b/appintro/src/main/java/com/github/appintro/indicator/ProgressIndicatorController.kt
index 5a56636d0..62676f343 100644
--- a/appintro/src/main/java/com/github/appintro/indicator/ProgressIndicatorController.kt
+++ b/appintro/src/main/java/com/github/appintro/indicator/ProgressIndicatorController.kt
@@ -1,9 +1,14 @@
package com.github.appintro.indicator
import android.content.Context
-import android.graphics.PorterDuff
import android.util.AttributeSet
+import android.view.View
import android.widget.ProgressBar
+import androidx.core.graphics.BlendModeColorFilterCompat
+import androidx.core.graphics.BlendModeCompat
+import com.github.appintro.internal.LayoutUtil
+
+internal const val DEFAULT_COLOR = 1
/**
* An [IndicatorController] that shows a [ProgressBar] for express the number of
@@ -11,32 +16,54 @@ import android.widget.ProgressBar
* Use this when the number of page is higher and the [DotIndicatorController]
* would not fit in the screen.
*/
-class ProgressIndicatorController @JvmOverloads constructor(
- context: Context,
- attrs: AttributeSet? = null,
- defStyleAttr: Int = android.R.attr.progressBarStyleHorizontal
-) : IndicatorController, ProgressBar(context, attrs, defStyleAttr) {
-
- override var selectedIndicatorColor = DEFAULT_COLOR
- set(value) {
- field = value
- progressDrawable.setColorFilter(value, PorterDuff.Mode.SRC_IN)
- }
+class ProgressIndicatorController
+ @JvmOverloads
+ constructor(
+ context: Context,
+ attrs: AttributeSet? = null,
+ defStyleAttr: Int = android.R.attr.progressBarStyleHorizontal,
+ ) : IndicatorController, ProgressBar(context, attrs, defStyleAttr) {
+ override var selectedIndicatorColor = DEFAULT_COLOR
+ set(value) {
+ field = value
+ progressDrawable.colorFilter =
+ BlendModeColorFilterCompat.createBlendModeColorFilterCompat(
+ value,
+ BlendModeCompat.SRC_ATOP,
+ )
+ }
- override var unselectedIndicatorColor = DEFAULT_COLOR
- set(value) {
- field = value
- indeterminateDrawable.setColorFilter(value, PorterDuff.Mode.SRC_IN)
- }
+ override var unselectedIndicatorColor = DEFAULT_COLOR
+ set(value) {
+ field = value
+ progressDrawable.colorFilter =
+ BlendModeColorFilterCompat.createBlendModeColorFilterCompat(
+ value,
+ BlendModeCompat.SRC_ATOP,
+ )
+ }
- override fun newInstance(context: Context) = this
+ override fun newInstance(context: Context) = this
- override fun initialize(slideCount: Int) {
- this.max = slideCount
- selectPosition(0)
- }
+ override fun initialize(slideCount: Int) {
+ this.max = slideCount
+ if (isRtl) {
+ this.scaleX = -1F
+ }
+ if (slideCount == 1) {
+ this.visibility = View.INVISIBLE
+ }
+ selectPosition(0)
+ }
+
+ override fun selectPosition(index: Int) {
+ this.progress =
+ if (isRtl) {
+ max - index
+ } else {
+ index + 1
+ }
+ }
- override fun selectPosition(index: Int) {
- this.progress = index + 1
+ private val isRtl: Boolean get() = LayoutUtil.isRtl(this.context)
}
-}
diff --git a/appintro/src/main/java/com/github/appintro/internal/AppIntroViewPager.kt b/appintro/src/main/java/com/github/appintro/internal/AppIntroViewPager.kt
deleted file mode 100644
index 629fe41d6..000000000
--- a/appintro/src/main/java/com/github/appintro/internal/AppIntroViewPager.kt
+++ /dev/null
@@ -1,223 +0,0 @@
-package com.github.appintro.internal
-
-import android.content.Context
-import android.util.AttributeSet
-import android.view.MotionEvent
-import android.view.animation.Interpolator
-import androidx.viewpager.widget.ViewPager
-import com.github.appintro.AppIntroBase
-import com.github.appintro.AppIntroPageTransformerType
-import com.github.appintro.AppIntroViewPagerListener
-import com.github.appintro.internal.viewpager.ViewPagerTransformer
-import kotlin.math.absoluteValue
-import kotlin.math.max
-
-/**
- * Class that controls the [AppIntro] of AppIntro.
- * This is responsible of handling of paging, managing touch and dispatching events.
- *
- * @property isFullPagingEnabled Enable or disable swiping at all.
- * @property isPermissionSlide If the current slide has permissions.
- * @property lockPage Set the page where the lock happened.
- * @property onNextPageRequestedListener Listener for Next Page events.
- * @property isNextPagingEnabled Enable or disable swiping to the next page.
- */
-internal class AppIntroViewPager(context: Context, attrs: AttributeSet) : ViewPager(context, attrs) {
-
- var isFullPagingEnabled = true
- var isPermissionSlide = false
- var lockPage = 0
- var onNextPageRequestedListener: AppIntroViewPagerListener? = null
- var isNextPagingEnabled: Boolean = true
- set(value) {
- field = value
- if (!value) {
- lockPage = currentItem
- }
- }
-
- private var currentTouchDownX: Float = 0.toFloat()
- private var currentTouchDownY: Float = 0.toFloat()
- private var illegallyRequestedNextPageLastCalled: Long = 0
- private var customScroller: ScrollerCustomDuration? = null
- private var pageChangeListener: OnPageChangeListener? = null
-
- init {
- // Override the Scroller instance with our own class so we can change the duration
- try {
- val scroller = ViewPager::class.java.getDeclaredField("mScroller")
- scroller.isAccessible = true
-
- val interpolator = ViewPager::class.java.getDeclaredField("sInterpolator")
- interpolator.isAccessible = true
-
- customScroller = ScrollerCustomDuration(context, interpolator.get(null) as Interpolator)
- scroller.set(this, customScroller)
- } catch (e: NoSuchFieldException) {
- e.printStackTrace()
- }
- }
-
- internal fun addOnPageChangeListener(listener: AppIntroBase.OnPageChangeListener) {
- super.addOnPageChangeListener(listener)
- this.pageChangeListener = listener
- }
-
- fun goToNextSlide() {
- currentItem += if (!LayoutUtil.isRtl(context)) 1 else -1
- }
-
- fun goToPreviousSlide() {
- currentItem += if (!LayoutUtil.isRtl(context)) -1 else 1
- }
-
- internal fun reCenterCurrentSlide() {
- // Hacky way to force a recenter of the ViewPager to the current slide.
- // We perform a page back and forward to recenter the ViewPager at the current position.
- // This is needed as we're interrupting the user Swipe due to Permissions.
- // If the user denies a permission, we want to recenter the slide.
- val item = currentItem
- setCurrentItem(max(item - 1, 0), false)
- setCurrentItem(item, false)
- }
-
- fun isFirstSlide(size: Int): Boolean {
- return if (LayoutUtil.isRtl(context)) (currentItem - size + 1 == 0) else (currentItem == 0)
- }
-
- fun getCurrentSlideNumber(size: Int): Int {
- return if (LayoutUtil.isRtl(context)) (size - currentItem) else (currentItem + 1)
- }
-
- /**
- * Override is required to trigger [AppIntroBase.OnPageChangeListener.onPageSelected] for the first page.
- * This is needed to correctly handle progress button display after rotation on a locked first page.
- */
- override fun setCurrentItem(currentItem: Int) {
- val oldItem = super.getCurrentItem()
- super.setCurrentItem(currentItem)
-
- // When you pass set current item to 0,
- // The pageChangeListener won't be called so we call it on our own
- if (oldItem == 0 && currentItem == 0) {
- pageChangeListener?.onPageSelected(0)
- }
- }
-
- /**
- * Set the factor by which the Scrolling duration will change.
- */
- fun setScrollDurationFactor(factor: Double) {
- customScroller?.scrollDurationFactor = factor
- }
-
- override fun performClick() = super.performClick()
-
- override fun onInterceptTouchEvent(event: MotionEvent): Boolean {
- if (!handleTouchEvent(event)) {
- return false
- }
-
- // Calling super will allow the slider to "work" left and right.
- return super.onInterceptTouchEvent(event)
- }
-
- override fun onTouchEvent(event: MotionEvent): Boolean {
- if (!handleTouchEvent(event)) {
- return false
- }
-
- // Calling super will allow the slider to "work" left and right.
- return super.onTouchEvent(event)
- }
-
- /**
- * Checks for illegal sliding attempts.
- * Every time the user presses the screen, the respective coordinates are stored.
- * Once the user swipes/stops pressing, the new coordinates are checked against the stored ones.
- * Therefor [userIllegallyRequestNextPage] is called. If this call detects an illegal swipe,
- * the respective listener [onNextPageRequestedListener] gets called.
- */
- private fun handleTouchEvent(event: MotionEvent): Boolean {
- // If paging is disabled we should ignore any viewpager touch
- // (also, not display any error message)
- if (!isFullPagingEnabled) {
- return false
- }
-
- when (event.action) {
- MotionEvent.ACTION_DOWN -> {
- currentTouchDownX = event.x
- currentTouchDownY = event.y
- }
- else -> {
- if (event.action == MotionEvent.ACTION_UP) {
- performClick()
- }
- val canRequestNextPage = onNextPageRequestedListener?.onCanRequestNextPage() ?: true
-
- // If user can't request the page, we shortcircuit the ACTION_MOVE logic here.
- // We need to return false, and also call onIllegallyRequestedNextPage if the
- // threshold was too high (so the user can be informed).
- if (!canRequestNextPage) {
- if (userIllegallyRequestNextPage(event)) {
- onNextPageRequestedListener?.onIllegallyRequestedNextPage()
- }
-
- return false
- }
-
- // If the slide contains permissions, check for forward swipe.
- if (isPermissionSlide && isSwipeForward(currentTouchDownX, event.x)) {
- onNextPageRequestedListener?.onUserRequestedPermissionsDialog()
- }
- }
- }
- return isFullPagingEnabled
- }
-
- /**
- * Util function to check if the user swiped forward.
- * The direction of forward is different in RTL mode.
- */
- private fun isSwipeForward(oldX: Float, newX: Float): Boolean {
- return (if (LayoutUtil.isRtl(context)) (newX > oldX) else (oldX > newX))
- }
-
- /**
- * Util function to check if the user illegally requests a swipe.
- * Throttles such requests to max one request per second.
- *
- * To prevent false positives one has to check that the user scrolls mainly horizontally
- * and the horizontal scrolling does not belong to a actual vertical scrolling.
- */
- private fun userIllegallyRequestNextPage(event: MotionEvent): Boolean {
- if (isASwipeGesture(event, currentTouchDownX, currentTouchDownY) &&
- System.currentTimeMillis() - illegallyRequestedNextPageLastCalled >=
- ON_ILLEGALLY_REQUESTED_NEXT_PAGE_MAX_INTERVAL
- ) {
- illegallyRequestedNextPageLastCalled = System.currentTimeMillis()
- return true
- }
-
- return false
- }
-
- /**
- * Checks if two points are aligned and could represent a slide gesture from the user.
- */
- private fun isASwipeGesture(startPoint: MotionEvent, x: Float, y: Float) = (
- (startPoint.x - x).absoluteValue >= VALID_SWIPE_THRESHOLD_PX_X &&
- (startPoint.y - y).absoluteValue <= VALID_SWIPE_THRESHOLD_PX_Y
- )
-
- fun setAppIntroPageTransformer(appIntroTransformer: AppIntroPageTransformerType) {
- setPageTransformer(true, ViewPagerTransformer(appIntroTransformer))
- }
-
- private companion object {
- private const val ON_ILLEGALLY_REQUESTED_NEXT_PAGE_MAX_INTERVAL = 1000
- private const val VALID_SWIPE_THRESHOLD_PX_X = 25
- private const val VALID_SWIPE_THRESHOLD_PX_Y = 25
- }
-}
diff --git a/appintro/src/main/java/com/github/appintro/internal/AppIntroViewPagerController.kt b/appintro/src/main/java/com/github/appintro/internal/AppIntroViewPagerController.kt
new file mode 100644
index 000000000..a19892b4b
--- /dev/null
+++ b/appintro/src/main/java/com/github/appintro/internal/AppIntroViewPagerController.kt
@@ -0,0 +1,302 @@
+package com.github.appintro.internal
+
+import android.gesture.GestureOverlayView
+import android.gesture.GestureOverlayView.OnGestureListener
+import android.view.MotionEvent
+import androidx.viewpager2.widget.ViewPager2
+import com.github.appintro.AppIntroBase
+import com.github.appintro.AppIntroPageTransformerType
+import com.github.appintro.AppIntroViewPagerListener
+import com.github.appintro.internal.viewpager.PagerAdapter
+import com.github.appintro.internal.viewpager.ViewPagerTransformer
+import kotlin.math.max
+
+/**
+ * Class that controls the [ViewPager2] of AppIntro.
+ * This is responsible of handling of paging, managing touch and dispatching events.
+ *
+ * @property isFullPagingEnabled Enable or disable swiping at all.
+ * @property isPermissionSlide If the current slide has permissions.
+ * @property onNextPageRequestedListener Listener for Next Page events.
+ */
+internal class AppIntroViewPagerController(
+ private val viewPager: ViewPager2,
+ private val viewPagerGestureOverlay: GestureOverlayView,
+) {
+ var isFullPagingEnabled = true
+ var isPermissionSlide = false
+ var onNextPageRequestedListener: AppIntroViewPagerListener? = null
+
+ private var currentTouchDownX: Float = 0.toFloat()
+ private var currentTouchDownY: Float = 0.toFloat()
+ private var illegallyRequestedNextPageLastCalled: Long = 0
+ private var pageChangeCallback: AppIntroBase.OnPageChangeCallback? = null
+
+ init {
+ addPagerTouchInterceptor()
+ }
+
+ fun goToNextSlide() {
+ with(viewPager) {
+ // avoid IllegalStateException when changing item while fake dragging
+ endFakeDrag()
+ setCurrentViewPagerItem(
+ position = if (!LayoutUtil.isRtl(context)) currentItem + 1 else currentItem - 1,
+ smoothScrool = true,
+ )
+ }
+ }
+
+ fun goToPreviousSlide() {
+ with(viewPager) {
+ // avoid IllegalStateException when changing item while fake dragging
+ endFakeDrag()
+ setCurrentViewPagerItem(
+ position = if (!LayoutUtil.isRtl(context)) currentItem - 1 else currentItem + 1,
+ smoothScrool = true,
+ )
+ }
+ }
+
+ fun isFirstSlide(size: Int): Boolean {
+ with(viewPager) {
+ return if (LayoutUtil.isRtl(context)) (currentItem - size + 1 == 0) else (currentItem == 0)
+ }
+ }
+
+ fun isLastSlide(size: Int): Boolean {
+ with(viewPager) {
+ return if (LayoutUtil.isRtl(context)) (currentItem == 0) else (currentItem - size + 1 == 0)
+ }
+ }
+
+ fun getCurrentSlideNumber(size: Int): Int {
+ with(viewPager) {
+ return if (LayoutUtil.isRtl(context)) (size - currentItem) else (currentItem + 1)
+ }
+ }
+
+ /**
+ * Override is required to trigger [AppIntroBase.OnPageChangeCallback.onPageSelected] for the first page.
+ * This is needed to correctly handle progress button display after rotation on a locked first page.
+ */
+ fun setCurrentViewPagerItem(
+ position: Int,
+ smoothScrool: Boolean = false,
+ ) {
+ with(viewPager) {
+ endFakeDrag()
+
+ val oldItem = currentItem
+ viewPager.setCurrentItem(position, smoothScrool)
+
+ // When you pass set current item to 0,
+ // The pageChangeListener won't be called so we call it on our own
+ if (oldItem == 0 && position == 0) {
+ pageChangeCallback?.onPageSelected(0)
+ }
+ }
+ }
+
+ /**
+ * Checks for illegal sliding attempts.
+ * Every time the user presses the screen, the respective coordinates are stored.
+ * Once the user swipes/stops pressing, the new coordinates are checked against the stored ones.
+ * Therefore [userIllegallyRequestNextPage] is called. If this call detects an illegal swipe,
+ * the respective listener [onNextPageRequestedListener] gets called.
+ */
+ private fun canPerformTouchEvent(event: MotionEvent?): Boolean {
+ // If paging is disabled we should ignore any viewpager touch
+ // (also, not display any error message)
+ if (!isFullPagingEnabled || event == null) {
+ return false
+ }
+
+ when (event.action) {
+ MotionEvent.ACTION_DOWN -> {
+ currentTouchDownX = event.x
+ currentTouchDownY = event.y
+ }
+ else -> {
+ if (event.action == MotionEvent.ACTION_UP) {
+ viewPager.performClick()
+ }
+ val canRequestNextPage = onNextPageRequestedListener?.onCanRequestNextPage() ?: true
+
+ // If user can't request the page, we shortcircuit the ACTION_MOVE logic here.
+ // We need to return false if we detect that the user swipes forward,
+ // and also call onIllegallyRequestedNextPage if the threshold was too high
+ // (so the user can be informed).
+ if (!canRequestNextPage && isSwipeForward(currentTouchDownX, event.x)) {
+ if (userIllegallyRequestNextPage()) {
+ onNextPageRequestedListener?.onIllegallyRequestedNextPage()
+ }
+ return false
+ }
+
+ // If the slide contains permissions, check for forward swipe.
+ if (isPermissionSlide && isSwipeForward(currentTouchDownX, event.x)) {
+ onNextPageRequestedListener?.onUserRequestedPermissionsDialog()
+ }
+ }
+ }
+
+ return isFullPagingEnabled
+ }
+
+ private var lastTouchValue: Float = 0f
+
+ /**
+ * Simulate touch events on the ViewPager2 using fakeDrag, since isUserInputEnabled = false
+ * We need this to eventually block user touches in forward if policy is not respected
+ */
+ private fun handleOnTouchEvent(event: MotionEvent?): Boolean {
+ if (!canPerformTouchEvent(event)) {
+ return false
+ }
+
+ // allow the slider to "work" left and right.
+ when (event?.action) {
+ MotionEvent.ACTION_DOWN -> {
+ lastTouchValue = event.x
+ if (!viewPager.isFakeDragging) {
+ viewPager.beginFakeDrag()
+ }
+ }
+
+ MotionEvent.ACTION_MOVE -> {
+ val value = event.x
+ val delta = value - lastTouchValue
+
+ viewPager.fakeDragBy(delta)
+ lastTouchValue = value
+ return true
+ }
+
+ MotionEvent.ACTION_CANCEL, MotionEvent.ACTION_UP -> {
+ viewPager.endFakeDrag()
+ }
+ }
+ return true
+ }
+
+ /**
+ * Util function to check if the user swiped forward.
+ * The direction of forward is different in RTL mode.
+ */
+ private fun isSwipeForward(
+ oldX: Float,
+ newX: Float,
+ ): Boolean {
+ with(viewPager) {
+ return (if (LayoutUtil.isRtl(context)) (newX > oldX) else (oldX > newX))
+ }
+ }
+
+ /**
+ * Util function to throttle illegallyRequestedNext to max one request per second.
+ */
+ private fun userIllegallyRequestNextPage(): Boolean {
+ if (System.currentTimeMillis() - illegallyRequestedNextPageLastCalled >=
+ ON_ILLEGALLY_REQUESTED_NEXT_PAGE_MAX_INTERVAL
+ ) {
+ illegallyRequestedNextPageLastCalled = System.currentTimeMillis()
+ return true
+ }
+
+ return false
+ }
+
+ /**
+ * Disables ViewPager swipes and handles manually touch events.
+ * This is because we may want to discard some swipes in a particular direction
+ * if policy doesn't allow that.
+ *
+ * Touch events are then forwarded (if policy allows that) to pager
+ * using fakeDrag feature of ViewPager2.
+ */
+ private fun addPagerTouchInterceptor() {
+ // disable ViewPager swipe
+ // touch events will be forwarded to gesture overlay to check policies
+ viewPager.isUserInputEnabled = false
+
+ viewPagerGestureOverlay.addOnGestureListener(
+ object : OnGestureListener {
+ override fun onGestureStarted(
+ overlayView: GestureOverlayView?,
+ event: MotionEvent?,
+ ) {
+ handleOnTouchEvent(event)
+ }
+
+ override fun onGesture(
+ overlayView: GestureOverlayView?,
+ event: MotionEvent?,
+ ) {
+ handleOnTouchEvent(event)
+ }
+
+ override fun onGestureEnded(
+ overlayView: GestureOverlayView?,
+ event: MotionEvent?,
+ ) {
+ handleOnTouchEvent(event)
+ }
+
+ override fun onGestureCancelled(
+ overlayView: GestureOverlayView?,
+ event: MotionEvent?,
+ ) {
+ handleOnTouchEvent(event)
+ }
+ },
+ )
+ }
+
+ internal fun reCenterCurrentSlide() {
+ // Hacky way to force a recenter of the ViewPager to the current slide.
+ // We perform a page back and forward to recenter the ViewPager at the current position.
+ // This is needed as we're interrupting the user Swipe due to Permissions.
+ // If the user denies a permission, we want to recenter the slide.
+ with(viewPager) {
+ val item = currentItem
+ setCurrentViewPagerItem(max(item - 1, 0), false)
+ setCurrentViewPagerItem(item, false)
+ }
+ }
+
+ internal fun registerOnPageChangeCallback(callback: AppIntroBase.OnPageChangeCallback) {
+ viewPager.registerOnPageChangeCallback(callback)
+ this.pageChangeCallback = callback
+ }
+
+ fun setAppIntroPageTransformer(appIntroTransformer: AppIntroPageTransformerType) {
+ with(viewPager) {
+ setPageTransformer(ViewPagerTransformer(appIntroTransformer))
+ }
+ }
+
+ fun setPageTransformer(pageTransformer: ViewPager2.PageTransformer?) {
+ with(viewPager) {
+ setPageTransformer(pageTransformer)
+ }
+ }
+
+ fun setOffscreenPageLimit(offscreenPageLimit: Int) {
+ viewPager.offscreenPageLimit = offscreenPageLimit
+ }
+
+ fun setAdapter(pagerAdapter: PagerAdapter) {
+ viewPager.adapter = pagerAdapter
+ }
+
+ fun post(function: () -> Unit) {
+ viewPager.post(function)
+ }
+
+ fun getCurrentItem() = viewPager.currentItem
+
+ private companion object {
+ private const val ON_ILLEGALLY_REQUESTED_NEXT_PAGE_MAX_INTERVAL = 1000
+ }
+}
diff --git a/appintro/src/main/java/com/github/appintro/internal/CustomFontCache.kt b/appintro/src/main/java/com/github/appintro/internal/CustomFontCache.kt
index 99e883c24..2f553741d 100644
--- a/appintro/src/main/java/com/github/appintro/internal/CustomFontCache.kt
+++ b/appintro/src/main/java/com/github/appintro/internal/CustomFontCache.kt
@@ -9,11 +9,14 @@ import androidx.core.content.res.ResourcesCompat
* Prevent(s) memory leaks due to Typeface objects
*/
internal object CustomFontCache {
-
private val TAG = LogHelper.makeLogTag(CustomFontCache::class)
private val cache = hashMapOf()
- fun getFont(ctx: Context, path: String?, fontCallback: ResourcesCompat.FontCallback) {
+ fun getFont(
+ ctx: Context,
+ path: String?,
+ fontCallback: ResourcesCompat.FontCallback,
+ ) {
if (path.isNullOrEmpty()) {
LogHelper.w(TAG, "Empty typeface path provided!")
return
diff --git a/appintro/src/main/java/com/github/appintro/internal/LayoutUtil.kt b/appintro/src/main/java/com/github/appintro/internal/LayoutUtil.kt
index e64826185..d9d66cc31 100644
--- a/appintro/src/main/java/com/github/appintro/internal/LayoutUtil.kt
+++ b/appintro/src/main/java/com/github/appintro/internal/LayoutUtil.kt
@@ -1,17 +1,14 @@
package com.github.appintro.internal
import android.content.Context
-import android.os.Build
import android.view.View
/**
* Util object for interacting with Layouts
*/
internal object LayoutUtil {
-
@JvmStatic
fun isRtl(ctx: Context): Boolean {
- return Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1 &&
- ctx.resources.configuration.layoutDirection == View.LAYOUT_DIRECTION_RTL
+ return ctx.resources.configuration.layoutDirection == View.LAYOUT_DIRECTION_RTL
}
}
diff --git a/appintro/src/main/java/com/github/appintro/internal/LogHelper.kt b/appintro/src/main/java/com/github/appintro/internal/LogHelper.kt
index d4dfbe8ae..0e8bb7031 100644
--- a/appintro/src/main/java/com/github/appintro/internal/LogHelper.kt
+++ b/appintro/src/main/java/com/github/appintro/internal/LogHelper.kt
@@ -11,7 +11,6 @@ private const val MAX_LOG_TAG_LENGTH = 23
* Helper object to interact with the Android [Log] class.
*/
internal object LogHelper {
-
/**
* Creates a tag for the logs from a [Class]
* Don't use this when obfuscating class names!
@@ -29,7 +28,10 @@ internal object LogHelper {
LOG_PREFIX +
cutTagLength(cls.simpleName ?: "", MAX_LOG_TAG_LENGTH - LOG_PREFIX_LENGTH)
- private fun cutTagLength(tag: String, length: Int): String {
+ private fun cutTagLength(
+ tag: String,
+ length: Int,
+ ): String {
return if (tag.length > length) {
tag.substring(0, length - 1)
} else {
@@ -38,29 +40,50 @@ internal object LogHelper {
}
@JvmStatic
- fun d(tag: String, message: String) = Log.d(tag, message)
+ fun d(
+ tag: String,
+ message: String,
+ ) = Log.d(tag, message)
@JvmStatic
- fun v(tag: String, message: String) = Log.v(tag, message)
+ fun v(
+ tag: String,
+ message: String,
+ ) = Log.v(tag, message)
@JvmStatic
- fun i(tag: String, message: String) = Log.i(tag, message)
+ fun i(
+ tag: String,
+ message: String,
+ ) = Log.i(tag, message)
@JvmOverloads
@JvmStatic
- fun w(tag: String, message: String, throwable: Throwable? = null) {
+ fun w(
+ tag: String,
+ message: String,
+ throwable: Throwable? = null,
+ ) {
Log.w(tag, message, throwable)
}
@JvmOverloads
@JvmStatic
- fun e(tag: String, message: String, throwable: Throwable? = null) {
+ fun e(
+ tag: String,
+ message: String,
+ throwable: Throwable? = null,
+ ) {
Log.e(tag, message, throwable)
}
@JvmOverloads
@JvmStatic
- fun wtf(tag: String, message: String, throwable: Throwable? = null) {
+ fun wtf(
+ tag: String,
+ message: String,
+ throwable: Throwable? = null,
+ ) {
Log.wtf(tag, message, throwable)
}
}
diff --git a/appintro/src/main/java/com/github/appintro/internal/PermissionWrapper.kt b/appintro/src/main/java/com/github/appintro/internal/PermissionWrapper.kt
index 7dfc3cf52..3d6e4a0b7 100644
--- a/appintro/src/main/java/com/github/appintro/internal/PermissionWrapper.kt
+++ b/appintro/src/main/java/com/github/appintro/internal/PermissionWrapper.kt
@@ -11,9 +11,8 @@ import java.io.Serializable
internal data class PermissionWrapper(
var permissions: Array,
var position: Int,
- var required: Boolean = true
+ var required: Boolean = true,
) : Serializable {
-
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
@@ -33,4 +32,8 @@ internal data class PermissionWrapper(
result = 31 * result + required.hashCode()
return result
}
+
+ companion object {
+ private const val serialVersionUID: Long = 1L
+ }
}
diff --git a/appintro/src/main/java/com/github/appintro/internal/SavedStateHelper.kt b/appintro/src/main/java/com/github/appintro/internal/SavedStateHelper.kt
new file mode 100644
index 000000000..83ee9e41e
--- /dev/null
+++ b/appintro/src/main/java/com/github/appintro/internal/SavedStateHelper.kt
@@ -0,0 +1,25 @@
+package com.github.appintro.internal
+
+import androidx.lifecycle.SavedStateHandle
+import kotlin.properties.ReadWriteProperty
+import kotlin.reflect.KProperty
+
+inline fun SavedStateHandle.delegate(key: String? = null): ReadWriteProperty =
+ object : ReadWriteProperty {
+ override fun getValue(
+ thisRef: Any,
+ property: KProperty<*>,
+ ): T? {
+ val stateKey = key ?: property.name
+ return this@delegate[stateKey]
+ }
+
+ override fun setValue(
+ thisRef: Any,
+ property: KProperty<*>,
+ value: T?,
+ ) {
+ val stateKey = key ?: property.name
+ this@delegate[stateKey] = value
+ }
+ }
diff --git a/appintro/src/main/java/com/github/appintro/internal/ScrollerCustomDuration.kt b/appintro/src/main/java/com/github/appintro/internal/ScrollerCustomDuration.kt
deleted file mode 100644
index 4ecfd8f72..000000000
--- a/appintro/src/main/java/com/github/appintro/internal/ScrollerCustomDuration.kt
+++ /dev/null
@@ -1,25 +0,0 @@
-package com.github.appintro.internal
-
-import android.content.Context
-import android.view.animation.Interpolator
-import android.widget.Scroller
-
-private const val DEFAULT_SCROLL_DURATION_FACTOR = 6.0
-
-/**
- * A [Scroller] that will allow to customize the duration of the scrolling.
- */
-internal class ScrollerCustomDuration constructor(
- context: Context,
- interpolator: Interpolator
-) : Scroller(context, interpolator) {
-
- /**
- * Set the factor by which the duration will change
- */
- var scrollDurationFactor = DEFAULT_SCROLL_DURATION_FACTOR
-
- override fun startScroll(startX: Int, startY: Int, dx: Int, dy: Int, duration: Int) {
- super.startScroll(startX, startY, dx, dy, (duration * scrollDurationFactor).toInt())
- }
-}
diff --git a/appintro/src/main/java/com/github/appintro/internal/TypefaceContainer.kt b/appintro/src/main/java/com/github/appintro/internal/TypefaceContainer.kt
index 0d277fcbc..c5079cf43 100644
--- a/appintro/src/main/java/com/github/appintro/internal/TypefaceContainer.kt
+++ b/appintro/src/main/java/com/github/appintro/internal/TypefaceContainer.kt
@@ -14,9 +14,8 @@ import androidx.core.content.res.ResourcesCompat
*/
internal data class TypefaceContainer(
var typeFaceUrl: String? = null,
- @FontRes var typeFaceResource: Int = 0
+ @FontRes var typeFaceResource: Int = 0,
) {
-
/**
* Applies typeface to a given TextView object.
* If there is no typeface (either URL or resource) set, this method is a no-op.
@@ -32,14 +31,16 @@ internal data class TypefaceContainer(
}
// Callback to font retrieval
- val callback = object : ResourcesCompat.FontCallback() {
- override fun onFontRetrievalFailed(reason: Int) {
- // Don't be panic, just do nothing.
- }
- override fun onFontRetrieved(typeface: Typeface) {
- textView.typeface = typeface
+ val callback =
+ object : ResourcesCompat.FontCallback() {
+ override fun onFontRetrievalFailed(reason: Int) {
+ // Don't be panic, just do nothing.
+ }
+
+ override fun onFontRetrieved(typeface: Typeface) {
+ textView.typeface = typeface
+ }
}
- }
// We give priority to the FontRes here.
if (typeFaceResource != 0) {
diff --git a/appintro/src/main/java/com/github/appintro/internal/VibrationHelper.kt b/appintro/src/main/java/com/github/appintro/internal/VibrationHelper.kt
new file mode 100644
index 000000000..18ae80bea
--- /dev/null
+++ b/appintro/src/main/java/com/github/appintro/internal/VibrationHelper.kt
@@ -0,0 +1,43 @@
+package com.github.appintro.internal
+
+import android.annotation.SuppressLint
+import android.content.Context
+import android.os.Build
+import android.os.VibrationEffect
+import android.os.Vibrator
+import android.os.VibratorManager
+import androidx.appcompat.app.AppCompatActivity
+import androidx.core.content.ContextCompat.getSystemService
+
+object VibrationHelper {
+ private var vibrator: Vibrator? = null
+
+ private fun initializeVibrator(context: Context) {
+ vibrator =
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
+ val vibratorManager =
+ context.getSystemService(Context.VIBRATOR_MANAGER_SERVICE) as VibratorManager
+ vibratorManager.defaultVibrator
+ } else {
+ @Suppress("DEPRECATION")
+ context.getSystemService(AppCompatActivity.VIBRATOR_SERVICE) as Vibrator
+ }
+ }
+
+ // You must grant vibration permissions on your AndroidManifest.xml file
+ @SuppressLint("MissingPermission")
+ fun vibrate(
+ context: Context,
+ vibrateDuration: Long,
+ ) {
+ if (vibrator == null) {
+ initializeVibrator(context)
+ }
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ vibrator?.vibrate(VibrationEffect.createOneShot(vibrateDuration, VibrationEffect.DEFAULT_AMPLITUDE))
+ } else {
+ @Suppress("DEPRECATION")
+ vibrator?.vibrate(vibrateDuration)
+ }
+ }
+}
diff --git a/appintro/src/main/java/com/github/appintro/internal/viewpager/PagerAdapter.kt b/appintro/src/main/java/com/github/appintro/internal/viewpager/PagerAdapter.kt
index 3b076363b..26b02fc32 100644
--- a/appintro/src/main/java/com/github/appintro/internal/viewpager/PagerAdapter.kt
+++ b/appintro/src/main/java/com/github/appintro/internal/viewpager/PagerAdapter.kt
@@ -1,17 +1,24 @@
package com.github.appintro.internal.viewpager
import androidx.fragment.app.Fragment
+import androidx.fragment.app.FragmentActivity
import androidx.fragment.app.FragmentManager
-import androidx.fragment.app.FragmentPagerAdapter
+import androidx.viewpager2.adapter.FragmentStateAdapter
internal class PagerAdapter(
- fragmentManager: FragmentManager,
- private val fragments: List
-) : FragmentPagerAdapter(fragmentManager, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
+ fragmentActivity: FragmentActivity,
+ private val fragments: MutableList,
+) : FragmentStateAdapter(fragmentActivity) {
+ override fun getItemCount() = this.fragments.size
- override fun getItem(position: Int): Fragment {
+ override fun createFragment(position: Int): Fragment {
return fragments[position]
}
- override fun getCount() = this.fragments.size
+ fun getItem(
+ position: Int,
+ fragmentManager: FragmentManager,
+ ): Fragment? {
+ return fragmentManager.findFragmentByTag("f$position")
+ }
}
diff --git a/appintro/src/main/java/com/github/appintro/internal/viewpager/ViewPagerTransformer.kt b/appintro/src/main/java/com/github/appintro/internal/viewpager/ViewPagerTransformer.kt
index f7998f5cb..26a2105d3 100644
--- a/appintro/src/main/java/com/github/appintro/internal/viewpager/ViewPagerTransformer.kt
+++ b/appintro/src/main/java/com/github/appintro/internal/viewpager/ViewPagerTransformer.kt
@@ -1,11 +1,11 @@
package com.github.appintro.internal.viewpager
import android.view.View
-import android.widget.ImageView
-import android.widget.TextView
-import androidx.viewpager.widget.ViewPager
+import androidx.viewpager2.widget.ViewPager2
import com.github.appintro.AppIntroPageTransformerType
-import com.github.appintro.R
+import com.github.appintro.internal.LogHelper
+import kotlin.math.abs
+import kotlin.math.max
private const val MIN_SCALE_DEPTH = 0.75f
private const val MIN_SCALE_ZOOM = 0.85f
@@ -16,14 +16,16 @@ private const val MIN_ALPHA_SLIDE = 0.35f
private const val FLOW_ROTATION_ANGLE = -30f
internal class ViewPagerTransformer(
- private val transformType: AppIntroPageTransformerType
-) : ViewPager.PageTransformer {
-
+ private val transformType: AppIntroPageTransformerType,
+) : ViewPager2.PageTransformer {
private var titlePF: Double = 0.0
private var imagePF: Double = 0.0
private var descriptionPF: Double = 0.0
- override fun transformPage(page: View, position: Float) {
+ override fun transformPage(
+ page: View,
+ position: Float,
+ ) {
when (transformType) {
AppIntroPageTransformerType.Flow -> {
page.rotationY = position * FLOW_ROTATION_ANGLE
@@ -36,32 +38,65 @@ internal class ViewPagerTransformer(
titlePF = transformType.titleParallaxFactor
imagePF = transformType.imageParallaxFactor
descriptionPF = transformType.descriptionParallaxFactor
- transformParallax(position, page)
+ transformParallax(
+ position,
+ page,
+ transformType.titleViewId,
+ transformType.imageViewId,
+ transformType.descriptionViewId,
+ )
}
}
}
- private fun transformParallax(position: Float, page: View) {
+ private fun transformParallax(
+ position: Float,
+ page: View,
+ titleViewId: Int,
+ imageViewId: Int,
+ descriptionViewId: Int,
+ ) {
if (position > -1 && position < 1) {
try {
- applyParallax(page, position)
+ applyParallax(page, position, titleViewId, titlePF, "title")
+ applyParallax(page, position, imageViewId, imagePF, "image")
+ applyParallax(page, position, descriptionViewId, descriptionPF, "description")
} catch (e: IllegalStateException) {
- e.printStackTrace()
+ LogHelper.e(TAG, "Failed to apply parallax effect", e)
}
}
}
- private fun applyParallax(page: View, position: Float) {
- page.findViewById(R.id.title).translationX = computeParallax(page, position, titlePF)
- page.findViewById(R.id.image).translationX = computeParallax(page, position, imagePF)
- page.findViewById(R.id.description).translationX = computeParallax(page, position, descriptionPF)
+ private fun applyParallax(
+ page: View,
+ position: Float,
+ viewId: Int,
+ parallaxFactor: Double,
+ logLabel: String,
+ ) {
+ page.findViewById(viewId)?.let {
+ it.translationX = computeParallax(page, position, parallaxFactor)
+ } ?: {
+ LogHelper.e(
+ TAG,
+ "Could not parallax animate view '$logLabel' as " +
+ "the provided view ID can't be found in the layout",
+ )
+ }
}
- private fun computeParallax(page: View, position: Float, parallaxFactor: Double): Float {
+ private fun computeParallax(
+ page: View,
+ position: Float,
+ parallaxFactor: Double,
+ ): Float {
return (-position * (page.width / parallaxFactor)).toFloat()
}
- private fun transformFade(position: Float, page: View) {
+ private fun transformFade(
+ position: Float,
+ page: View,
+ ) {
if (position <= -1.0f || position >= 1.0f) {
page.translationX = page.width.toFloat()
page.alpha = 0.0f
@@ -73,13 +108,16 @@ internal class ViewPagerTransformer(
} else {
// position is between -1.0F & 0.0F OR 0.0F & 1.0F
page.translationX = page.width * -position
- page.alpha = 1.0f - Math.abs(position)
+ page.alpha = 1.0f - abs(position)
}
}
- private fun transformZoom(position: Float, page: View) {
+ private fun transformZoom(
+ position: Float,
+ page: View,
+ ) {
if (position >= -1 && position <= 1) {
- page.scaleXY = Math.max(MIN_SCALE_ZOOM, 1 - Math.abs(position))
+ page.scaleXY = max(MIN_SCALE_ZOOM, 1 - abs(position))
page.alpha = MIN_ALPHA_ZOOM + (page.scaleXY - MIN_SCALE_ZOOM) /
(1 - MIN_SCALE_ZOOM) * (1 - MIN_ALPHA_ZOOM)
val vMargin = page.height * (1 - page.scaleXY) / 2
@@ -94,22 +132,28 @@ internal class ViewPagerTransformer(
}
}
- private fun transformDepth(position: Float, page: View) {
+ private fun transformDepth(
+ position: Float,
+ page: View,
+ ) {
if (position > 0 && position < 1) {
// moving to the right
page.alpha = 1 - position
- page.scaleXY = MIN_SCALE_DEPTH + (1 - MIN_SCALE_DEPTH) * (1 - Math.abs(position))
+ page.scaleXY = MIN_SCALE_DEPTH + (1 - MIN_SCALE_DEPTH) * (1 - abs(position))
page.translationX = page.width * -position
} else {
page.transformDefaults()
}
}
- private fun transformSlideOver(position: Float, page: View) {
+ private fun transformSlideOver(
+ position: Float,
+ page: View,
+ ) {
if (position < 0 && position > -1) {
// this is the page to the left
- page.scaleXY = Math.abs(Math.abs(position) - 1) * (1.0f - SCALE_FACTOR_SLIDE) + SCALE_FACTOR_SLIDE
- page.alpha = Math.max(MIN_ALPHA_SLIDE, 1 - Math.abs(position))
+ page.scaleXY = abs(abs(position) - 1) * (1.0f - SCALE_FACTOR_SLIDE) + SCALE_FACTOR_SLIDE
+ page.alpha = max(MIN_ALPHA_SLIDE, 1 - abs(position))
val pageWidth = page.width
val translateValue = position * -pageWidth
if (translateValue > -pageWidth) {
@@ -121,6 +165,10 @@ internal class ViewPagerTransformer(
page.transformDefaults()
}
}
+
+ companion object {
+ private val TAG = LogHelper.makeLogTag(ViewPagerTransformer::class)
+ }
}
private fun View.transformDefaults() {
diff --git a/appintro/src/main/java/com/github/appintro/model/SliderPage.kt b/appintro/src/main/java/com/github/appintro/model/SliderPage.kt
index 189064ed8..802d731e2 100644
--- a/appintro/src/main/java/com/github/appintro/model/SliderPage.kt
+++ b/appintro/src/main/java/com/github/appintro/model/SliderPage.kt
@@ -2,58 +2,88 @@ package com.github.appintro.model
import android.os.Bundle
import androidx.annotation.ColorInt
+import androidx.annotation.ColorRes
import androidx.annotation.DrawableRes
import androidx.annotation.FontRes
import com.github.appintro.ARG_BG_COLOR
+import com.github.appintro.ARG_BG_COLOR_RES
import com.github.appintro.ARG_BG_DRAWABLE
import com.github.appintro.ARG_DESC
import com.github.appintro.ARG_DESC_COLOR
-import com.github.appintro.ARG_DESC_TYPEFACE
+import com.github.appintro.ARG_DESC_COLOR_RES
import com.github.appintro.ARG_DESC_TYPEFACE_RES
+import com.github.appintro.ARG_DESC_TYPEFACE_URL
import com.github.appintro.ARG_DRAWABLE
import com.github.appintro.ARG_TITLE
import com.github.appintro.ARG_TITLE_COLOR
-import com.github.appintro.ARG_TITLE_TYPEFACE
+import com.github.appintro.ARG_TITLE_COLOR_RES
import com.github.appintro.ARG_TITLE_TYPEFACE_RES
+import com.github.appintro.ARG_TITLE_TYPEFACE_URL
/**
* Slide Page Model.
*
* This data class represent a single page that can be visualized with AppIntro.
*/
-data class SliderPage @JvmOverloads constructor(
- var title: CharSequence? = null,
- var description: CharSequence? = null,
- @DrawableRes var imageDrawable: Int = 0,
- @ColorInt var backgroundColor: Int = 0,
- @ColorInt var titleColor: Int = 0,
- @ColorInt var descriptionColor: Int = 0,
- @FontRes var titleTypefaceFontRes: Int = 0,
- @FontRes var descriptionTypefaceFontRes: Int = 0,
- var titleTypeface: String? = null,
- var descriptionTypeface: String? = null,
- @DrawableRes var backgroundDrawable: Int = 0
-) {
- val titleString: String? get() = title?.toString()
- val descriptionString: String? get() = description?.toString()
+data class SliderPage
+ @JvmOverloads
+ constructor(
+ var title: CharSequence? = null,
+ var description: CharSequence? = null,
+ @DrawableRes var imageDrawable: Int? = null,
+ @ColorInt
+ @Deprecated(
+ "`backgroundColor` has been deprecated to support configuration changes",
+ ReplaceWith("backgroundColorRes"),
+ )
+ var backgroundColor: Int? = null,
+ @ColorInt
+ @Deprecated(
+ "`titleColor` has been deprecated to support configuration changes",
+ ReplaceWith("titleColorRes"),
+ )
+ var titleColor: Int? = null,
+ @ColorInt
+ @Deprecated(
+ "`descriptionColor` has been deprecated to support configuration changes",
+ ReplaceWith("descriptionColorRes"),
+ )
+ var descriptionColor: Int? = null,
+ @ColorRes var backgroundColorRes: Int? = null,
+ @ColorRes var titleColorRes: Int? = null,
+ @ColorRes var descriptionColorRes: Int? = null,
+ @FontRes var titleTypefaceFontRes: Int? = null,
+ @FontRes var descriptionTypefaceFontRes: Int? = null,
+ var titleTypeface: String? = null,
+ var descriptionTypeface: String? = null,
+ @DrawableRes var backgroundDrawable: Int? = null,
+ ) {
+ val titleString: CharSequence? get() = title
+ val descriptionString: CharSequence? get() = description
- /**
- * Util method to convert a [SliderPage] into an Android [Bundle].
- * This method will be used to pass the [SliderPage] to [AppIntroBaseFragment] implementations.
- */
- fun toBundle(): Bundle {
- val newBundle = Bundle()
- newBundle.putString(ARG_TITLE, this.titleString)
- newBundle.putString(ARG_TITLE_TYPEFACE, this.titleTypeface)
- newBundle.putInt(ARG_TITLE_TYPEFACE_RES, this.titleTypefaceFontRes)
- newBundle.putInt(ARG_TITLE_COLOR, this.titleColor)
- newBundle.putString(ARG_DESC, this.descriptionString)
- newBundle.putString(ARG_DESC_TYPEFACE, this.descriptionTypeface)
- newBundle.putInt(ARG_DESC_TYPEFACE_RES, this.descriptionTypefaceFontRes)
- newBundle.putInt(ARG_DESC_COLOR, this.descriptionColor)
- newBundle.putInt(ARG_DRAWABLE, this.imageDrawable)
- newBundle.putInt(ARG_BG_COLOR, this.backgroundColor)
- newBundle.putInt(ARG_BG_DRAWABLE, this.backgroundDrawable)
- return newBundle
+ /**
+ * Util method to convert a [SliderPage] into an Android [Bundle].
+ * This method will be used to pass the [SliderPage] to [AppIntroBaseFragment] implementations.
+ */
+ @Suppress("DEPRECATION")
+ fun toBundle(): Bundle {
+ val newBundle = Bundle()
+ newBundle.putCharSequence(ARG_TITLE, this.titleString)
+ newBundle.putString(ARG_TITLE_TYPEFACE_URL, this.titleTypeface)
+ newBundle.putCharSequence(ARG_DESC, this.descriptionString)
+ newBundle.putString(ARG_DESC_TYPEFACE_URL, this.descriptionTypeface)
+
+ this.titleTypefaceFontRes?.let { newBundle.putInt(ARG_TITLE_TYPEFACE_RES, it) }
+ this.titleColor?.let { newBundle.putInt(ARG_TITLE_COLOR, it) }
+ this.titleColorRes?.let { newBundle.putInt(ARG_TITLE_COLOR_RES, it) }
+ this.descriptionTypefaceFontRes?.let { newBundle.putInt(ARG_DESC_TYPEFACE_RES, it) }
+ this.descriptionColor?.let { newBundle.putInt(ARG_DESC_COLOR, it) }
+ this.descriptionColorRes?.let { newBundle.putInt(ARG_DESC_COLOR_RES, it) }
+ this.imageDrawable?.let { newBundle.putInt(ARG_DRAWABLE, it) }
+ this.backgroundColor?.let { newBundle.putInt(ARG_BG_COLOR, it) }
+ this.backgroundColorRes?.let { newBundle.putInt(ARG_BG_COLOR_RES, it) }
+ this.backgroundDrawable?.let { newBundle.putInt(ARG_BG_DRAWABLE, it) }
+
+ return newBundle
+ }
}
-}
diff --git a/appintro/src/main/java/com/github/appintro/model/SliderPagerBuilder.kt b/appintro/src/main/java/com/github/appintro/model/SliderPagerBuilder.kt
index ab0adf284..380b270f7 100644
--- a/appintro/src/main/java/com/github/appintro/model/SliderPagerBuilder.kt
+++ b/appintro/src/main/java/com/github/appintro/model/SliderPagerBuilder.kt
@@ -1,6 +1,7 @@
package com.github.appintro.model
import androidx.annotation.ColorInt
+import androidx.annotation.ColorRes
import androidx.annotation.DrawableRes
import androidx.annotation.FontRes
@@ -10,35 +11,43 @@ import androidx.annotation.FontRes
* a [SliderPage] directly.
*/
class SliderPagerBuilder {
-
private var title: CharSequence? = null
private var description: CharSequence? = null
@DrawableRes
- private var imageDrawable: Int = 0
+ private var imageDrawable: Int? = null
@ColorInt
- private var backgroundColor: Int = 0
+ private var backgroundColor: Int? = null
+
+ @ColorRes
+ private var backgroundColorRes: Int? = null
@ColorInt
- private var titleColor: Int = 0
+ private var titleColor: Int? = null
+
+ @ColorRes
+ private var titleColorRes: Int? = null
@ColorInt
- private var descriptionColor: Int = 0
+ private var descriptionColor: Int? = null
+
+ @ColorRes
+ private var descriptionColorRes: Int? = null
@FontRes
- private var titleTypefaceFontRes: Int = 0
+ private var titleTypefaceFontRes: Int? = null
@FontRes
- private var descriptionTypefaceFontRes: Int = 0
+ private var descriptionTypefaceFontRes: Int? = null
private var titleTypeface: String? = null
private var descriptionTypeface: String? = null
@DrawableRes
- private var backgroundDrawable: Int = 0
+ private var backgroundDrawable: Int? = null
fun title(title: CharSequence): SliderPagerBuilder {
this.title = title
@@ -50,32 +59,77 @@ class SliderPagerBuilder {
return this
}
- fun imageDrawable(@DrawableRes imageDrawable: Int): SliderPagerBuilder {
+ fun imageDrawable(
+ @DrawableRes imageDrawable: Int,
+ ): SliderPagerBuilder {
this.imageDrawable = imageDrawable
return this
}
- fun backgroundColor(@ColorInt backgroundColor: Int): SliderPagerBuilder {
+ @Deprecated(
+ "`backgroundColor(...)` has been deprecated to support configuration changes",
+ ReplaceWith("backgroundColorRes(backgroundColor)"),
+ )
+ fun backgroundColor(
+ @ColorInt backgroundColor: Int,
+ ): SliderPagerBuilder {
this.backgroundColor = backgroundColor
return this
}
- fun titleColor(@ColorInt titleColor: Int): SliderPagerBuilder {
+ fun backgroundColorRes(
+ @ColorRes backgroundColorRes: Int,
+ ): SliderPagerBuilder {
+ this.backgroundColorRes = backgroundColorRes
+ return this
+ }
+
+ @Deprecated(
+ "`titleColor(...)` has been deprecated to support configuration changes",
+ ReplaceWith("titleColorRes(titleColor)"),
+ )
+ fun titleColor(
+ @ColorInt titleColor: Int,
+ ): SliderPagerBuilder {
this.titleColor = titleColor
return this
}
- fun descriptionColor(@ColorInt descriptionColor: Int): SliderPagerBuilder {
+ fun titleColorRes(
+ @ColorRes titleColorRes: Int,
+ ): SliderPagerBuilder {
+ this.titleColorRes = titleColorRes
+ return this
+ }
+
+ @Deprecated(
+ "`descriptionColor(...)` has been deprecated to support configuration changes",
+ ReplaceWith("descriptionColorRes(descriptionColor)"),
+ )
+ fun descriptionColor(
+ @ColorInt descriptionColor: Int,
+ ): SliderPagerBuilder {
this.descriptionColor = descriptionColor
return this
}
- fun titleTypefaceFontRes(@FontRes titleTypefaceFontRes: Int): SliderPagerBuilder {
+ fun descriptionColorRes(
+ @ColorRes descriptionColorRes: Int,
+ ): SliderPagerBuilder {
+ this.descriptionColorRes = descriptionColorRes
+ return this
+ }
+
+ fun titleTypefaceFontRes(
+ @FontRes titleTypefaceFontRes: Int,
+ ): SliderPagerBuilder {
this.titleTypefaceFontRes = titleTypefaceFontRes
return this
}
- fun descriptionTypefaceFontRes(@FontRes descriptionTypefaceFontRes: Int): SliderPagerBuilder {
+ fun descriptionTypefaceFontRes(
+ @FontRes descriptionTypefaceFontRes: Int,
+ ): SliderPagerBuilder {
this.descriptionTypefaceFontRes = descriptionTypefaceFontRes
return this
}
@@ -90,22 +144,28 @@ class SliderPagerBuilder {
return this
}
- fun backgroundDrawable(@DrawableRes backgroundDrawable: Int): SliderPagerBuilder {
+ fun backgroundDrawable(
+ @DrawableRes backgroundDrawable: Int,
+ ): SliderPagerBuilder {
this.backgroundDrawable = backgroundDrawable
return this
}
- fun build() = SliderPage(
- title = this.title,
- description = this.description,
- imageDrawable = this.imageDrawable,
- backgroundColor = this.backgroundColor,
- titleColor = this.titleColor,
- descriptionColor = this.descriptionColor,
- titleTypefaceFontRes = this.titleTypefaceFontRes,
- descriptionTypeface = this.descriptionTypeface,
- titleTypeface = this.titleTypeface,
- descriptionTypefaceFontRes = this.descriptionTypefaceFontRes,
- backgroundDrawable = this.backgroundDrawable
- )
+ fun build() =
+ SliderPage(
+ title = this.title,
+ description = this.description,
+ imageDrawable = this.imageDrawable,
+ backgroundColor = this.backgroundColor,
+ backgroundColorRes = this.backgroundColorRes,
+ titleColor = this.titleColor,
+ titleColorRes = this.titleColorRes,
+ descriptionColor = this.descriptionColor,
+ descriptionColorRes = this.descriptionColorRes,
+ titleTypefaceFontRes = this.titleTypefaceFontRes,
+ descriptionTypeface = this.descriptionTypeface,
+ titleTypeface = this.titleTypeface,
+ descriptionTypefaceFontRes = this.descriptionTypefaceFontRes,
+ backgroundDrawable = this.backgroundDrawable,
+ )
}
diff --git a/appintro/src/main/res/drawable-v21/ic_appintro_ripple.xml b/appintro/src/main/res/drawable-v21/ic_appintro_ripple.xml
deleted file mode 100644
index 7ec20e180..000000000
--- a/appintro/src/main/res/drawable-v21/ic_appintro_ripple.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
- -
-
-
-
-
-
diff --git a/appintro/src/main/res/drawable/ic_appintro_indicator_selected.xml b/appintro/src/main/res/drawable/ic_appintro_indicator.xml
similarity index 86%
rename from appintro/src/main/res/drawable/ic_appintro_indicator_selected.xml
rename to appintro/src/main/res/drawable/ic_appintro_indicator.xml
index 31f37bebe..30f193f34 100644
--- a/appintro/src/main/res/drawable/ic_appintro_indicator_selected.xml
+++ b/appintro/src/main/res/drawable/ic_appintro_indicator.xml
@@ -6,7 +6,7 @@
android:right="@dimen/appintro_indicator_inset"
android:top="@dimen/appintro_indicator_inset">
-
+
diff --git a/appintro/src/main/res/drawable/ic_appintro_indicator_unselected.xml b/appintro/src/main/res/drawable/ic_appintro_indicator_unselected.xml
deleted file mode 100644
index d328b9c0d..000000000
--- a/appintro/src/main/res/drawable/ic_appintro_indicator_unselected.xml
+++ /dev/null
@@ -1,15 +0,0 @@
-
-
- -
-
-
-
-
-
-
diff --git a/appintro/src/main/res/drawable/ic_appintro_ripple.xml b/appintro/src/main/res/drawable/ic_appintro_ripple.xml
index a90d72dfd..a194c18a0 100644
--- a/appintro/src/main/res/drawable/ic_appintro_ripple.xml
+++ b/appintro/src/main/res/drawable/ic_appintro_ripple.xml
@@ -1,4 +1,9 @@
-
-
-
\ No newline at end of file
+
+ -
+
+
+
+
+
\ No newline at end of file
diff --git a/appintro/src/main/res/layout-land/appintro_fragment_intro.xml b/appintro/src/main/res/layout-land/appintro_fragment_intro.xml
index 394dfe7a5..d1346da6e 100644
--- a/appintro/src/main/res/layout-land/appintro_fragment_intro.xml
+++ b/appintro/src/main/res/layout-land/appintro_fragment_intro.xml
@@ -13,12 +13,14 @@
android:id="@+id/title"
style="@style/AppIntroDefaultHeading"
android:layout_width="0dp"
- android:layout_height="wrap_content"
+ android:layout_height="0dp"
+ app:layout_constrainedWidth="true"
app:layout_constraintBottom_toTopOf="@+id/description"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="@+id/midline"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_chainStyle="spread"
+ app:layout_constraintVertical_weight="2"
tools:text="Welcome" />
+ app:layout_constraintTop_toBottomOf="@+id/title"
+ app:layout_constraintVertical_weight="5" />
diff --git a/appintro/src/main/res/layout/appintro_intro_layout.xml b/appintro/src/main/res/layout/appintro_intro_layout.xml
index 859377808..ef28eaf88 100644
--- a/appintro/src/main/res/layout/appintro_intro_layout.xml
+++ b/appintro/src/main/res/layout/appintro_intro_layout.xml
@@ -5,20 +5,26 @@
android:id="@+id/background"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:background="@color/appintro_background_color"
android:fitsSystemWindows="false"
android:theme="@style/AppIntroStyle">
-
+ app:layout_constraintTop_toTopOf="parent">
+
+
+
@@ -55,6 +62,7 @@
android:visibility="invisible"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toTopOf="@+id/bottom"
app:srcCompat="@drawable/ic_appintro_next" />
+ tools:background="@drawable/ic_appintro_indicator" />
diff --git a/appintro/src/main/res/layout/appintro_intro_layout2.xml b/appintro/src/main/res/layout/appintro_intro_layout2.xml
index aff6dc9b3..704b8a4a4 100644
--- a/appintro/src/main/res/layout/appintro_intro_layout2.xml
+++ b/appintro/src/main/res/layout/appintro_intro_layout2.xml
@@ -5,20 +5,26 @@
android:id="@+id/background"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:background="@color/appintro_background_color"
android:fitsSystemWindows="false"
android:theme="@style/AppIntroStyle">
-
+ app:layout_constraintTop_toTopOf="parent">
+
+
+
+ tools:background="@drawable/ic_appintro_indicator" />
diff --git a/appintro/src/main/res/values-az/strings.xml b/appintro/src/main/res/values-az/strings.xml
new file mode 100644
index 000000000..29ca9d486
--- /dev/null
+++ b/appintro/src/main/res/values-az/strings.xml
@@ -0,0 +1,8 @@
+
+
+ KEÇ
+ NÖVBƏTİ
+ GERİ
+ BİTDİ
+ qrafika
+
diff --git a/appintro/src/main/res/values-bg/strings.xml b/appintro/src/main/res/values-bg/strings.xml
new file mode 100644
index 000000000..c19356cdc
--- /dev/null
+++ b/appintro/src/main/res/values-bg/strings.xml
@@ -0,0 +1,8 @@
+
+
+ ПРОПУСНИ
+ СЛЕДВАЩ
+ НАЗАД
+ ГОТОВО
+ графики
+
\ No newline at end of file
diff --git a/appintro/src/main/res/values-bn-rIN/strings.xml b/appintro/src/main/res/values-bn-rIN/strings.xml
new file mode 100644
index 000000000..dfa88e098
--- /dev/null
+++ b/appintro/src/main/res/values-bn-rIN/strings.xml
@@ -0,0 +1,8 @@
+
+
+ এড়িয়ে যান
+ সম্পন্ন
+ পরবর্তী
+ পেছনে
+ চিত্র
+
\ No newline at end of file
diff --git a/appintro/src/main/res/values-es/strings.xml b/appintro/src/main/res/values-es/strings.xml
index 370e5231b..791a7f48d 100644
--- a/appintro/src/main/res/values-es/strings.xml
+++ b/appintro/src/main/res/values-es/strings.xml
@@ -1,5 +1,8 @@
-
+
SALTAR
+ SIGUIENTE
+ ATRÁS
HECHO
+ gráficos
diff --git a/appintro/src/main/res/values-fa/strings.xml b/appintro/src/main/res/values-fa/strings.xml
index bf751ff45..99e2dc7f4 100644
--- a/appintro/src/main/res/values-fa/strings.xml
+++ b/appintro/src/main/res/values-fa/strings.xml
@@ -1,5 +1,9 @@
بگذر
+ بعدی
+ بازگشت
پایان
+ تصویر
+
diff --git a/appintro/src/main/res/values-fr/strings.xml b/appintro/src/main/res/values-fr/strings.xml
index 71bbf2d43..77ebd6f89 100644
--- a/appintro/src/main/res/values-fr/strings.xml
+++ b/appintro/src/main/res/values-fr/strings.xml
@@ -1,5 +1,8 @@
PASSER
+ SUIVANT
+ PRÉCÉDENT
TERMINER
+ graphiques
diff --git a/appintro/src/main/res/values-gu/strings.xml b/appintro/src/main/res/values-gu/strings.xml
new file mode 100644
index 000000000..00f9222df
--- /dev/null
+++ b/appintro/src/main/res/values-gu/strings.xml
@@ -0,0 +1,8 @@
+
+
+ અવગણો
+ આગળ
+ પાછા
+ થઈ ગયું
+ ગ્રાફિક્સ
+
\ No newline at end of file
diff --git a/appintro/src/main/res/values-hu/strings.xml b/appintro/src/main/res/values-hu/strings.xml
index a78579c4a..4406ebb9d 100644
--- a/appintro/src/main/res/values-hu/strings.xml
+++ b/appintro/src/main/res/values-hu/strings.xml
@@ -1,5 +1,8 @@
-
+
KIHAGYÁS
+ TOVÁBB
+ VISSZA
KÉSZ
+ GRAFIKA
diff --git a/appintro/src/main/res/values-id/strings.xml b/appintro/src/main/res/values-id/strings.xml
deleted file mode 100644
index 58be36252..000000000
--- a/appintro/src/main/res/values-id/strings.xml
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
- LEWATI
- LANJUT
- KEMBALI
- SELESAI
- grafik
-
diff --git a/appintro/src/main/res/values-in/strings.xml b/appintro/src/main/res/values-in/strings.xml
index c7866de31..58be36252 100644
--- a/appintro/src/main/res/values-in/strings.xml
+++ b/appintro/src/main/res/values-in/strings.xml
@@ -1,5 +1,8 @@
-
+
LEWATI
+ LANJUT
+ KEMBALI
SELESAI
+ grafik
diff --git a/appintro/src/main/res/values-ja/strings.xml b/appintro/src/main/res/values-ja/strings.xml
new file mode 100644
index 000000000..2ee126278
--- /dev/null
+++ b/appintro/src/main/res/values-ja/strings.xml
@@ -0,0 +1,8 @@
+
+
+ スキップ
+ 次へ
+ 前へ
+ 完了
+ 画像
+
diff --git a/appintro/src/main/res/values-ne/strings.xml b/appintro/src/main/res/values-ne/strings.xml
new file mode 100644
index 000000000..e260a5da5
--- /dev/null
+++ b/appintro/src/main/res/values-ne/strings.xml
@@ -0,0 +1,8 @@
+
+
+छोड्नुहोस्
+अर्को
+फिर्ता
+पूरा भयो
+चित्रहरू
+
diff --git a/appintro/src/main/res/values-pt-rBR/strings.xml b/appintro/src/main/res/values-pt-rBR/strings.xml
index dfa89fb91..da98dbeb4 100644
--- a/appintro/src/main/res/values-pt-rBR/strings.xml
+++ b/appintro/src/main/res/values-pt-rBR/strings.xml
@@ -1,5 +1,8 @@
-
+
PULAR
+ PRÓXIMO
+ VOLTAR
PRONTO
+ imagem
diff --git a/appintro/src/main/res/values-ro/strings.xml b/appintro/src/main/res/values-ro/strings.xml
new file mode 100644
index 000000000..049e2eb70
--- /dev/null
+++ b/appintro/src/main/res/values-ro/strings.xml
@@ -0,0 +1,8 @@
+
+
+ SARI PESTE
+ URMĂTORUL
+ ÎNAPOI
+ GATA
+ grafică
+
diff --git a/appintro/src/main/res/values-ru/strings.xml b/appintro/src/main/res/values-ru/strings.xml
index d1df750d5..a5ffd75ef 100644
--- a/appintro/src/main/res/values-ru/strings.xml
+++ b/appintro/src/main/res/values-ru/strings.xml
@@ -1,5 +1,8 @@
- Пропустить
- Готово
+ ПРОПУСТИТЬ
+ ДАЛЕЕ
+ НАЗАД
+ ГОТОВО
+ изображение
diff --git a/appintro/src/main/res/values-sw600dp/dimen-sw600dp.xml b/appintro/src/main/res/values-sw600dp/dimen-sw600dp.xml
new file mode 100644
index 000000000..639494205
--- /dev/null
+++ b/appintro/src/main/res/values-sw600dp/dimen-sw600dp.xml
@@ -0,0 +1,22 @@
+
+
+
+ 30sp
+ 42sp
+ 24sp
+ 2sp
+
+ 12dp
+ 92dp
+ 92dp
+
+ 24dp
+ 98dp
+ 80dp
+
+ 24sp
+ 24sp
+ 100dp
+ 100dp
+
+
\ No newline at end of file
diff --git a/appintro/src/main/res/values-ta/strings.xml b/appintro/src/main/res/values-ta/strings.xml
new file mode 100644
index 000000000..1b53967b1
--- /dev/null
+++ b/appintro/src/main/res/values-ta/strings.xml
@@ -0,0 +1,8 @@
+
+
+ தவிர்
+ அடுத்து
+ பின்னால்
+ முடிந்தது
+ வரைகலை
+
diff --git a/appintro/src/main/res/values-uk-rUA/strings.xml b/appintro/src/main/res/values-uk-rUA/strings.xml
new file mode 100644
index 000000000..9b0b96cbe
--- /dev/null
+++ b/appintro/src/main/res/values-uk-rUA/strings.xml
@@ -0,0 +1,8 @@
+
+
+ ПРОПУСТИТИ
+ ДАЛІ
+ НАЗАД
+ ГОТОВО
+ зображення
+
\ No newline at end of file
diff --git a/appintro/src/main/res/values-v21/styles.xml b/appintro/src/main/res/values-v21/styles.xml
deleted file mode 100644
index ad0486e7e..000000000
--- a/appintro/src/main/res/values-v21/styles.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
-
-
-
-
diff --git a/appintro/src/main/res/values-yo/strings.xml b/appintro/src/main/res/values-yo/strings.xml
new file mode 100644
index 000000000..6f449ab6a
--- /dev/null
+++ b/appintro/src/main/res/values-yo/strings.xml
@@ -0,0 +1,8 @@
+
+
+ Ti pari
+ Foo
+ Eyi to kan
+ Pada
+ Aworan
+
\ No newline at end of file
diff --git a/appintro/src/main/res/values-zh-rCN/strings.xml b/appintro/src/main/res/values-zh-rCN/strings.xml
index ad23a23e2..f18ccfeed 100644
--- a/appintro/src/main/res/values-zh-rCN/strings.xml
+++ b/appintro/src/main/res/values-zh-rCN/strings.xml
@@ -1,5 +1,8 @@
-
+
跳过
+ 下一页
+ 上一页
完成
+ 图片
diff --git a/appintro/src/main/res/values-zh-rTW/strings.xml b/appintro/src/main/res/values-zh-rTW/strings.xml
index 9a15514d0..720426d56 100644
--- a/appintro/src/main/res/values-zh-rTW/strings.xml
+++ b/appintro/src/main/res/values-zh-rTW/strings.xml
@@ -1,5 +1,8 @@
跳過
+ 下一頁
+ 上一頁
完成
+ 圖片
diff --git a/appintro/src/main/res/values/dimen.xml b/appintro/src/main/res/values/dimen.xml
index d3514a199..10a1df869 100644
--- a/appintro/src/main/res/values/dimen.xml
+++ b/appintro/src/main/res/values/dimen.xml
@@ -22,4 +22,9 @@
16dp
8dp
28sp
+ 14sp
+ 14sp
+ 12sp
+ 2sp
+
diff --git a/appintro/src/main/res/values/styles.xml b/appintro/src/main/res/values/styles.xml
index e72a73c40..48304bf19 100644
--- a/appintro/src/main/res/values/styles.xml
+++ b/appintro/src/main/res/values/styles.xml
@@ -1,7 +1,11 @@
-
+
-
-
diff --git a/example/src/main/res/values-w820dp/dimens.xml b/example/src/main/res/values-w820dp/dimens.xml
deleted file mode 100644
index 63fc81644..000000000
--- a/example/src/main/res/values-w820dp/dimens.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
- 64dp
-
diff --git a/example/src/main/res/values/colors.xml b/example/src/main/res/values/colors.xml
index 1d8a5b95d..3e8fd6e04 100644
--- a/example/src/main/res/values/colors.xml
+++ b/example/src/main/res/values/colors.xml
@@ -1,5 +1,7 @@
- #1976D2
- #96ffffff
+ #EF6C00
+ #CDDC39
+ #448AFF
+ #607D8B
\ No newline at end of file
diff --git a/example/src/main/res/values/dimens.xml b/example/src/main/res/values/dimens.xml
index 47c822467..1cfb8e04e 100644
--- a/example/src/main/res/values/dimens.xml
+++ b/example/src/main/res/values/dimens.xml
@@ -1,5 +1,4 @@
-
- 16dp
- 16dp
+ 16dp
+ 8dp
diff --git a/example/src/main/res/values/ic_launcher_background.xml b/example/src/main/res/values/ic_launcher_background.xml
deleted file mode 100644
index 524b2120a..000000000
--- a/example/src/main/res/values/ic_launcher_background.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
- #F4A517
-
\ No newline at end of file
diff --git a/example/src/main/res/values/strings.xml b/example/src/main/res/values/strings.xml
index 05dfa9eef..7538fa11c 100644
--- a/example/src/main/res/values/strings.xml
+++ b/example/src/main/res/values/strings.xml
@@ -1,18 +1,52 @@
- AppIntro Example
- Drawer Header
- Launch Demo
+ AppIntro Sample App
+ appintro_logo
+ Start
+ This app will show you some of the features of AppIntro. You can find more documentation on github.com/AppIntro/AppIntro
+
+
+ Default Intro
+ Java Default Intro
+ Default Intro 2
+ Custom Layout Intro
+ Custom Background Intro
+ Set Bar Margin
+ Slide Policy Intro
+ Permissions Intro
+ Permissions Intro 2
- This is the most basic form of the AppIntro library. Minimal code is required to achieve this, and there are two layout options available. Tap the button below to launch the demo.
- This is the second most basic form of the AppIntro library. Minimal code is required to achieve ths, and this is the second layout option that the library offers. Tap the button below to launch the demo.
- This demonstrates the ability to pass a custom layout to the library for added customization. Tap the button below to launch the demo.
- This demonstrates the ability to set custom backgrounds for different slides. Useful if you want images vs. colors in your backgrounds. Tap the button below to launch the demo.
+ This is the basic layout of the AppIntro library.
+ This is the basic layout of the AppIntro library in Java.
+ This is the second layout provided by AppIntro library, with icons instead of text
+ This demonstrates the ability to pass a custom layout to the library for added customization.
+ This demonstrates the ability to set custom backgrounds for different slides. Useful if you want images vs. colors in your backgrounds.
+ This demonstrates the SlidePolicy feature, with the possibility to block a user during a Intro.
+ This demonstrates the possibility to add some padding to the bottom of the slide to that it doesn\'t overlap with the bottom bar.
- This demonstrates the ability to request permission on any slide specified. Tap the button below to launch the demo.
- This demonstrates the ability to request permission on any slide specified, only it\'s using the second slide layout. Tap the button below to launch the demo.
+ This demonstrates the ability to request runtime permission on any desired slide.
+ This demonstrates the ability to request permission on any slide specified using the second slide layout.
+
+
+ Please select the Checkbox before proceeding
+ SlidePolicy Example
+ This slide won\'t allow the user to proceed till you click the following checkbox
+ Please check to continue
+
+
+ Enable / Disable margin
+ This is not visible since the bar overlaps.
+
+
+ Sample Layout 1
+ Button 1
+ Sample Layout 2
+ Button 2
+ Sample Layout 3
+ Button 3
+ Sample Layout 4
+ Button 4
-
diff --git a/example/src/main/res/values/styles.xml b/example/src/main/res/values/styles.xml
index e391794e1..ff1c56816 100644
--- a/example/src/main/res/values/styles.xml
+++ b/example/src/main/res/values/styles.xml
@@ -1,15 +1,8 @@
-
-
-
-
diff --git a/example/src/media/cover.png b/example/src/media/cover.png
deleted file mode 100644
index 108045d5f..000000000
Binary files a/example/src/media/cover.png and /dev/null differ
diff --git a/example/src/media/screen0.png b/example/src/media/screen0.png
deleted file mode 100644
index 26b906dfe..000000000
Binary files a/example/src/media/screen0.png and /dev/null differ
diff --git a/example/src/media/screen1.png b/example/src/media/screen1.png
deleted file mode 100644
index 09b2c3230..000000000
Binary files a/example/src/media/screen1.png and /dev/null differ
diff --git a/example/src/media/screen2.png b/example/src/media/screen2.png
deleted file mode 100644
index fc9f49485..000000000
Binary files a/example/src/media/screen2.png and /dev/null differ
diff --git a/gradle.properties b/gradle.properties
index 8cee72bc1..01236edf1 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -16,18 +16,4 @@ org.gradle.jvmargs=-Xmx1536m
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true
-VERSION_NAME=6.0.0
-GROUP=com.github.appintro
-
-POM_DESCRIPTION=AppIntro library
-POM_URL=https://github.com/AppIntro/AppIntro
-POM_SCM_URL=https://github.com/AppIntro/AppIntro
-POM_SCM_CONNECTION=git@github.com:AppIntro/AppIntro.git
-POM_SCM_DEV_CONNECTION=git@github.com:AppIntro/AppIntro.git
-POM_LICENCE_NAME=Apache License
-POM_LICENCE_URL=http://www.apache.org/licenses/LICENSE-2.0
-POM_LICENCE_DIST=repo
-POM_DEVELOPER_ID=AppIntro
-POM_DEVELOPER_NAME=AppIntro Developers
-
android.useAndroidX=true
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
new file mode 100644
index 000000000..979450536
--- /dev/null
+++ b/gradle/libs.versions.toml
@@ -0,0 +1,37 @@
+[versions]
+agp = "8.13.1"
+appcompat = "1.7.1"
+annotation = "1.9.1"
+cardview = "1.0.0"
+compile_sdk_version = "34"
+constraintlayout = "2.2.1"
+core_ktx = "1.13.1"
+detekt = "1.23.8"
+junit = "4.13.2"
+kotlin = "2.2.21"
+ktlint_gradle = "12.3.0"
+min_sdk_version = "21"
+mockito_core = "5.21.0"
+recyclerview = "1.3.2"
+viewpager2 = "1.1.0"
+target_sdk_version = "34"
+fragment = "1.8.9"
+
+[libraries]
+androidx_annotation = { module = "androidx.annotation:annotation", version.ref = "annotation" }
+androidx_appcompat = { module = "androidx.appcompat:appcompat", version.ref = "appcompat" }
+androidx_cardview = { module = "androidx.cardview:cardview", version.ref = "cardview" }
+androidx_constraintlayout = { module = "androidx.constraintlayout:constraintlayout", version.ref = "constraintlayout" }
+androidx_core_ktx = { module = "androidx.core:core-ktx", version.ref = "core.ktx" }
+androidx_recyclerview = { module = "androidx.recyclerview:recyclerview", version.ref = "recyclerview" }
+androidx_viewpager2 = { module = "androidx.viewpager2:viewpager2", version.ref = "viewpager2"}
+androidx_fragment = { module = "androidx.fragment:fragment-ktx", version.ref = "fragment"}
+junit = { module = "junit:junit", version.ref = "junit" }
+mockito_core = { module = "org.mockito:mockito-core", version.ref = "mockito.core" }
+
+[plugins]
+application = { id = "com.android.application", version.ref = "agp" }
+kotlin_android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
+library = { id = "com.android.library", version.ref = "agp" }
+detekt = { id = "io.gitlab.arturbosch.detekt", version.ref = "detekt" }
+ktlint = { id = "org.jlleitschuh.gradle.ktlint", version.ref = "ktlint.gradle" }
\ No newline at end of file
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
index 29953ea14..1b33c55ba 100644
Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index 31a0802f3..d4081da47 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,5 +1,7 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-6.1-all.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip
+networkTimeout=10000
+validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
diff --git a/gradlew b/gradlew
index cccdd3d51..23d15a936 100755
--- a/gradlew
+++ b/gradlew
@@ -1,78 +1,129 @@
-#!/usr/bin/env sh
+#!/bin/sh
+
+#
+# Copyright © 2015-2021 the original authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# SPDX-License-Identifier: Apache-2.0
+#
##############################################################################
-##
-## Gradle start up script for UN*X
-##
+#
+# Gradle start up script for POSIX generated by Gradle.
+#
+# Important for running:
+#
+# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
+# noncompliant, but you have some other compliant shell such as ksh or
+# bash, then to run this script, type that shell name before the whole
+# command line, like:
+#
+# ksh Gradle
+#
+# Busybox and similar reduced shells will NOT work, because this script
+# requires all of these POSIX shell features:
+# * functions;
+# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
+# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
+# * compound commands having a testable exit status, especially «case»;
+# * various built-in commands including «command», «set», and «ulimit».
+#
+# Important for patching:
+#
+# (2) This script targets any POSIX shell, so it avoids extensions provided
+# by Bash, Ksh, etc; in particular arrays are avoided.
+#
+# The "traditional" practice of packing multiple parameters into a
+# space-separated string is a well documented source of bugs and security
+# problems, so this is (mostly) avoided, by progressively accumulating
+# options in "$@", and eventually passing that to Java.
+#
+# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
+# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
+# see the in-line comments for details.
+#
+# There are tweaks for specific operating systems such as AIX, CygWin,
+# Darwin, MinGW, and NonStop.
+#
+# (3) This script is generated from the Groovy template
+# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
+# within the Gradle project.
+#
+# You can find Gradle at https://github.com/gradle/gradle/.
+#
##############################################################################
# Attempt to set APP_HOME
+
# Resolve links: $0 may be a link
-PRG="$0"
-# Need this for relative symlinks.
-while [ -h "$PRG" ] ; do
- ls=`ls -ld "$PRG"`
- link=`expr "$ls" : '.*-> \(.*\)$'`
- if expr "$link" : '/.*' > /dev/null; then
- PRG="$link"
- else
- PRG=`dirname "$PRG"`"/$link"
- fi
+app_path=$0
+
+# Need this for daisy-chained symlinks.
+while
+ APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
+ [ -h "$app_path" ]
+do
+ ls=$( ls -ld "$app_path" )
+ link=${ls#*' -> '}
+ case $link in #(
+ /*) app_path=$link ;; #(
+ *) app_path=$APP_HOME$link ;;
+ esac
done
-SAVED="`pwd`"
-cd "`dirname \"$PRG\"`/" >/dev/null
-APP_HOME="`pwd -P`"
-cd "$SAVED" >/dev/null
-
-APP_NAME="Gradle"
-APP_BASE_NAME=`basename "$0"`
-# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
-DEFAULT_JVM_OPTS=""
+# This is normally unused
+# shellcheck disable=SC2034
+APP_BASE_NAME=${0##*/}
+# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
+APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value.
-MAX_FD="maximum"
+MAX_FD=maximum
warn () {
echo "$*"
-}
+} >&2
die () {
echo
echo "$*"
echo
exit 1
-}
+} >&2
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
-case "`uname`" in
- CYGWIN* )
- cygwin=true
- ;;
- Darwin* )
- darwin=true
- ;;
- MINGW* )
- msys=true
- ;;
- NONSTOP* )
- nonstop=true
- ;;
+case "$( uname )" in #(
+ CYGWIN* ) cygwin=true ;; #(
+ Darwin* ) darwin=true ;; #(
+ MSYS* | MINGW* ) msys=true ;; #(
+ NONSTOP* ) nonstop=true ;;
esac
-CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+CLASSPATH="\\\"\\\""
+
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
- JAVACMD="$JAVA_HOME/jre/sh/java"
+ JAVACMD=$JAVA_HOME/jre/sh/java
else
- JAVACMD="$JAVA_HOME/bin/java"
+ JAVACMD=$JAVA_HOME/bin/java
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
@@ -81,92 +132,120 @@ Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
- JAVACMD="java"
- which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+ JAVACMD=java
+ if ! command -v java >/dev/null 2>&1
+ then
+ die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
+ fi
fi
# Increase the maximum file descriptors if we can.
-if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
- MAX_FD_LIMIT=`ulimit -H -n`
- if [ $? -eq 0 ] ; then
- if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
- MAX_FD="$MAX_FD_LIMIT"
- fi
- ulimit -n $MAX_FD
- if [ $? -ne 0 ] ; then
- warn "Could not set maximum file descriptor limit: $MAX_FD"
- fi
- else
- warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
- fi
+if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
+ case $MAX_FD in #(
+ max*)
+ # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
+ # shellcheck disable=SC2039,SC3045
+ MAX_FD=$( ulimit -H -n ) ||
+ warn "Could not query maximum file descriptor limit"
+ esac
+ case $MAX_FD in #(
+ '' | soft) :;; #(
+ *)
+ # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
+ # shellcheck disable=SC2039,SC3045
+ ulimit -n "$MAX_FD" ||
+ warn "Could not set maximum file descriptor limit to $MAX_FD"
+ esac
fi
-# For Darwin, add options to specify how the application appears in the dock
-if $darwin; then
- GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
-fi
+# Collect all arguments for the java command, stacking in reverse order:
+# * args from the command line
+# * the main class name
+# * -classpath
+# * -D...appname settings
+# * --module-path (only if needed)
+# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
+
+# For Cygwin or MSYS, switch paths to Windows format before running java
+if "$cygwin" || "$msys" ; then
+ APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
+ CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
+
+ JAVACMD=$( cygpath --unix "$JAVACMD" )
-# For Cygwin, switch paths to Windows format before running java
-if $cygwin ; then
- APP_HOME=`cygpath --path --mixed "$APP_HOME"`
- CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
- JAVACMD=`cygpath --unix "$JAVACMD"`
-
- # We build the pattern for arguments to be converted via cygpath
- ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
- SEP=""
- for dir in $ROOTDIRSRAW ; do
- ROOTDIRS="$ROOTDIRS$SEP$dir"
- SEP="|"
- done
- OURCYGPATTERN="(^($ROOTDIRS))"
- # Add a user-defined pattern to the cygpath arguments
- if [ "$GRADLE_CYGPATTERN" != "" ] ; then
- OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
- fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
- i=0
- for arg in "$@" ; do
- CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
- CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
-
- if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
- eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
- else
- eval `echo args$i`="\"$arg\""
+ for arg do
+ if
+ case $arg in #(
+ -*) false ;; # don't mess with options #(
+ /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
+ [ -e "$t" ] ;; #(
+ *) false ;;
+ esac
+ then
+ arg=$( cygpath --path --ignore --mixed "$arg" )
fi
- i=$((i+1))
+ # Roll the args list around exactly as many times as the number of
+ # args, so each arg winds up back in the position where it started, but
+ # possibly modified.
+ #
+ # NB: a `for` loop captures its iteration list before it begins, so
+ # changing the positional parameters here affects neither the number of
+ # iterations, nor the values presented in `arg`.
+ shift # remove old arg
+ set -- "$@" "$arg" # push replacement arg
done
- case $i in
- (0) set -- ;;
- (1) set -- "$args0" ;;
- (2) set -- "$args0" "$args1" ;;
- (3) set -- "$args0" "$args1" "$args2" ;;
- (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
- (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
- (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
- (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
- (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
- (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
- esac
fi
-# Escape application args
-save () {
- for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
- echo " "
-}
-APP_ARGS=$(save "$@")
-
-# Collect all arguments for the java command, following the shell quoting and substitution rules
-eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
-# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
-if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
- cd "$(dirname "$0")"
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+
+# Collect all arguments for the java command:
+# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
+# and any embedded shellness will be escaped.
+# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
+# treated as '${Hostname}' itself on the command line.
+
+set -- \
+ "-Dorg.gradle.appname=$APP_BASE_NAME" \
+ -classpath "$CLASSPATH" \
+ -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
+ "$@"
+
+# Stop when "xargs" is not available.
+if ! command -v xargs >/dev/null 2>&1
+then
+ die "xargs is not available"
fi
+# Use "xargs" to parse quoted args.
+#
+# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
+#
+# In Bash we could simply go:
+#
+# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
+# set -- "${ARGS[@]}" "$@"
+#
+# but POSIX shell has neither arrays nor command substitution, so instead we
+# post-process each arg (as a line of input to sed) to backslash-escape any
+# character that might be a shell metacharacter, then use eval to reverse
+# that process (while maintaining the separation between arguments), and wrap
+# the whole thing up as a single "set" statement.
+#
+# This will of course break if any of these variables contains a newline or
+# an unmatched quote.
+#
+
+eval "set -- $(
+ printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
+ xargs -n1 |
+ sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
+ tr '\n' ' '
+ )" '"$@"'
+
exec "$JAVACMD" "$@"
diff --git a/gradlew.bat b/gradlew.bat
index e95643d6a..5eed7ee84 100644
--- a/gradlew.bat
+++ b/gradlew.bat
@@ -1,4 +1,22 @@
-@if "%DEBUG%" == "" @echo off
+@rem
+@rem Copyright 2015 the original author or authors.
+@rem
+@rem Licensed under the Apache License, Version 2.0 (the "License");
+@rem you may not use this file except in compliance with the License.
+@rem You may obtain a copy of the License at
+@rem
+@rem https://www.apache.org/licenses/LICENSE-2.0
+@rem
+@rem Unless required by applicable law or agreed to in writing, software
+@rem distributed under the License is distributed on an "AS IS" BASIS,
+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+@rem See the License for the specific language governing permissions and
+@rem limitations under the License.
+@rem
+@rem SPDX-License-Identifier: Apache-2.0
+@rem
+
+@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@@ -9,25 +27,29 @@
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
-if "%DIRNAME%" == "" set DIRNAME=.
+if "%DIRNAME%"=="" set DIRNAME=.
+@rem This is normally unused
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
+@rem Resolve any "." and ".." in APP_HOME to make it shorter.
+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
+
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
-set DEFAULT_JVM_OPTS=
+set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
-if "%ERRORLEVEL%" == "0" goto init
+if %ERRORLEVEL% equ 0 goto execute
-echo.
-echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
-echo.
-echo Please set the JAVA_HOME variable in your environment to match the
-echo location of your Java installation.
+echo. 1>&2
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
+echo. 1>&2
+echo Please set the JAVA_HOME variable in your environment to match the 1>&2
+echo location of your Java installation. 1>&2
goto fail
@@ -35,48 +57,36 @@ goto fail
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
-if exist "%JAVA_EXE%" goto init
+if exist "%JAVA_EXE%" goto execute
-echo.
-echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
-echo.
-echo Please set the JAVA_HOME variable in your environment to match the
-echo location of your Java installation.
+echo. 1>&2
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
+echo. 1>&2
+echo Please set the JAVA_HOME variable in your environment to match the 1>&2
+echo location of your Java installation. 1>&2
goto fail
-:init
-@rem Get command-line arguments, handling Windows variants
-
-if not "%OS%" == "Windows_NT" goto win9xME_args
-
-:win9xME_args
-@rem Slurp the command line arguments.
-set CMD_LINE_ARGS=
-set _SKIP=2
-
-:win9xME_args_slurp
-if "x%~1" == "x" goto execute
-
-set CMD_LINE_ARGS=%*
-
:execute
@rem Setup the command line
-set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+set CLASSPATH=
+
@rem Execute Gradle
-"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
:end
@rem End local scope for the variables with windows NT shell
-if "%ERRORLEVEL%"=="0" goto mainEnd
+if %ERRORLEVEL% equ 0 goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
-if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
-exit /b 1
+set EXIT_CODE=%ERRORLEVEL%
+if %EXIT_CODE% equ 0 set EXIT_CODE=1
+if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
+exit /b %EXIT_CODE%
:mainEnd
if "%OS%"=="Windows_NT" endlocal
diff --git a/jitpack.yml b/jitpack.yml
new file mode 100644
index 000000000..f29a6676c
--- /dev/null
+++ b/jitpack.yml
@@ -0,0 +1,5 @@
+jdk:
+ - openjdk17
+before_install:
+ - sdk install java 17.0.1-open
+ - sdk use java 17.0.1-open
\ No newline at end of file
diff --git a/renovate.json b/renovate.json
new file mode 100644
index 000000000..bdb65c46a
--- /dev/null
+++ b/renovate.json
@@ -0,0 +1,7 @@
+{
+ "$schema": "https://docs.renovatebot.com/renovate-schema.json",
+ "extends": [
+ "config:base",
+ ":automergeMinor"
+ ]
+}
diff --git a/settings.gradle b/settings.gradle
deleted file mode 100644
index 0fd9c5aaf..000000000
--- a/settings.gradle
+++ /dev/null
@@ -1 +0,0 @@
-include ':appintro', ':example'
diff --git a/settings.gradle.kts b/settings.gradle.kts
new file mode 100644
index 000000000..0c1d32ac3
--- /dev/null
+++ b/settings.gradle.kts
@@ -0,0 +1,16 @@
+pluginManagement {
+ repositories {
+ google()
+ mavenCentral()
+ gradlePluginPortal()
+ }
+}
+
+dependencyResolutionManagement {
+ repositories {
+ google()
+ mavenCentral()
+ }
+}
+
+include(":appintro", ":example")