├── .gitignore
├── CONTRIBUTING.md
├── ComposeInfoHostConfigurationGuide.md
├── LICENSE
├── README.md
├── SSComposeInfoBarConfigurationGuide.md
├── app
├── .gitignore
├── build.gradle.kts
├── proguard-rules.pro
└── src
│ └── main
│ ├── AndroidManifest.xml
│ ├── java
│ └── com
│ │ └── simform
│ │ └── sscustominfobarapp
│ │ ├── MainActivity.kt
│ │ ├── home
│ │ ├── Home.kt
│ │ └── SettingBottomSheet.kt
│ │ ├── ui
│ │ └── theme
│ │ │ ├── Color.kt
│ │ │ ├── Theme.kt
│ │ │ └── Type.kt
│ │ └── utils
│ │ ├── AppDimens.kt
│ │ └── AppUtils.kt
│ └── res
│ ├── drawable-v24
│ └── ic_launcher_foreground.xml
│ ├── drawable
│ ├── ic_launcher_background.xml
│ ├── liquid_cheese.xml
│ └── wintery_sunburst.png
│ ├── mipmap-anydpi-v26
│ ├── ic_launcher.xml
│ └── ic_launcher_round.xml
│ ├── mipmap-hdpi
│ ├── ic_launcher.webp
│ └── ic_launcher_round.webp
│ ├── mipmap-mdpi
│ ├── ic_launcher.webp
│ └── ic_launcher_round.webp
│ ├── mipmap-xhdpi
│ ├── ic_launcher.webp
│ └── ic_launcher_round.webp
│ ├── mipmap-xxhdpi
│ ├── ic_launcher.webp
│ └── ic_launcher_round.webp
│ ├── mipmap-xxxhdpi
│ ├── ic_launcher.webp
│ └── ic_launcher_round.webp
│ └── values
│ └── strings.xml
├── build.gradle.kts
├── gifs
├── action.gif
├── annotated.gif
├── default.gif
├── error.gif
├── expand_shrink.gif
├── fade_in_out.gif
├── gradient.gif
├── horizontal_slide.gif
├── internet_observer.gif
├── png.gif
├── scale.gif
├── scale_vertical.gif
├── slide_to_action.gif
├── success.gif
├── svg.gif
├── swipe_to_dismiss.gif
└── warning.gif
├── gradle.properties
├── gradle
├── libs.versions.toml
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── settings.gradle.kts
└── sscustominfobar
├── .gitignore
├── build.gradle.kts
├── consumer-rules.pro
├── proguard-rules.pro
└── src
└── main
├── AndroidManifest.xml
├── java
└── com
│ └── simform
│ └── sscustominfobar
│ ├── animation
│ └── AnimationUtil.kt
│ ├── defaultInfoBars
│ ├── ErrorInfoBar.kt
│ ├── OfflineInfoBar.kt
│ ├── SlideToPerformActionInfoBar.kt
│ ├── SuccessInfoBar.kt
│ └── WarningInfoBar.kt
│ ├── main
│ ├── SSComposeInfoBar.kt
│ ├── SSComposeInfoHost.kt
│ ├── SSInfoBarBackground.kt
│ └── components
│ │ └── CustomText.kt
│ ├── res
│ ├── Colors.kt
│ └── Dimens.kt
│ └── utils
│ ├── ConnectivityObserver.kt
│ ├── DirectionalLazyListState.kt
│ ├── LibUtils.kt
│ ├── SwipeToDismissModifier.kt
│ └── TextType.kt
└── res
└── values
└── strings.xml
/.gitignore:
--------------------------------------------------------------------------------
1 | # Created by https://www.toptal.com/developers/gitignore/api/android,androidstudio,kotlin,java,gradle,git
2 | # Edit at https://www.toptal.com/developers/gitignore?templates=android,androidstudio,kotlin,java,gradle,git
3 |
4 | ### Android ###
5 | # Built application files
6 | *.apk
7 | *.aar
8 | *.ap_
9 | *.aab
10 |
11 | # Files for the ART/Dalvik VM
12 | *.dex
13 |
14 | # Java class files
15 | *.class
16 |
17 | # Generated files
18 | bin/
19 | gen/
20 | out/
21 | # Uncomment the following line in case you need and you don't have the release build type files in your app
22 | release/
23 |
24 | # Gradle files
25 | .gradle/
26 | build/
27 |
28 | # Local configuration file (sdk path, etc)
29 | local.properties
30 |
31 | # Local secret keys file (api keys)
32 | apikeys.properties
33 |
34 | # Proguard folder generated by Eclipse
35 | proguard/
36 |
37 | # Log Files
38 | *.log
39 |
40 | # Android Studio Navigation editor temp files
41 | .navigation/
42 |
43 | # Android Studio captures folder
44 | captures/
45 |
46 | # IntelliJ
47 | .idea
48 | *.iml
49 | .idea/workspace.xml
50 | .idea/tasks.xml
51 | .idea/gradle.xml
52 | .idea/assetWizardSettings.xml
53 | .idea/dictionaries
54 | .idea/libraries
55 | # Android Studio 3 in .gitignore file.
56 | .idea/caches
57 | .idea/modules.xml
58 | # Comment next line if keeping position of elements in Navigation Editor is relevant for you
59 | .idea/navEditor.xml
60 |
61 | # Keystore files
62 | # Uncomment the following lines if you do not want to check your keystore files in.
63 | #*.jks
64 | #*.keystore
65 |
66 | # External native build folder generated in Android Studio 2.2 and later
67 | .externalNativeBuild
68 | .cxx/
69 |
70 | # Google Services (e.g. APIs or Firebase)
71 | # google-services.json
72 |
73 | # Freeline
74 | freeline.py
75 | freeline/
76 | freeline_project_description.json
77 |
78 | # fastlane
79 | fastlane/report.xml
80 | fastlane/Preview.html
81 | fastlane/screenshots
82 | fastlane/test_output
83 | fastlane/readme.md
84 |
85 | # Version control
86 | vcs.xml
87 |
88 | # lint
89 | lint/intermediates/
90 | lint/generated/
91 | lint/outputs/
92 | lint/tmp/
93 | # lint/reports/
94 |
95 | ### Android Patch ###
96 | gen-external-apklibs
97 | output.json
98 |
99 | # Replacement of .externalNativeBuild directories introduced
100 | # with Android Studio 3.5.
101 |
102 | ### Git ###
103 | # Created by git for backups. To disable backups in Git:
104 | # $ git config --global mergetool.keepBackup false
105 | *.orig
106 |
107 | # Created by git when using merge tools for conflicts
108 | *.BACKUP.*
109 | *.BASE.*
110 | *.LOCAL.*
111 | *.REMOTE.*
112 | *_BACKUP_*.txt
113 | *_BASE_*.txt
114 | *_LOCAL_*.txt
115 | *_REMOTE_*.txt
116 |
117 | ### Java ###
118 | # Compiled class file
119 |
120 | # Log file
121 |
122 | # BlueJ files
123 | *.ctxt
124 |
125 | # Mobile Tools for Java (J2ME)
126 | .mtj.tmp/
127 |
128 | # Package Files #
129 | *.jar
130 | *.war
131 | *.nar
132 | *.ear
133 | *.zip
134 | *.tar.gz
135 | *.rar
136 |
137 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
138 | hs_err_pid*
139 |
140 | ### Kotlin ###
141 | # Compiled class file
142 |
143 | # Log file
144 |
145 | # BlueJ files
146 |
147 | # Mobile Tools for Java (J2ME)
148 |
149 | # Package Files #
150 |
151 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
152 |
153 | ### Gradle ###
154 | .gradle
155 |
156 | # Ignore Gradle GUI config
157 | gradle-app.setting
158 |
159 | # Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored)
160 | !gradle-wrapper.jar
161 |
162 | # Cache of project
163 | .gradletasknamecache
164 |
165 | # # Work around https://youtrack.jetbrains.com/issue/IDEA-116898
166 | # gradle/wrapper/gradle-wrapper.properties
167 |
168 | ### Gradle Patch ###
169 | **/build/
170 |
171 | ### AndroidStudio ###
172 | # Covers files to be ignored for android development using Android Studio.
173 |
174 | # Built application files
175 |
176 | # Files for the ART/Dalvik VM
177 |
178 | # Java class files
179 |
180 | # Generated files
181 |
182 | # Gradle files
183 |
184 | # Signing files
185 | .signing/
186 |
187 | # Local configuration file (sdk path, etc)
188 |
189 | # Proguard folder generated by Eclipse
190 |
191 | # Log Files
192 |
193 | # Android Studio
194 | /*/build/
195 | /*/local.properties
196 | /*/out
197 | /*/*/build
198 | /*/*/production
199 | *.ipr
200 | *~
201 | *.swp
202 |
203 | # Android Patch
204 |
205 | # External native build folder generated in Android Studio 2.2 and later
206 |
207 | # NDK
208 | obj/
209 |
210 | # IntelliJ IDEA
211 | *.iws
212 | /out/
213 |
214 | # User-specific configurations
215 | .idea/caches/
216 | .idea/libraries/
217 | .idea/shelf/
218 | .idea/.name
219 | .idea/compiler.xml
220 | .idea/copyright/profiles_settings.xml
221 | .idea/encodings.xml
222 | .idea/misc.xml
223 | .idea/scopes/scope_settings.xml
224 | .idea/vcs.xml
225 | .idea/jsLibraryMappings.xml
226 | .idea/datasources.xml
227 | .idea/dataSources.ids
228 | .idea/sqlDataSources.xml
229 | .idea/dynamic.xml
230 | .idea/uiDesigner.xml
231 |
232 | # OS-specific files
233 | .DS_Store
234 | .DS_Store?
235 | ._*
236 | .Spotlight-V100
237 | .Trashes
238 | ehthumbs.db
239 | Thumbs.db
240 |
241 | # Legacy Eclipse project files
242 | .classpath
243 | .project
244 | .cproject
245 | .settings/
246 |
247 | # Mobile Tools for Java (J2ME)
248 |
249 | # Package Files #
250 |
251 | # virtual machine crash logs (Reference: http://www.java.com/en/download/help/error_hotspot.xml)
252 |
253 | ## Plugin-specific files:
254 |
255 | # mpeltonen/sbt-idea plugin
256 | .idea_modules/
257 |
258 | # JIRA plugin
259 | atlassian-ide-plugin.xml
260 |
261 | # Mongo Explorer plugin
262 | .idea/mongoSettings.xml
263 |
264 | # Crashlytics plugin (for Android Studio and IntelliJ)
265 | com_crashlytics_export_strings.xml
266 | crashlytics.properties
267 | crashlytics-build.properties
268 | fabric.properties
269 |
270 | ### AndroidStudio Patch ###
271 |
272 | !/gradle/wrapper/gradle-wrapper.jar
273 |
274 | # Settings
275 | settings/
276 |
277 | !/app/src/production
278 |
279 | # End of https://www.toptal.com/developers/gitignore/api/android,androidstudio,kotlin,java,gradle,git
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Way to contribute
2 |
3 | 1. Fork the repo and create your branch from `main`.
4 | 2. Clone the project to your own machine. (Please have a look at [**Readme.md**](https://github.com/SimformSolutionsPvtLtd/SSCompose-CustomInfoBar/blob/master/README.md) to understand how to run this project on your machine)
5 | 3. Commit changes to your own branch
6 | 4. Make sure your code lints.
7 | 5. Push your work back up to your fork.
8 | 6. Issue that pull request!
9 |
--------------------------------------------------------------------------------
/ComposeInfoHostConfigurationGuide.md:
--------------------------------------------------------------------------------
1 |
2 | # SSComposeInfoHost Config Customization
3 |
4 | ## You can customise the SSComposeInfoBar Behaviour by configuring the SSComposeInfoHost.
5 |
6 | 1. **Such as the direction from which the InfoBar will be shown.**
7 | ```kotlin
8 | var direction by remember {
9 | mutableStateOf((SSComposeInfoBarDirection.Top))
10 | }
11 |
12 | SSComposeInfoHost(
13 | modifier = Modifier
14 | .fillMaxSize(),
15 | composeHostState = composeInfoHostState,
16 | direction = direction
17 | )
18 | ```
19 |
20 | 2. **Whether we want to show a default info bar when there is no internet connection.**
21 | ```kotlin
22 | SSComposeInfoHost(
23 | modifier = Modifier
24 | .fillMaxSize(),
25 | composeHostState = composeInfoHostState,
26 | enableNetworkMonitoring = true,
27 | )
28 | ```
29 | 3. **To show a custom ComposeInfoBar we can pass our own Composable in the parameter.**
30 | ```kotlin
31 | SSComposeInfoHost(
32 | modifier = Modifier
33 | .fillMaxSize(),
34 | composeHostState = composeInfoHostState,
35 | composeInfoBar = { content ->
36 | SSComposeInfoBar(
37 | title = content.title,
38 | description = content.description
39 | )
40 | }
41 | )
42 | ```
43 |
44 | 4. **If you want to have a functionality where the info bar gets dismissed when user starts scrolling some content downwards and it should be visible only if user scrolls slightly upwards.**
45 | ```kotlin
46 | val lazyListState = rememberLazyListState()
47 |
48 | SSComposeInfoHost(
49 | modifier = Modifier
50 | .fillMaxSize(),
51 | composeHostState = composeInfoHostState,
52 | contentScrollState = lazyListState
53 | )
54 | ```
55 |
56 | 5. **If you want a callback when the info bar is dismissed successfully then we can set an onDismissListener to ComposeInfoHostState**
57 | ```kotlin
58 | val composeInfoHostState by remember {
59 | mutableStateOf(SSComposeInfoHostState())
60 | }
61 |
62 | composeInfoHostState.setOnInfoBarDismiss {
63 | Toast.makeText(
64 | context,
65 | context.getString(R.string.info_bar_dismissed_successfully),
66 | Toast.LENGTH_SHORT
67 | ).show()
68 | }
69 |
70 | SSComposeInfoHost(
71 | modifier = Modifier
72 | .fillMaxSize(),
73 | composeHostState = composeInfoHostState
74 | )
75 | ```
76 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2024 Simform Solutions
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ![Banner]
2 |
3 | # :speech_balloon: SSCompose-CustomInfoBar :speech_balloon:
4 |
5 |
6 |
7 | [![Platform-badge]][Android]
8 | [![Jetpack Compose-badge]][Android]
9 | [![API-badge]][Android]
10 | [](https://kotlinlang.org)
11 | [![Release-badge]][Release]
12 |
13 |
14 |
15 |
16 | Welcome to our SSCompose-CustomInfoBar Library! :tada:
17 | Tired of generic snackbars? This Jetpack Compose library offers a powerful and customizable way to
18 | display informative messages within your app.
19 |
20 | ## :zap: Features
21 |
22 | - **Content**: Add :speech_balloon: text, icons, or even custom composables to your info bar for
23 | ultimate flexibility.
24 | - **Position**: Display the info bar strategically at the :point_up: top or :point_down: bottom of
25 | your
26 | screen.
27 | - **Duration**: Control how long the info bar stays visible, from indefinite to short :zap: or
28 | long :
29 | hourglass: durations.
30 | - **Styles**: Predefined styles like :warning: error, :information_source: info, and warning are
31 | available, or craft your own style with custom background,
32 | text color, and icons.
33 | - **Swipe to dismiss**: Users can dismiss the info bar with a simple swipe, providing an interactive
34 | touch.
35 | - **Look & Feel**: :paintbrush: Customize the info bar's shape, color, and elevation to seamlessly
36 | match
37 | your app's design.
38 | - **Theming**: Integrate with Jetpack Compose's theming system for a consistent and beautiful look
39 | across your app.
40 | - **Animations**: :tada: Liven up your info bars with various built-in entrance and exit animations.
41 | - **Offline Notifications**: :signal_strength: A constructor flag which allows to automatically
42 | displays
43 | an info bar giving "you are offline" notification.
44 | - **Action button**: :+1: Optional action button to show on InfoBar.
45 | - **On Dismiss Callback**: Get notified about bar dismissal.
46 | - **Queueing System**: :repeat: Display multiple info bars sequentially.
47 |
48 | **NOTE:**
49 | - Scroll to show and hide info bar feature work only with LazyListState.
50 | - If the duration of infoBar is infinite then the queue feature won't work.
51 | - Currently there is no limit to how many info bars can be added to queue for displaying.
52 |
53 |
54 | ## :framed_picture: Preview
55 | |Full Preview |
56 | |---|
57 | ||
58 |
59 | | Default Demo | Error Themed | Warning Themed |
60 | |------------------------------------------------|----------------------------------------------|------------------------------------------------|
61 | |
|
|
|
62 |
63 | | Success Themed | Annotated Strings | Gradient Background |
64 | |--------------------------------------------------|----------------------------------------------------|---------------------------------------------------|
65 | |
|
|
|
66 |
67 | | SVG Background | PNG Background | Action Button |
68 | |----------------------------------------------|----------------------------------------------|-------------------------------------------------|
69 | |
|
|
|
70 |
71 | # :books: How it works:
72 |
73 | 1. Add the dependency in your app's build.gradle file
74 |
75 | ```kotlin
76 | dependencies {
77 | implementation("com.github.SimformSolutionsPvtLtd:SSCompose-CustomInfoBar:1.0")
78 | }
79 | ```
80 |
81 | 2. Add the JitPack repository
82 |
83 | For latest Android Studio, in **settings.gradle** file
84 | inside **`dependencyResolutionManagement`** block
85 |
86 | ```kotlin
87 | dependencyResolutionManagement {
88 | repositories {
89 | ...
90 | maven { url = uri("https://jitpack.io") }
91 | }
92 | }
93 | ```
94 |
95 | 3. Create ComposeInfoHost with default parameters to host ComposeInfoBar (NOTE: Here we are not
96 | passing an custom ComposeInfoBar so it will use a default implementation. To show custom
97 | ComposeInfoBar check out [SSComposeInfoHost-Customisation-Guide])
98 |
99 | ```kotlin
100 | val composeInfoHostState by remember {
101 | mutableStateOf(SSComposeInfoHostState())
102 | }
103 |
104 | SSComposeInfoHost(
105 | modifier = Modifier
106 | .fillMaxSize(),
107 | composeHostState = composeInfoHostState
108 | ) {
109 | MainComposable()
110 | }
111 | ```
112 |
113 | 4. Show composeInfoBar anywhere in your project
114 |
115 | ```kotlin
116 | val coroutineScope = rememberCoroutineScope()
117 |
118 | var duration by remember {
119 | mutableStateOf(SSComposeInfoDuration.Short)
120 | }
121 |
122 | coroutineScope.launch {
123 | composeInfoHostState.show(
124 | infoBarData = SSComposeInfoBarData(title, description),
125 | duration = duration
126 | )
127 | }
128 | ```
129 |
130 | 5. For full customisation of SSComposeInfoBar checkout [SSComposeInfoBar-Customisation-Guide]
131 |
132 | ## :heart: Find this samples useful?
133 |
134 | Support it by joining [stargazers] :star: for this repository.
135 |
136 | ## :handshake: How to Contribute?
137 |
138 | Whether you're helping us fix bugs, improve the docs, or a feature request, we'd love to have you! :
139 | muscle: \
140 | Check out our __[Contributing Guide]__ for ideas on contributing.
141 |
142 | ## :lady_beetle: Bugs and Feedback
143 |
144 | For bugs, feature requests, and discussion use [GitHub Issues].
145 |
146 | ## :rocket: Other Mobile Libraries
147 |
148 | Check out our other libraries [Awesome-Mobile-Libraries].
149 |
150 | ## :balance_scale: License
151 |
152 | Distributed under the MIT license. See [LICENSE] for details.
153 |
154 |
155 |
156 | [Banner]: https://github.com/user-attachments/assets/231ef0fa-4ac0-4bf4-9060-63c3b15f0512
157 |
158 | [Android]: https://www.android.com/
159 |
160 | [Android App Architecture]: https://developer.android.com/topic/architecture
161 |
162 | [Release]: https://github.com/SimformSolutionsPvtLtd/SSCompose-CustomInfoBar/releases/latest
163 |
164 | [stargazers]: https://github.com/SimformSolutionsPvtLtd/SSCompose-CustomInfoBar/stargazers
165 |
166 | [Contributing Guide]: CONTRIBUTING.md
167 |
168 | [Github Issues]: https://github.com/SimformSolutionsPvtLtd/SSCompose-CustomInfoBar/issues
169 |
170 | [Awesome-Mobile-Libraries]: https://github.com/SimformSolutionsPvtLtd/Awesome-Mobile-Libraries
171 |
172 | [license]: LICENSE
173 |
174 | [SSComposeInfoBar-Customisation-Guide]: SSComposeInfoBarConfigurationGuide.md
175 |
176 | [SSComposeInfoHost-Customisation-Guide]: ComposeInfoHostConfigurationGuide.md
177 |
178 |
179 |
180 | [Platform-badge]: https://img.shields.io/badge/Platform-Android-green.svg?logo=Android
181 |
182 | [Jetpack Compose-badge]: https://img.shields.io/badge/Jetpack_Compose-v1.5.11-1c274a.svg?logo=jetpackcompose&logoColor=3ddc84
183 |
184 | [API-badge]: https://img.shields.io/badge/API-21+-51b055
185 |
186 | [Release-badge]: https://img.shields.io/github/v/release/SimformSolutionsPvtLtd/SSCompose-CustomInfoBar
187 |
188 | [License Badge-badge]: https://img.shields.io/github/license/SimformSolutionsPvtLtd/SSCompose-CustomInfoBar
189 |
--------------------------------------------------------------------------------
/SSComposeInfoBarConfigurationGuide.md:
--------------------------------------------------------------------------------
1 |
2 | # SSComposeInfoBar Customization
3 |
4 | ### You can create your own custom InfoBar using SSComposeInfoBar composable which accepts following parameters.
5 |
6 | | Parameter Name | Parameter Type | Description | Default Value |
7 | |------------------|---------------------------|--------------------------------------------------------------------------------|----------------------------------------------------|
8 | | title | TextType | The title of the InfoBar (Wrapper around String and AnnotatedString. | N/A |
9 | | titleStyle | TextStyle | Text style of the InfoBar title. | SSComposeInfoBarDefaults.defaultTitleStyle |
10 | | description | TextType? | The description of the InfoBar. | null |
11 | | descriptionStyle | TextStyle | Text style of the InfoBar description. | SSComposeInfoBarDefaults.defaultDescriptionStyle |
12 | | customBackground | SSCustomBackground | Background of the InfoBar (Wrapper around Support PNG, SVG and Color). | SSComposeInfoBarDefaults.defaultSSCustomBackground |
13 | | icon | ImageVector | Icon that will be displayed along with title and description. | Icons.Default.Info |
14 | | shape | Shape | Shape of the InfoBar. | SSComposeInfoBarDefaults.shape |
15 | | elevations | SSComposeInfoBarElevation | Elevation of the InfoBar (Wrapper around Tonal elevation and Shadow elevation. | SSComposeInfoBarDefaults.elevations |
16 | | contentColors | SSComposeInfoBarColors | Content colors for InfoBar content. | SSComposeInfoBarDefaults.colors |
17 | | contentPadding | PaddingValues | PaddingValues that will be applied to InfoBar. | SSComposeInfoBarDefaults.contentPadding |
18 | | height | Dp | Height of the InfoBar. | SSComposeInfoBarDefaults.defaultHeight |
19 | | actionText | String | Title of the action button if action is showed. | SSComposeInfoBarDefaults.defaultActionTitle |
20 | | onActionClicked | (() -> Unit)? | Callback for when user clicks the action button on InfoBar. | null |
21 | | onCloseClicked | () -> Unit | Callback for when user clicks the close icon on InfoBar. | Empty Lambda |
22 | | isInfinite | Boolean | Flag that decides whether to show a close icon on InfoBar. | false |
23 |
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/app/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | alias(libs.plugins.androidApplication)
3 | alias(libs.plugins.jetbrainsKotlinAndroid)
4 | }
5 |
6 | android {
7 | namespace = libs.versions.applicationId.get()
8 | compileSdk = libs.versions.compileSDK.get().toInt()
9 |
10 | defaultConfig {
11 | applicationId = libs.versions.applicationId.get()
12 | minSdk = libs.versions.minSDK.get().toInt()
13 | targetSdk = libs.versions.targetSDK.get().toInt()
14 | versionCode = libs.versions.versionCode.get().toInt()
15 | versionName = libs.versions.version.get()
16 |
17 | testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
18 | vectorDrawables {
19 | useSupportLibrary = true
20 | }
21 | }
22 |
23 | buildTypes {
24 | release {
25 | isMinifyEnabled = false
26 | proguardFiles(
27 | getDefaultProguardFile("proguard-android-optimize.txt"),
28 | "proguard-rules.pro"
29 | )
30 | }
31 | }
32 | compileOptions {
33 | sourceCompatibility = JavaVersion.VERSION_17
34 | targetCompatibility = JavaVersion.VERSION_17
35 | }
36 | kotlinOptions {
37 | jvmTarget = "17"
38 | }
39 | buildFeatures {
40 | compose = true
41 | }
42 | composeOptions {
43 | kotlinCompilerExtensionVersion = "1.5.11"
44 | }
45 | packaging {
46 | resources {
47 | excludes += "/META-INF/{AL2.0,LGPL2.1}"
48 | }
49 | }
50 | }
51 |
52 | dependencies {
53 |
54 | implementation(libs.androidx.core.ktx)
55 | implementation(libs.androidx.lifecycle.runtime.ktx)
56 | implementation(libs.androidx.activity.compose)
57 | implementation(platform(libs.androidx.compose.bom))
58 | implementation(libs.androidx.ui)
59 | implementation(libs.androidx.ui.graphics)
60 | implementation(libs.androidx.ui.tooling.preview)
61 | implementation(libs.androidx.material3)
62 | implementation(libs.material.icons.extended)
63 | testImplementation(libs.junit)
64 | androidTestImplementation(libs.androidx.junit)
65 | androidTestImplementation(libs.androidx.espresso.core)
66 | androidTestImplementation(platform(libs.androidx.compose.bom))
67 | androidTestImplementation(libs.androidx.ui.test.junit4)
68 | debugImplementation(libs.androidx.ui.tooling)
69 | debugImplementation(libs.androidx.ui.test.manifest)
70 | implementation(project(path = ":sscustominfobar"))
71 | }
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
12 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/app/src/main/java/com/simform/sscustominfobarapp/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package com.simform.sscustominfobarapp
2 |
3 | import android.os.Bundle
4 | import androidx.activity.ComponentActivity
5 | import androidx.activity.compose.setContent
6 | import androidx.compose.foundation.layout.fillMaxSize
7 | import androidx.compose.material3.MaterialTheme
8 | import androidx.compose.material3.Surface
9 | import androidx.compose.ui.Modifier
10 | import com.simform.sscustominfobarapp.home.SSCustomInfoBarHome
11 | import com.simform.sscustominfobarapp.ui.theme.SSCustomInfoBarTheme
12 |
13 | class MainActivity : ComponentActivity() {
14 | override fun onCreate(savedInstanceState: Bundle?) {
15 | super.onCreate(savedInstanceState)
16 | setContent {
17 | SSCustomInfoBarTheme {
18 | Surface(
19 | modifier = Modifier.fillMaxSize(),
20 | color = MaterialTheme.colorScheme.background
21 | ) {
22 | SSCustomInfoBarHome()
23 | }
24 | }
25 | }
26 | }
27 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/simform/sscustominfobarapp/home/Home.kt:
--------------------------------------------------------------------------------
1 | package com.simform.sscustominfobarapp.home
2 |
3 | import android.widget.Toast
4 | import androidx.compose.foundation.layout.Arrangement
5 | import androidx.compose.foundation.layout.Box
6 | import androidx.compose.foundation.layout.PaddingValues
7 | import androidx.compose.foundation.layout.fillMaxSize
8 | import androidx.compose.foundation.layout.fillMaxWidth
9 | import androidx.compose.foundation.layout.height
10 | import androidx.compose.foundation.layout.padding
11 | import androidx.compose.foundation.lazy.LazyColumn
12 | import androidx.compose.foundation.lazy.items
13 | import androidx.compose.foundation.lazy.rememberLazyListState
14 | import androidx.compose.material.icons.Icons
15 | import androidx.compose.material.icons.outlined.Settings
16 | import androidx.compose.material3.Button
17 | import androidx.compose.material3.ButtonDefaults
18 | import androidx.compose.material3.ExperimentalMaterial3Api
19 | import androidx.compose.material3.Icon
20 | import androidx.compose.material3.IconButton
21 | import androidx.compose.material3.MaterialTheme
22 | import androidx.compose.material3.ModalBottomSheet
23 | import androidx.compose.material3.Scaffold
24 | import androidx.compose.material3.Text
25 | import androidx.compose.material3.TopAppBar
26 | import androidx.compose.material3.TopAppBarDefaults
27 | import androidx.compose.material3.rememberModalBottomSheetState
28 | import androidx.compose.runtime.Composable
29 | import androidx.compose.runtime.getValue
30 | import androidx.compose.runtime.mutableStateOf
31 | import androidx.compose.runtime.remember
32 | import androidx.compose.runtime.rememberCoroutineScope
33 | import androidx.compose.runtime.setValue
34 | import androidx.compose.ui.Alignment
35 | import androidx.compose.ui.Modifier
36 | import androidx.compose.ui.graphics.Color
37 | import androidx.compose.ui.platform.LocalContext
38 | import androidx.compose.ui.res.stringResource
39 | import androidx.compose.ui.text.font.FontWeight
40 | import androidx.compose.ui.unit.dp
41 | import com.simform.sscustominfobar.animation.AnimationType
42 | import com.simform.sscustominfobar.main.SSComposeInfoBar
43 | import com.simform.sscustominfobar.main.SSComposeInfoBarDirection
44 | import com.simform.sscustominfobar.main.SSComposeInfoBarShapes
45 | import com.simform.sscustominfobar.main.SSComposeInfoDuration
46 | import com.simform.sscustominfobar.main.SSComposeInfoHost
47 | import com.simform.sscustominfobar.main.SSComposeInfoHostState
48 | import com.simform.sscustominfobarapp.R
49 | import com.simform.sscustominfobarapp.ui.theme.SimformPink
50 | import com.simform.sscustominfobarapp.utils.AppDimens
51 | import com.simform.sscustominfobarapp.utils.ButtonType
52 | import com.simform.sscustominfobarapp.utils.InfoBarByButtonType
53 | import com.simform.sscustominfobarapp.utils.getButtonTitle
54 | import com.simform.sscustominfobarapp.utils.getInfoBarDescription
55 | import com.simform.sscustominfobarapp.utils.getInfoBarTitle
56 | import com.simform.sscustominfobarapp.utils.showSSComposeInfoBar
57 | import java.util.LinkedList
58 | import java.util.Queue
59 |
60 | /**
61 | * Custom Button used in home screen of the demo.
62 | *
63 | * @param title Text in the button.
64 | * @param buttonType [ButtonType] of the button.
65 | * @param onClick Called when user clicks on this button.
66 | */
67 | @Composable
68 | private fun CustomHomeButton(
69 | title: String,
70 | buttonType: ButtonType,
71 | onClick: (ButtonType) -> Unit
72 | ) {
73 | Button(
74 | modifier = Modifier.fillMaxWidth(),
75 | shape = MaterialTheme.shapes.small,
76 | colors = ButtonDefaults.buttonColors(contentColor = Color.White, containerColor = SimformPink),
77 | onClick = {
78 | onClick(buttonType)
79 | }) {
80 | Text(text = title)
81 | }
82 | }
83 |
84 | /**
85 | * Custom Action bar to display app title and a setting icon.
86 | *
87 | * @param modifier The [Modifier] that is applied to this Custom action bar.
88 | * @param onSettingClicked Called when the user clicks on the settings icon.
89 | */
90 | @OptIn(ExperimentalMaterial3Api::class)
91 | @Composable
92 | fun HomeActionBar(modifier: Modifier = Modifier, onSettingClicked: () -> Unit) {
93 | TopAppBar(
94 | title = {
95 | Text(
96 | text = stringResource(R.string.ss_compose_info_bar_demo),
97 | color = Color.White,
98 | style = MaterialTheme.typography.titleMedium,
99 | fontWeight = FontWeight.Bold
100 | )
101 | },
102 | modifier = modifier,
103 | actions = {
104 | IconButton(onClick = onSettingClicked) {
105 | Icon(
106 | imageVector = Icons.Outlined.Settings,
107 | contentDescription = stringResource(R.string.info_bar_settings),
108 | tint = Color.White
109 | )
110 | }
111 | },
112 | colors = TopAppBarDefaults.topAppBarColors(
113 | containerColor = SimformPink,
114 | actionIconContentColor = MaterialTheme.colorScheme.onPrimary
115 | )
116 | )
117 | }
118 |
119 | /**
120 | * Home Screen for the demonstration of the [SSComposeInfoBar] Library.
121 | */
122 | @OptIn(ExperimentalMaterial3Api::class)
123 | @Composable
124 | fun SSCustomInfoBarHome() {
125 | val context = LocalContext.current
126 | val composeInfoHostState by remember {
127 | mutableStateOf(SSComposeInfoHostState())
128 | }
129 | val lazyListState = rememberLazyListState()
130 | val coroutineScope = rememberCoroutineScope()
131 | var shouldShowSettingSheet by remember {
132 | mutableStateOf(false)
133 | }
134 | var duration by remember {
135 | mutableStateOf(SSComposeInfoDuration.Short)
136 | }
137 | var direction by remember {
138 | mutableStateOf((SSComposeInfoBarDirection.Top))
139 | }
140 | var animationType by remember {
141 | mutableStateOf((AnimationType.SlideVertically))
142 | }
143 | val btnTypeQueue: Queue = remember {
144 | LinkedList()
145 | }
146 | var isSwipeToDismissEnabled by remember {
147 | mutableStateOf(false)
148 | }
149 | var isNetworkMonitoringEnabled by remember {
150 | mutableStateOf(true)
151 | }
152 | composeInfoHostState.setOnInfoBarDismiss {
153 | btnTypeQueue.remove()
154 | Toast.makeText(
155 | context,
156 | context.getString(R.string.info_bar_dismissed_successfully),
157 | Toast.LENGTH_SHORT
158 | ).show()
159 | }
160 | val sheetState = rememberModalBottomSheetState(true)
161 | Scaffold(
162 | modifier = Modifier,
163 | topBar = {
164 | HomeActionBar(
165 | modifier = Modifier
166 | ) {
167 | shouldShowSettingSheet = shouldShowSettingSheet.not()
168 | }
169 | }
170 | ) {
171 | SSComposeInfoHost(
172 | modifier = Modifier
173 | .fillMaxSize()
174 | .padding(it),
175 | composeHostState = composeInfoHostState,
176 | direction = direction,
177 | animationType = animationType,
178 | contentScrollState = lazyListState,
179 | enableNetworkMonitoring = isNetworkMonitoringEnabled,
180 | isSwipeToDismissEnabled = isSwipeToDismissEnabled,
181 | composeInfoBar = { content ->
182 | val currentBtnType = btnTypeQueue.element()
183 | InfoBarByButtonType(
184 | type = currentBtnType,
185 | content = content,
186 | isInfinite = composeInfoHostState.isInfinite.value,
187 | shape = if (composeInfoHostState.direction.value == SSComposeInfoBarDirection.Top) SSComposeInfoBarShapes.roundedBottom else SSComposeInfoBarShapes.roundedTop,
188 | onClose = {
189 | composeInfoHostState.dismiss()
190 | }
191 | )
192 | }
193 | ) {
194 | LazyColumn(
195 | modifier = Modifier
196 | .fillMaxSize()
197 | .padding(horizontal = AppDimens.DpMedium),
198 | state = lazyListState,
199 | contentPadding = PaddingValues(vertical = AppDimens.DpMedium),
200 | horizontalAlignment = Alignment.CenterHorizontally,
201 | verticalArrangement = Arrangement.spacedBy(AppDimens.DpMedium)
202 | ) {
203 | items(ButtonType.entries) { bType ->
204 | CustomHomeButton(
205 | title = stringResource(id = getButtonTitle(bType)),
206 | buttonType = bType
207 | ) { btnType ->
208 | val title = getInfoBarTitle(context, btnType)
209 | val desc = getInfoBarDescription(context, btnType)
210 |
211 | // NOTE: This if and else logic is only need for demo purposes as we are using multiple themes for infobar with a queue.
212 | if (duration != SSComposeInfoDuration.Indefinite) {
213 | btnTypeQueue.add(btnType)
214 | } else if (btnTypeQueue.isEmpty()) {
215 | // If the duration is infinite then only add the btnType in the queue for the very first time and not again.
216 | btnTypeQueue.add(btnType)
217 | }
218 | coroutineScope.showSSComposeInfoBar(
219 | title = title,
220 | description = desc,
221 | composeInfoHostState = composeInfoHostState,
222 | duration = duration
223 | )
224 | }
225 | }
226 | // For temporary usage will be removed in future.
227 | // add these invisible boxes to make the current list scrollable
228 | // in order to demonstrate scroll to show and hide feature
229 | if (composeInfoHostState.isVisible) {
230 | items(20) {
231 | Box(modifier = Modifier.height(24.dp))
232 | }
233 | }
234 | }
235 | if (shouldShowSettingSheet) {
236 | ModalBottomSheet(
237 | onDismissRequest = {
238 | shouldShowSettingSheet = false
239 | },
240 | sheetState = sheetState
241 | ) {
242 | // Sheet content
243 | SettingBottomSheet(
244 | inputDuration = duration,
245 | inputDirection = direction,
246 | inputAnimationType = animationType,
247 | inputSwipeToDismissState = isSwipeToDismissEnabled,
248 | inputNetworkObserverState = isNetworkMonitoringEnabled,
249 | onCancel = { shouldShowSettingSheet = false },
250 | onConfirm = { selectedDuration, selectedDirection, selectedAnimation,
251 | swipeToDismissState, networkObserverState ->
252 | duration = selectedDuration
253 | direction = selectedDirection
254 | animationType = selectedAnimation
255 | isSwipeToDismissEnabled = swipeToDismissState
256 | isNetworkMonitoringEnabled = networkObserverState
257 | shouldShowSettingSheet = false
258 | }
259 | )
260 | }
261 | }
262 | }
263 | }
264 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/simform/sscustominfobarapp/home/SettingBottomSheet.kt:
--------------------------------------------------------------------------------
1 | package com.simform.sscustominfobarapp.home
2 |
3 | import androidx.compose.foundation.layout.Arrangement
4 | import androidx.compose.foundation.layout.Column
5 | import androidx.compose.foundation.layout.Row
6 | import androidx.compose.foundation.layout.Spacer
7 | import androidx.compose.foundation.layout.fillMaxWidth
8 | import androidx.compose.foundation.layout.height
9 | import androidx.compose.foundation.layout.padding
10 | import androidx.compose.foundation.layout.width
11 | import androidx.compose.foundation.rememberScrollState
12 | import androidx.compose.foundation.verticalScroll
13 | import androidx.compose.material3.Button
14 | import androidx.compose.material3.HorizontalDivider
15 | import androidx.compose.material3.MaterialTheme
16 | import androidx.compose.material3.RadioButton
17 | import androidx.compose.material3.Surface
18 | import androidx.compose.material3.Switch
19 | import androidx.compose.material3.Text
20 | import androidx.compose.runtime.Composable
21 | import androidx.compose.runtime.getValue
22 | import androidx.compose.runtime.mutableStateOf
23 | import androidx.compose.runtime.remember
24 | import androidx.compose.runtime.setValue
25 | import androidx.compose.ui.Alignment
26 | import androidx.compose.ui.Modifier
27 | import androidx.compose.ui.draw.clip
28 | import androidx.compose.ui.res.stringResource
29 | import androidx.compose.ui.text.font.FontWeight
30 | import com.simform.sscustominfobar.animation.AnimationType
31 | import com.simform.sscustominfobar.main.SSComposeInfoBar
32 | import com.simform.sscustominfobar.main.SSComposeInfoBarDirection
33 | import com.simform.sscustominfobar.main.SSComposeInfoDuration
34 | import com.simform.sscustominfobarapp.R
35 | import com.simform.sscustominfobarapp.utils.AppDimens
36 |
37 | /**
38 | * Custom Bottom Sheet to display settings that user can set for the [SSComposeInfoBar] Displayed.
39 | *
40 | * @param modifier The modifier to apply to [SettingBottomSheet].
41 | * @param inputDuration The default value for [SSComposeInfoDuration].
42 | * @param inputDirection The default value for [SSComposeInfoBarDirection].
43 | * @param inputSwipeToDismissState The default value for whether the swipe to dismiss is enabled or not.
44 | * @param inputNetworkObserverState The default value for whether the network observer is enabled or not.
45 | * @param onCancel Called when the user clicks on the cancel button.
46 | * @param onConfirm Called when the user clicks on the confirm button.
47 | */
48 | @Composable
49 | fun SettingBottomSheet(
50 | modifier: Modifier = Modifier,
51 | inputDuration: SSComposeInfoDuration,
52 | inputDirection: SSComposeInfoBarDirection,
53 | inputAnimationType: AnimationType,
54 | inputSwipeToDismissState: Boolean,
55 | inputNetworkObserverState: Boolean,
56 | onCancel: () -> Unit,
57 | onConfirm: (SSComposeInfoDuration, SSComposeInfoBarDirection, AnimationType, Boolean, Boolean) -> Unit
58 | ) {
59 | var selectedDuration by remember {
60 | mutableStateOf(inputDuration)
61 | }
62 | var selectedDirection by remember {
63 | mutableStateOf(inputDirection)
64 | }
65 | var selectedAnimationType by remember {
66 | mutableStateOf(inputAnimationType)
67 | }
68 | var isSwipeToDismissEnabled by remember {
69 | mutableStateOf(inputSwipeToDismissState)
70 | }
71 | var isNetworkObserverEnabled by remember {
72 | mutableStateOf(inputNetworkObserverState)
73 | }
74 | Surface(modifier.clip(MaterialTheme.shapes.medium)) {
75 | Column(
76 | Modifier
77 | .padding(AppDimens.DpMedium)
78 | .verticalScroll(rememberScrollState())
79 | ) {
80 | DurationSection(selectedOption = selectedDuration) { duration ->
81 | selectedDuration = duration
82 | }
83 | HorizontalDivider(modifier = Modifier.height(AppDimens.DpMedium))
84 | DirectionSection(selectedOption = selectedDirection) { direction ->
85 | selectedDirection = direction
86 | }
87 | HorizontalDivider(modifier = Modifier.height(AppDimens.DpMedium))
88 | AnimationSection(selectedOption = selectedAnimationType) { animationType ->
89 | selectedAnimationType = animationType
90 | }
91 | HorizontalDivider(modifier = Modifier.height(AppDimens.DpMedium))
92 | CustomChipSection(
93 | title = stringResource(R.string.swipe_to_dismiss),
94 | isSelected = isSwipeToDismissEnabled,
95 | onSelected = { isSwipeToDismissEnabled = it }
96 | )
97 | CustomChipSection(
98 | title = stringResource(R.string.network_state_observation),
99 | isSelected = isNetworkObserverEnabled,
100 | onSelected = { isNetworkObserverEnabled = it }
101 | )
102 | ButtonRow(
103 | onCancel = onCancel,
104 | onConfirm = {
105 | onConfirm(
106 | selectedDuration,
107 | selectedDirection,
108 | selectedAnimationType,
109 | isSwipeToDismissEnabled,
110 | isNetworkObserverEnabled
111 | )
112 | }
113 | )
114 | }
115 | }
116 | }
117 |
118 |
119 | /**
120 | * Custom composable to show buttons in a row.
121 | * Here used for cancel and confirm buttons.
122 | *
123 | * @param onCancel Called when user clicks on cancel button
124 | * @param onConfirm Called when user clicks on confirm button
125 | */
126 | @Composable
127 | private fun ButtonRow(
128 | onCancel: () -> Unit,
129 | onConfirm: () -> Unit
130 | ) {
131 | Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceEvenly) {
132 | Button(
133 | onClick = onCancel,
134 | modifier = Modifier.weight(1f)
135 | ) {
136 | Text(text = stringResource(R.string.cancel))
137 | }
138 | Spacer(modifier = Modifier.width(AppDimens.DpMedium))
139 | Button(
140 | onClick = onConfirm,
141 | modifier = Modifier.weight(1f)
142 | ) {
143 | Text(text = stringResource(R.string.confirm))
144 | }
145 | }
146 | }
147 |
148 | /**
149 | * [SettingBottomSheet]'s sub composable for selecting animationType of [SSComposeInfoBar].
150 | *
151 | * @param selectedOption currently selected option on [AnimationType].
152 | * @param onOptionSelected called when user selects a new animationType radio button.
153 | */
154 | @Composable
155 | private fun AnimationSection(
156 | selectedOption: AnimationType,
157 | onOptionSelected: (animationType: AnimationType) -> Unit
158 | ) {
159 | BaseSettingSection(
160 | sectionTitle = stringResource(id = R.string.animation),
161 | selectedOption = selectedOption.name,
162 | optionsList = AnimationType.entries.map { it.name }) { newAnimation ->
163 | onOptionSelected(AnimationType.valueOf(newAnimation))
164 | }
165 | }
166 |
167 | /**
168 | * [SettingBottomSheet]'s sub composable for selecting duration of [SSComposeInfoBar].
169 | *
170 | * @param selectedOption currently selected option on [SSComposeInfoDuration].
171 | * @param onOptionSelected called when user selects a new duration radio button.
172 | */
173 | @Composable
174 | private fun DurationSection(
175 | selectedOption: SSComposeInfoDuration,
176 | onOptionSelected: (selectedDuration: SSComposeInfoDuration) -> Unit
177 | ) {
178 | BaseSettingSection(
179 | sectionTitle = stringResource(id = R.string.duration),
180 | selectedOption = selectedOption.name,
181 | optionsList = SSComposeInfoDuration.entries.map { it.name }) { newSelectedDuration ->
182 | onOptionSelected(SSComposeInfoDuration.valueOf(newSelectedDuration))
183 | }
184 | }
185 |
186 | /**
187 | * [SettingBottomSheet]'s sub composable for selecting direction of [SSComposeInfoBar].
188 | *
189 | * @param selectedOption currently selected option on [SSComposeInfoBarDirection].
190 | * @param onOptionSelected called when user selects a new direction radio button.
191 | */
192 | @Composable
193 | private fun DirectionSection(
194 | selectedOption: SSComposeInfoBarDirection,
195 | onOptionSelected: (selectedDirection: SSComposeInfoBarDirection) -> Unit
196 | ) {
197 | BaseSettingSection(
198 | sectionTitle = stringResource(id = R.string.direction),
199 | selectedOption = selectedOption.name,
200 | optionsList = SSComposeInfoBarDirection.entries.map { it.name }) { newSelectedDirection ->
201 | onOptionSelected(SSComposeInfoBarDirection.valueOf(newSelectedDirection))
202 | }
203 | }
204 |
205 | /**
206 | * [SettingBottomSheet]'s sub composable for enabling/disabling a feature of [SSComposeInfoBar].
207 | *
208 | * @param title Name of the feature.
209 | * @param isSelected Initial info about whether it is selected or not.
210 | * @param onSelected Callback for when user changes the configuration.
211 | */
212 | @Composable
213 | private fun CustomChipSection(
214 | title: String,
215 | isSelected: Boolean,
216 | onSelected: (Boolean) -> Unit
217 | ) {
218 | Row(
219 | Modifier.fillMaxWidth(),
220 | horizontalArrangement = Arrangement.SpaceBetween,
221 | verticalAlignment = Alignment.CenterVertically
222 | ) {
223 | Text(
224 | text = title,
225 | style = MaterialTheme.typography.titleMedium.copy(fontWeight = FontWeight.Normal)
226 | )
227 | Spacer(modifier = Modifier.width(AppDimens.DpMedium))
228 | Switch(checked = isSelected, onCheckedChange = {
229 | onSelected(it)
230 | })
231 | }
232 | }
233 |
234 | /**
235 | * Base composable for settings radio buttons sections
236 | *
237 | * @param sectionTitle Title of the section.
238 | * @param selectedOption Currently selected option.
239 | * @param optionsList List of names of the available options.
240 | * @param onOptionSelected Called when user selects any one of the options.
241 | */
242 | @Composable
243 | private fun BaseSettingSection(
244 | sectionTitle: String,
245 | selectedOption: String,
246 | optionsList: List,
247 | onOptionSelected: (String) -> Unit
248 | ) {
249 | Text(
250 | text = sectionTitle,
251 | style = MaterialTheme.typography.titleLarge
252 | )
253 | Spacer(modifier = Modifier.height(AppDimens.DpExtraSmall))
254 | optionsList.forEach { option ->
255 | Row(
256 | Modifier.fillMaxWidth(),
257 | horizontalArrangement = Arrangement.SpaceBetween,
258 | verticalAlignment = Alignment.CenterVertically
259 | ) {
260 | Text(
261 | text = option,
262 | style = MaterialTheme.typography.titleMedium.copy(fontWeight = FontWeight.Normal)
263 | )
264 | Spacer(modifier = Modifier.width(AppDimens.DpMedium))
265 | RadioButton(
266 | selected = selectedOption == option,
267 | onClick = { onOptionSelected(option) }
268 | )
269 | }
270 | }
271 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/simform/sscustominfobarapp/ui/theme/Color.kt:
--------------------------------------------------------------------------------
1 | package com.simform.sscustominfobarapp.ui.theme
2 |
3 | import androidx.compose.ui.graphics.Color
4 |
5 | val Pink80 = Color(0xFFEFB8C8)
6 | val Pink40 = Color(0xFF7D5260)
7 | val SimformPink = Color(0xFFFD4A5E)
--------------------------------------------------------------------------------
/app/src/main/java/com/simform/sscustominfobarapp/ui/theme/Theme.kt:
--------------------------------------------------------------------------------
1 | package com.simform.sscustominfobarapp.ui.theme
2 |
3 | import android.os.Build
4 | import androidx.compose.foundation.isSystemInDarkTheme
5 | import androidx.compose.material3.MaterialTheme
6 | import androidx.compose.material3.darkColorScheme
7 | import androidx.compose.material3.dynamicDarkColorScheme
8 | import androidx.compose.material3.dynamicLightColorScheme
9 | import androidx.compose.material3.lightColorScheme
10 | import androidx.compose.runtime.Composable
11 | import androidx.compose.ui.graphics.Color
12 | import androidx.compose.ui.platform.LocalContext
13 |
14 | private val DarkColorScheme = darkColorScheme(
15 | primary = SimformPink,
16 | secondary = SimformPink,
17 | tertiary = Pink80,
18 | onPrimary = Color.White
19 | )
20 |
21 | private val LightColorScheme = lightColorScheme(
22 | primary = SimformPink,
23 | secondary = SimformPink,
24 | tertiary = Pink40
25 | )
26 |
27 | @Composable
28 | fun SSCustomInfoBarTheme(
29 | darkTheme: Boolean = isSystemInDarkTheme(),
30 | // Dynamic color is available on Android 12+
31 | dynamicColor: Boolean = false,
32 | content: @Composable () -> Unit
33 | ) {
34 | val colorScheme = when {
35 | dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
36 | val context = LocalContext.current
37 | if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
38 | }
39 |
40 | darkTheme -> DarkColorScheme
41 | else -> LightColorScheme
42 | }
43 | MaterialTheme(
44 | colorScheme = colorScheme,
45 | typography = Typography,
46 | content = content
47 | )
48 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/simform/sscustominfobarapp/ui/theme/Type.kt:
--------------------------------------------------------------------------------
1 | package com.simform.sscustominfobarapp.ui.theme
2 |
3 | import androidx.compose.material3.Typography
4 | import androidx.compose.ui.text.TextStyle
5 | import androidx.compose.ui.text.font.FontFamily
6 | import androidx.compose.ui.text.font.FontWeight
7 | import androidx.compose.ui.unit.sp
8 |
9 | // Set of Material typography styles to start with
10 | val Typography = Typography(
11 | bodyLarge = TextStyle(
12 | fontFamily = FontFamily.Default,
13 | fontWeight = FontWeight.Normal,
14 | fontSize = 16.sp,
15 | lineHeight = 24.sp,
16 | letterSpacing = 0.5.sp
17 | )
18 | )
--------------------------------------------------------------------------------
/app/src/main/java/com/simform/sscustominfobarapp/utils/AppDimens.kt:
--------------------------------------------------------------------------------
1 | package com.simform.sscustominfobarapp.utils
2 |
3 | import androidx.compose.ui.unit.dp
4 | import androidx.compose.ui.unit.sp
5 |
6 | internal object AppDimens {
7 | // Dp Dimensions
8 | val DpZero = 0.dp
9 | val DpExtraSmall = 4.dp
10 | val DpSmall = 8.dp
11 | val DpMedium = 16.dp
12 | val Large = 20.dp
13 | val ExtraLarge = 24.dp
14 |
15 | val DpTwentyEight = 28.dp
16 | val DpTwelve = 12.dp
17 | val DpEighty = 80.dp
18 |
19 | // Sp Dimensions
20 | val SpExtraSmall = 4.sp
21 | val SpSmall = 8.sp
22 | val SpMedium = 16.sp
23 | val SpLarge = 20.sp
24 | val SpExtraLarge = 24.sp
25 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/simform/sscustominfobarapp/utils/AppUtils.kt:
--------------------------------------------------------------------------------
1 | package com.simform.sscustominfobarapp.utils
2 |
3 | import android.content.Context
4 | import android.widget.Toast
5 | import androidx.compose.material.icons.Icons
6 | import androidx.compose.material.icons.filled.Info
7 | import androidx.compose.material.icons.outlined.CheckCircle
8 | import androidx.compose.material.icons.outlined.ErrorOutline
9 | import androidx.compose.material.icons.outlined.Warning
10 | import androidx.compose.material3.ButtonDefaults
11 | import androidx.compose.material3.MaterialTheme
12 | import androidx.compose.runtime.Composable
13 | import androidx.compose.ui.graphics.Brush
14 | import androidx.compose.ui.graphics.Color
15 | import androidx.compose.ui.graphics.Shape
16 | import androidx.compose.ui.graphics.painter.Painter
17 | import androidx.compose.ui.graphics.vector.ImageVector
18 | import androidx.compose.ui.platform.LocalContext
19 | import androidx.compose.ui.res.painterResource
20 | import androidx.compose.ui.res.stringResource
21 | import androidx.compose.ui.text.SpanStyle
22 | import androidx.compose.ui.text.buildAnnotatedString
23 | import androidx.compose.ui.text.withStyle
24 | import com.simform.sscustominfobar.defaultInfoBars.ErrorInfoBar
25 | import com.simform.sscustominfobar.defaultInfoBars.SlideToPerformInfoBar
26 | import com.simform.sscustominfobar.defaultInfoBars.SuccessInfoBar
27 | import com.simform.sscustominfobar.defaultInfoBars.WarningInfoBar
28 | import com.simform.sscustominfobar.main.SSComposeInfoBar
29 | import com.simform.sscustominfobar.main.SSComposeInfoBarColors
30 | import com.simform.sscustominfobar.main.SSComposeInfoBarData
31 | import com.simform.sscustominfobar.main.SSComposeInfoBarDefaults
32 | import com.simform.sscustominfobar.main.SSComposeInfoDuration
33 | import com.simform.sscustominfobar.main.SSComposeInfoHostState
34 | import com.simform.sscustominfobar.main.toSSCustomBackground
35 | import com.simform.sscustominfobar.utils.TextType
36 | import com.simform.sscustominfobar.utils.toTextType
37 | import com.simform.sscustominfobarapp.R
38 | import kotlinx.coroutines.CoroutineScope
39 | import kotlinx.coroutines.CoroutineStart
40 | import kotlinx.coroutines.Job
41 | import kotlinx.coroutines.launch
42 | import kotlin.coroutines.CoroutineContext
43 | import kotlin.coroutines.EmptyCoroutineContext
44 |
45 | /**
46 | * Button type of the buttons displayed on the home screen.
47 | */
48 | enum class ButtonType {
49 | Default,
50 | Error,
51 | Warning,
52 | Success,
53 | AnnotatedText,
54 | GradientDemoBrush,
55 | DrawableDemoSVG,
56 | DrawableDemoPNG,
57 | ActionInfoBar,
58 | SlideToPerformAction
59 | }
60 |
61 | /**
62 | * Function to create [SSComposeInfoBar] based on the pressed button's type.
63 | *
64 | * @param type [ButtonType] of the button pressed.
65 | * @param content [SSComposeInfoBarData] which contains title and message for [SSComposeInfoBar].
66 | * @param isInfinite Flag that represents whether the [SSComposeInfoBar]'s Duration is infinite or not.
67 | * @param shape Shape of the [SSComposeInfoBar].
68 | * @param onClose Called when user clicks on clear button on [SSComposeInfoBar] (Note: This Clear button will only be visible if isInfinite is true).
69 | */
70 | @Composable
71 | fun InfoBarByButtonType(
72 | type: ButtonType,
73 | content: SSComposeInfoBarData,
74 | isInfinite: Boolean,
75 | shape: Shape,
76 | onClose: () -> Unit = {}
77 | ) {
78 | return when (type) {
79 | ButtonType.Default -> DummyDefaultInfoBar(
80 | title = content.title,
81 | description = content.description,
82 | isInfinite = isInfinite,
83 | shape = shape,
84 | onClose = onClose
85 | )
86 |
87 | ButtonType.Error -> ErrorInfoBar(
88 | errorData = SSComposeInfoBarData(
89 | icon = Icons.Outlined.ErrorOutline,
90 | title = content.title,
91 | description = content.description,
92 | ),
93 | isInfinite = isInfinite,
94 | shape = shape,
95 | onCloseClicked = onClose
96 | )
97 |
98 | ButtonType.Warning -> WarningInfoBar(
99 | warningData = SSComposeInfoBarData(
100 | icon = Icons.Outlined.Warning,
101 | title = content.title,
102 | description = content.description,
103 | ),
104 | isInfinite = isInfinite,
105 | shape = shape,
106 | onCloseClicked = onClose
107 | )
108 |
109 | ButtonType.Success -> SuccessInfoBar(
110 | successData = SSComposeInfoBarData(
111 | icon = Icons.Outlined.CheckCircle,
112 | title = content.title,
113 | description = content.description,
114 | ),
115 | isInfinite = isInfinite,
116 | shape = shape,
117 | onCloseClicked = onClose
118 | )
119 |
120 | ButtonType.AnnotatedText -> DummyDefaultInfoBar(
121 | title = content.title,
122 | description = content.description,
123 | isInfinite = isInfinite,
124 | shape = shape,
125 | onClose = onClose
126 | )
127 |
128 | ButtonType.GradientDemoBrush -> DummyGradientBrushDemo(
129 | title = content.title,
130 | description = content.description,
131 | isInfinite = isInfinite,
132 | background = Brush.horizontalGradient(listOf(Color.Red, Color.Green, Color.Blue)),
133 | shape = shape,
134 | onClose = onClose
135 | )
136 |
137 | ButtonType.DrawableDemoSVG -> DummyInfobarWithSVGBackground(
138 | title = content.title,
139 | description = content.description,
140 | background = painterResource(id = R.drawable.liquid_cheese),
141 | isInfinite = isInfinite,
142 | shape = shape,
143 | onClose = onClose
144 | )
145 |
146 | ButtonType.DrawableDemoPNG -> DummyInfoBarWithDrawableBackground(
147 | title = content.title,
148 | background = painterResource(id = R.drawable.wintery_sunburst),
149 | description = content.description,
150 | isInfinite = isInfinite,
151 | shape = shape,
152 | onClose = onClose
153 | )
154 |
155 | ButtonType.ActionInfoBar -> DummyInfoBarWithAction(
156 | title = content.title,
157 | description = content.description,
158 | isInfinite = isInfinite,
159 | shape = shape,
160 | onClose = onClose
161 | )
162 |
163 | ButtonType.SlideToPerformAction -> SlideToPerformInfoBar(
164 | actionText = content.title,
165 | onActionDoneText = stringResource(R.string.slide_complete).toTextType(),
166 | backgroundShape = MaterialTheme.shapes.medium,
167 | sliderShape = MaterialTheme.shapes.medium,
168 | onSlideComplete = onClose
169 | )
170 | }
171 | }
172 |
173 | fun getButtonTitle(buttonType: ButtonType): Int {
174 | return when (buttonType) {
175 | ButtonType.Default -> R.string.show_default_info_bar
176 | ButtonType.Error -> R.string.show_error_info_bar
177 | ButtonType.Warning -> R.string.show_warning_info_bar
178 | ButtonType.Success -> R.string.show_success_info_bar
179 | ButtonType.GradientDemoBrush -> R.string.gradient_demo_using_brush
180 | ButtonType.DrawableDemoSVG -> R.string.drawable_demo_using_svg
181 | ButtonType.DrawableDemoPNG -> R.string.drawable_demo_using_png
182 | ButtonType.AnnotatedText -> R.string.show_annotated_text_info_bar
183 | ButtonType.ActionInfoBar -> R.string.show_composeinfobar_with_action
184 | ButtonType.SlideToPerformAction -> R.string.slide_info_bar_title
185 | }
186 | }
187 |
188 | private val titleAnnotated = buildAnnotatedString {
189 | withStyle(SpanStyle(color = Color.White)) {
190 | append("Hello")
191 | }
192 | withStyle(SpanStyle(color = Color.Blue)) {
193 | append("World")
194 | }
195 | }
196 | private val descAnnotated = buildAnnotatedString {
197 | withStyle(SpanStyle(color = Color.Blue, fontSize = AppDimens.SpMedium)) {
198 | append("This")
199 | }
200 | withStyle(SpanStyle(color = Color.White, fontSize = AppDimens.SpMedium)) {
201 | append(" is")
202 | }
203 | withStyle(SpanStyle(color = Color.Blue, fontSize = AppDimens.SpMedium)) {
204 | append(" an annotated string")
205 | }
206 | }
207 |
208 | /**
209 | * Utility Function to get InfoBar title.
210 | *
211 | * @param context [Context]
212 | * @param buttonType [ButtonType]
213 | * @return
214 | */
215 | fun getInfoBarTitle(context: Context, buttonType: ButtonType): TextType {
216 | return when (buttonType) {
217 | ButtonType.Default -> context.getString(R.string.hey_user_good_morning).toTextType()
218 | ButtonType.Error -> context.getString(R.string.error).toTextType()
219 | ButtonType.Warning -> context.getString(R.string.warning).toTextType()
220 | ButtonType.Success -> context.getString(R.string.success).toTextType()
221 | ButtonType.GradientDemoBrush -> context.getString(R.string.success).toTextType()
222 | ButtonType.DrawableDemoSVG -> context.getString(R.string.success).toTextType()
223 | ButtonType.DrawableDemoPNG -> context.getString(R.string.success).toTextType()
224 | ButtonType.AnnotatedText -> titleAnnotated.toTextType()
225 | ButtonType.ActionInfoBar -> context.getString(R.string.sscomposeinfobar_with_action)
226 | .toTextType()
227 | ButtonType.SlideToPerformAction -> context.getString(R.string.slide_to_perform_action)
228 | .toTextType()
229 | }
230 | }
231 |
232 | /**
233 | * Utility Function to get InfoBar description.
234 | *
235 | * @param context [Context]
236 | * @param buttonType [ButtonType]
237 | * @return
238 | */
239 | fun getInfoBarDescription(context: Context, buttonType: ButtonType): TextType? {
240 | return when (buttonType) {
241 | ButtonType.Default -> context.getString(R.string.we_hope_you_are_doing_well_in_your_life).toTextType()
242 | ButtonType.Error -> context.getString(R.string.failed_to_fetch_data_from_the_server).toTextType()
243 | ButtonType.Warning -> context.getString(R.string.trying_to_access_sensitive_information).toTextType()
244 | ButtonType.Success -> context.getString(R.string.successfully_fetched_network_data).toTextType()
245 | ButtonType.GradientDemoBrush -> context.getString(R.string.successfully_fetched_network_data).toTextType()
246 | ButtonType.DrawableDemoSVG -> context.getString(R.string.successfully_fetched_network_data).toTextType()
247 | ButtonType.DrawableDemoPNG -> context.getString(R.string.successfully_fetched_network_data).toTextType()
248 | ButtonType.AnnotatedText -> descAnnotated.toTextType()
249 | ButtonType.ActionInfoBar -> null
250 | ButtonType.SlideToPerformAction -> null
251 | }
252 | }
253 |
254 | /**
255 | * Dummy InfoBar to show [SSComposeInfoBar] with action.
256 | *
257 | * @param title Title string for the [SSComposeInfoBar].
258 | * @param description Description string for the [SSComposeInfoBar].
259 | * @param isInfinite Flag that decides whether [SSComposeInfoBar]'s duration is infinite.
260 | * @param shape Shape of the [SSComposeInfoBar].
261 | * @param onClose Called when user clicks on the clear button on the [SSComposeInfoBar].
262 | */
263 | @Composable
264 | private fun DummyInfoBarWithAction(
265 | title: TextType,
266 | description: TextType? = null,
267 | isInfinite: Boolean,
268 | shape: Shape,
269 | onClose: () -> Unit
270 | ) {
271 | val context = LocalContext.current
272 | SSComposeInfoBar(
273 | title = title,
274 | description = description,
275 | isInfinite = isInfinite,
276 | shape = shape,
277 | contentColors = SSComposeInfoBarDefaults.colors.copy(
278 | actionButtonColors = ButtonDefaults.elevatedButtonColors(containerColor = Color.White)
279 | ),
280 | onCloseClicked = onClose,
281 | onActionClicked = {
282 | Toast.makeText(context, context.getString(R.string.action_clicked), Toast.LENGTH_SHORT)
283 | .show()
284 | }
285 | )
286 | }
287 |
288 | /**
289 | * Dummy Default [SSComposeInfoBar] for the demo purposes.
290 | *
291 | * @param title Title string for the [SSComposeInfoBar].
292 | * @param description Description string for the [SSComposeInfoBar].
293 | * @param isInfinite Flag that decides whether [SSComposeInfoBar]'s duration is infinite.
294 | * @param shape Shape of the [SSComposeInfoBar].
295 | * @param onClose Called when user clicks on the clear button on the [SSComposeInfoBar].
296 | */
297 | @Composable
298 | private fun DummyDefaultInfoBar(
299 | title: TextType,
300 | description: TextType? = null,
301 | isInfinite: Boolean,
302 | shape: Shape,
303 | onClose: () -> Unit
304 | ) {
305 | SSComposeInfoBar(
306 | title = title,
307 | description = description,
308 | isInfinite = isInfinite,
309 | shape = shape,
310 | onCloseClicked = onClose
311 | )
312 | }
313 |
314 |
315 | /**
316 | * Dummy [SSComposeInfoBar] with Drawable Background for demo purposes.
317 | *
318 | * @param title Title string for the [SSComposeInfoBar].
319 | * @param description Description string for the [SSComposeInfoBar].
320 | * @param background of type [Painter].
321 | * @param isInfinite Flag that decides whether [SSComposeInfoBar]'s duration is infinite.
322 | * @param shape Shape of the [SSComposeInfoBar].
323 | * @param onClose Called when user clicks on the clear button on the [SSComposeInfoBar].
324 | */
325 | @Composable
326 | private fun DummyInfoBarWithDrawableBackground(
327 | title: TextType,
328 | description: TextType? = null,
329 | background: Painter,
330 | isInfinite: Boolean,
331 | shape: Shape,
332 | onClose: () -> Unit
333 | ) {
334 | SSComposeInfoBar(
335 | title = title,
336 | description = description,
337 | customBackground = background.toSSCustomBackground(),
338 | contentColors = SSComposeInfoBarColors(
339 | iconColor = Color.Black,
340 | titleColor = Color.Black,
341 | descriptionColor = Color.Black,
342 | dismissIconColor = Color.Black
343 | ),
344 | shape = shape,
345 | isInfinite = isInfinite,
346 | onCloseClicked = onClose
347 | )
348 | }
349 |
350 | /**
351 | * Dummy [SSComposeInfoBar] with SVG Background for demo purposes.
352 | *
353 | * @param title Title string for the [SSComposeInfoBar].
354 | * @param background of type [Painter].
355 | * @param description Description string for the [SSComposeInfoBar].
356 | * @param isInfinite Flag that decides whether [SSComposeInfoBar]'s duration is infinite.
357 | * @param shape Shape of the [SSComposeInfoBar].
358 | * @param onClose Called when user clicks on the clear button on the [SSComposeInfoBar].
359 | */
360 | @Composable
361 | private fun DummyInfobarWithSVGBackground(
362 | title: TextType,
363 | description: TextType? = null,
364 | background: Painter,
365 | isInfinite: Boolean,
366 | shape: Shape,
367 | onClose: () -> Unit
368 | ) {
369 | SSComposeInfoBar(
370 | title = title,
371 | description = description,
372 | customBackground = background.toSSCustomBackground(),
373 | contentColors = SSComposeInfoBarColors(
374 | iconColor = Color.Black,
375 | titleColor = Color.Black,
376 | descriptionColor = Color.Black,
377 | dismissIconColor = Color.Black
378 | ),
379 | shape = shape,
380 | isInfinite = isInfinite,
381 | onCloseClicked = onClose
382 | )
383 | }
384 |
385 | /**
386 | * Dummy [SSComposeInfoBar] with Gradient Background Using Brush for demo purposes.
387 | *
388 | * @param title Title string for the [SSComposeInfoBar].
389 | * @param description Description string for the [SSComposeInfoBar].
390 | * @param background Background of type [Brush] to be applied to [SSComposeInfoBar].
391 | * @param isInfinite Flag that decides whether [SSComposeInfoBar]'s duration is infinite.
392 | * @param shape Shape of the [SSComposeInfoBar].
393 | * @param onClose Called when user clicks on the clear button on the [SSComposeInfoBar].
394 | */
395 | @Composable
396 | private fun DummyGradientBrushDemo(
397 | title: TextType,
398 | description: TextType? = null,
399 | background: Brush,
400 | isInfinite: Boolean,
401 | shape: Shape,
402 | onClose: () -> Unit
403 | ) {
404 | SSComposeInfoBar(
405 | title = title,
406 | description = description,
407 | customBackground = background.toSSCustomBackground(),
408 | contentColors = SSComposeInfoBarColors(
409 | iconColor = Color.White,
410 | titleColor = Color.White,
411 | descriptionColor = Color.White,
412 | dismissIconColor = Color.White
413 | ),
414 | shape = shape,
415 | isInfinite = isInfinite,
416 | onCloseClicked = onClose
417 | )
418 | }
419 |
420 | /**
421 | * Extension function on [CoroutineScope] to show [SSComposeInfoBar].
422 | *
423 | * @param title title of the [SSComposeInfoBar].
424 | * @param description description of the [SSComposeInfoBar].
425 | * @param composeInfoHostState [SSComposeInfoHostState] used to show the [SSComposeInfoBar].
426 | * @param duration duration for the [SSComposeInfoBar].
427 | * @param context additional to CoroutineScope.coroutineContext context of the coroutine.
428 | * @param start coroutine start option. The default value is CoroutineStart.DEFAULT.
429 | *
430 | * @return [Job] to control the coroutine.
431 | */
432 | fun CoroutineScope.showSSComposeInfoBar(
433 | title: TextType,
434 | description: TextType? = null,
435 | icon: ImageVector = Icons.Default.Info,
436 | composeInfoHostState: SSComposeInfoHostState,
437 | duration: SSComposeInfoDuration,
438 | context: CoroutineContext = EmptyCoroutineContext,
439 | start: CoroutineStart = CoroutineStart.DEFAULT
440 | ) = launch(context, start) {
441 | composeInfoHostState.show(
442 | infoBarData = SSComposeInfoBarData(title, description, icon),
443 | duration = duration
444 | )
445 | }
--------------------------------------------------------------------------------
/app/src/main/res/drawable-v24/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
15 |
18 |
21 |
22 |
23 |
24 |
30 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
10 |
15 |
20 |
25 |
30 |
35 |
40 |
45 |
50 |
55 |
60 |
65 |
70 |
75 |
80 |
85 |
90 |
95 |
100 |
105 |
110 |
115 |
120 |
125 |
130 |
135 |
140 |
145 |
150 |
155 |
160 |
165 |
170 |
171 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/liquid_cheese.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
12 |
15 |
18 |
21 |
24 |
27 |
30 |
33 |
36 |
39 |
40 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/wintery_sunburst.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SimformSolutionsPvtLtd/SSCompose-CustomInfoBar/978fb86cc5af051edc976dd35d9cdbf0e5deef33/app/src/main/res/drawable/wintery_sunburst.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SimformSolutionsPvtLtd/SSCompose-CustomInfoBar/978fb86cc5af051edc976dd35d9cdbf0e5deef33/app/src/main/res/mipmap-hdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SimformSolutionsPvtLtd/SSCompose-CustomInfoBar/978fb86cc5af051edc976dd35d9cdbf0e5deef33/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SimformSolutionsPvtLtd/SSCompose-CustomInfoBar/978fb86cc5af051edc976dd35d9cdbf0e5deef33/app/src/main/res/mipmap-mdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SimformSolutionsPvtLtd/SSCompose-CustomInfoBar/978fb86cc5af051edc976dd35d9cdbf0e5deef33/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SimformSolutionsPvtLtd/SSCompose-CustomInfoBar/978fb86cc5af051edc976dd35d9cdbf0e5deef33/app/src/main/res/mipmap-xhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SimformSolutionsPvtLtd/SSCompose-CustomInfoBar/978fb86cc5af051edc976dd35d9cdbf0e5deef33/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SimformSolutionsPvtLtd/SSCompose-CustomInfoBar/978fb86cc5af051edc976dd35d9cdbf0e5deef33/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SimformSolutionsPvtLtd/SSCompose-CustomInfoBar/978fb86cc5af051edc976dd35d9cdbf0e5deef33/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SimformSolutionsPvtLtd/SSCompose-CustomInfoBar/978fb86cc5af051edc976dd35d9cdbf0e5deef33/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SimformSolutionsPvtLtd/SSCompose-CustomInfoBar/978fb86cc5af051edc976dd35d9cdbf0e5deef33/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | SSCustomInfoBar
4 |
5 | Info Bar Settings
6 | SSComposeInfoBar Demo
7 | Duration
8 | Animation Type
9 | Direction
10 | Cancel
11 | Confirm
12 | Show Default ComposeInfoBar
13 | Hey user, Good morning
14 | We hope you are doing well in your life.
15 | Show Error ComposeInfoBar
16 | Error!!
17 | Failed to fetch data from the server.
18 | Show Warning ComposeInfoBar
19 | Trying to access sensitive information.
20 | Warning!!
21 | Show Success ComposeInfoBar
22 | Success!!
23 | Successfully fetched network data.
24 | Show Annotated Text Info Bar
25 | Drawable Demo Using SVG
26 | Gradient Demo using brush
27 | Drawable demo using PNG
28 | Show ComposeInfoBar with action
29 | InfoBar with Action
30 | Action Clicked
31 | Info Bar Dismissed Successfully!!
32 | Swipe to dismiss
33 | Network state observation
34 | Slide to perform action
35 | Slide Info Bar Demo
36 | Slide Complete
37 |
--------------------------------------------------------------------------------
/build.gradle.kts:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 | plugins {
3 | alias(libs.plugins.androidApplication) apply false
4 | alias(libs.plugins.jetbrainsKotlinAndroid) apply false
5 | alias(libs.plugins.androidLibrary) apply false
6 | }
--------------------------------------------------------------------------------
/gifs/action.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SimformSolutionsPvtLtd/SSCompose-CustomInfoBar/978fb86cc5af051edc976dd35d9cdbf0e5deef33/gifs/action.gif
--------------------------------------------------------------------------------
/gifs/annotated.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SimformSolutionsPvtLtd/SSCompose-CustomInfoBar/978fb86cc5af051edc976dd35d9cdbf0e5deef33/gifs/annotated.gif
--------------------------------------------------------------------------------
/gifs/default.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SimformSolutionsPvtLtd/SSCompose-CustomInfoBar/978fb86cc5af051edc976dd35d9cdbf0e5deef33/gifs/default.gif
--------------------------------------------------------------------------------
/gifs/error.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SimformSolutionsPvtLtd/SSCompose-CustomInfoBar/978fb86cc5af051edc976dd35d9cdbf0e5deef33/gifs/error.gif
--------------------------------------------------------------------------------
/gifs/expand_shrink.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SimformSolutionsPvtLtd/SSCompose-CustomInfoBar/978fb86cc5af051edc976dd35d9cdbf0e5deef33/gifs/expand_shrink.gif
--------------------------------------------------------------------------------
/gifs/fade_in_out.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SimformSolutionsPvtLtd/SSCompose-CustomInfoBar/978fb86cc5af051edc976dd35d9cdbf0e5deef33/gifs/fade_in_out.gif
--------------------------------------------------------------------------------
/gifs/gradient.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SimformSolutionsPvtLtd/SSCompose-CustomInfoBar/978fb86cc5af051edc976dd35d9cdbf0e5deef33/gifs/gradient.gif
--------------------------------------------------------------------------------
/gifs/horizontal_slide.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SimformSolutionsPvtLtd/SSCompose-CustomInfoBar/978fb86cc5af051edc976dd35d9cdbf0e5deef33/gifs/horizontal_slide.gif
--------------------------------------------------------------------------------
/gifs/internet_observer.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SimformSolutionsPvtLtd/SSCompose-CustomInfoBar/978fb86cc5af051edc976dd35d9cdbf0e5deef33/gifs/internet_observer.gif
--------------------------------------------------------------------------------
/gifs/png.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SimformSolutionsPvtLtd/SSCompose-CustomInfoBar/978fb86cc5af051edc976dd35d9cdbf0e5deef33/gifs/png.gif
--------------------------------------------------------------------------------
/gifs/scale.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SimformSolutionsPvtLtd/SSCompose-CustomInfoBar/978fb86cc5af051edc976dd35d9cdbf0e5deef33/gifs/scale.gif
--------------------------------------------------------------------------------
/gifs/scale_vertical.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SimformSolutionsPvtLtd/SSCompose-CustomInfoBar/978fb86cc5af051edc976dd35d9cdbf0e5deef33/gifs/scale_vertical.gif
--------------------------------------------------------------------------------
/gifs/slide_to_action.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SimformSolutionsPvtLtd/SSCompose-CustomInfoBar/978fb86cc5af051edc976dd35d9cdbf0e5deef33/gifs/slide_to_action.gif
--------------------------------------------------------------------------------
/gifs/success.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SimformSolutionsPvtLtd/SSCompose-CustomInfoBar/978fb86cc5af051edc976dd35d9cdbf0e5deef33/gifs/success.gif
--------------------------------------------------------------------------------
/gifs/svg.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SimformSolutionsPvtLtd/SSCompose-CustomInfoBar/978fb86cc5af051edc976dd35d9cdbf0e5deef33/gifs/svg.gif
--------------------------------------------------------------------------------
/gifs/swipe_to_dismiss.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SimformSolutionsPvtLtd/SSCompose-CustomInfoBar/978fb86cc5af051edc976dd35d9cdbf0e5deef33/gifs/swipe_to_dismiss.gif
--------------------------------------------------------------------------------
/gifs/warning.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SimformSolutionsPvtLtd/SSCompose-CustomInfoBar/978fb86cc5af051edc976dd35d9cdbf0e5deef33/gifs/warning.gif
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 | # IDE (e.g. Android Studio) users:
3 | # Gradle settings configured through the IDE *will override*
4 | # any settings specified in this file.
5 | # For more details on how to configure your build environment visit
6 | # http://www.gradle.org/docs/current/userguide/build_environment.html
7 | # Specifies the JVM arguments used for the daemon process.
8 | # The setting is particularly useful for tweaking memory settings.
9 | org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
10 | # When configured, Gradle will run in incubating parallel mode.
11 | # This option should only be used with decoupled projects. For more details, visit
12 | # https://developer.android.com/r/tools/gradle-multi-project-decoupled-projects
13 | # org.gradle.parallel=true
14 | # AndroidX package structure to make it clearer which packages are bundled with the
15 | # Android operating system, and which are packaged with your app's APK
16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn
17 | android.useAndroidX=true
18 | # Kotlin code style for this project: "official" or "obsolete":
19 | kotlin.code.style=official
20 | # Enables namespacing of each library's R class so that its R class includes only the
21 | # resources declared in the library itself and none from the library's dependencies,
22 | # thereby reducing the size of the R class for that library
23 | android.nonTransitiveRClass=true
--------------------------------------------------------------------------------
/gradle/libs.versions.toml:
--------------------------------------------------------------------------------
1 | [versions]
2 | activityCompose = "1.9.0"
3 | agp = "8.3.2"
4 | appcompat = "1.7.0"
5 | composeBom = "2024.06.00"
6 | coreKtx = "1.13.1"
7 | espressoCore = "3.6.1"
8 | junit = "4.13.2"
9 | junitVersion = "1.2.1"
10 | kotlin = "1.9.23"
11 | lifecycleRuntimeKtx = "2.8.3"
12 | uiTooling = "1.6.8"
13 |
14 | # For build configuration
15 | compileSDK = "34"
16 | minSDK = "21"
17 | targetSDK = "34"
18 | versionCode = "1"
19 | applicationId = "com.simform.sscustominfobarapp"
20 |
21 | # For publishing the library
22 | mavenpublish = "maven-publish"
23 | release = "release"
24 | group-id = "com.simform.sscustominfobar"
25 | artifact-id = "sscustominfobar"
26 | version = "1.0"
27 |
28 |
29 | [libraries]
30 | androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
31 | androidx-lifecycle-runtime-compose = { group = "androidx.lifecycle", name = "lifecycle-runtime-compose", version.ref = "lifecycleRuntimeKtx" }
32 | junit = { group = "junit", name = "junit", version.ref = "junit" }
33 | androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" }
34 | androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" }
35 | androidx-lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "lifecycleRuntimeKtx" }
36 | androidx-activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "activityCompose" }
37 | androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "composeBom" }
38 | androidx-ui = { group = "androidx.compose.ui", name = "ui" }
39 | androidx-ui-graphics = { group = "androidx.compose.ui", name = "ui-graphics" }
40 | androidx-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling" }
41 | androidx-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-tooling-preview" }
42 | androidx-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" }
43 | androidx-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" }
44 | androidx-material3 = { group = "androidx.compose.material3", name = "material3" }
45 | androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" }
46 | ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling", version.ref = "uiTooling" }
47 | material-icons-extended = { group = "androidx.compose.material", name = "material-icons-extended" }
48 |
49 | [plugins]
50 | androidApplication = { id = "com.android.application", version.ref = "agp" }
51 | jetbrainsKotlinAndroid = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
52 | androidLibrary = { id = "com.android.library", version.ref = "agp" }
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SimformSolutionsPvtLtd/SSCompose-CustomInfoBar/978fb86cc5af051edc976dd35d9cdbf0e5deef33/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Mon Mar 18 12:44:24 IST 2024
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip
5 | zipStoreBase=GRADLE_USER_HOME
6 | zipStorePath=wrapper/dists
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | #
4 | # Copyright 2015 the original author or authors.
5 | #
6 | # Licensed under the Apache License, Version 2.0 (the "License");
7 | # you may not use this file except in compliance with the License.
8 | # You may obtain a copy of the License at
9 | #
10 | # https://www.apache.org/licenses/LICENSE-2.0
11 | #
12 | # Unless required by applicable law or agreed to in writing, software
13 | # distributed under the License is distributed on an "AS IS" BASIS,
14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | # See the License for the specific language governing permissions and
16 | # limitations under the License.
17 | #
18 |
19 | ##############################################################################
20 | ##
21 | ## Gradle start up script for UN*X
22 | ##
23 | ##############################################################################
24 |
25 | # Attempt to set APP_HOME
26 | # Resolve links: $0 may be a link
27 | PRG="$0"
28 | # Need this for relative symlinks.
29 | while [ -h "$PRG" ] ; do
30 | ls=`ls -ld "$PRG"`
31 | link=`expr "$ls" : '.*-> \(.*\)$'`
32 | if expr "$link" : '/.*' > /dev/null; then
33 | PRG="$link"
34 | else
35 | PRG=`dirname "$PRG"`"/$link"
36 | fi
37 | done
38 | SAVED="`pwd`"
39 | cd "`dirname \"$PRG\"`/" >/dev/null
40 | APP_HOME="`pwd -P`"
41 | cd "$SAVED" >/dev/null
42 |
43 | APP_NAME="Gradle"
44 | APP_BASE_NAME=`basename "$0"`
45 |
46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
48 |
49 | # Use the maximum available, or set MAX_FD != -1 to use that value.
50 | MAX_FD="maximum"
51 |
52 | warn () {
53 | echo "$*"
54 | }
55 |
56 | die () {
57 | echo
58 | echo "$*"
59 | echo
60 | exit 1
61 | }
62 |
63 | # OS specific support (must be 'true' or 'false').
64 | cygwin=false
65 | msys=false
66 | darwin=false
67 | nonstop=false
68 | case "`uname`" in
69 | CYGWIN* )
70 | cygwin=true
71 | ;;
72 | Darwin* )
73 | darwin=true
74 | ;;
75 | MINGW* )
76 | msys=true
77 | ;;
78 | NONSTOP* )
79 | nonstop=true
80 | ;;
81 | esac
82 |
83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
84 |
85 |
86 | # Determine the Java command to use to start the JVM.
87 | if [ -n "$JAVA_HOME" ] ; then
88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
89 | # IBM's JDK on AIX uses strange locations for the executables
90 | JAVACMD="$JAVA_HOME/jre/sh/java"
91 | else
92 | JAVACMD="$JAVA_HOME/bin/java"
93 | fi
94 | if [ ! -x "$JAVACMD" ] ; then
95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
96 |
97 | Please set the JAVA_HOME variable in your environment to match the
98 | location of your Java installation."
99 | fi
100 | else
101 | JAVACMD="java"
102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
103 |
104 | Please set the JAVA_HOME variable in your environment to match the
105 | location of your Java installation."
106 | fi
107 |
108 | # Increase the maximum file descriptors if we can.
109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
110 | MAX_FD_LIMIT=`ulimit -H -n`
111 | if [ $? -eq 0 ] ; then
112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
113 | MAX_FD="$MAX_FD_LIMIT"
114 | fi
115 | ulimit -n $MAX_FD
116 | if [ $? -ne 0 ] ; then
117 | warn "Could not set maximum file descriptor limit: $MAX_FD"
118 | fi
119 | else
120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
121 | fi
122 | fi
123 |
124 | # For Darwin, add options to specify how the application appears in the dock
125 | if $darwin; then
126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
127 | fi
128 |
129 | # For Cygwin or MSYS, switch paths to Windows format before running java
130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
133 |
134 | JAVACMD=`cygpath --unix "$JAVACMD"`
135 |
136 | # We build the pattern for arguments to be converted via cygpath
137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
138 | SEP=""
139 | for dir in $ROOTDIRSRAW ; do
140 | ROOTDIRS="$ROOTDIRS$SEP$dir"
141 | SEP="|"
142 | done
143 | OURCYGPATTERN="(^($ROOTDIRS))"
144 | # Add a user-defined pattern to the cygpath arguments
145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
147 | fi
148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
149 | i=0
150 | for arg in "$@" ; do
151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
153 |
154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
156 | else
157 | eval `echo args$i`="\"$arg\""
158 | fi
159 | i=`expr $i + 1`
160 | done
161 | case $i in
162 | 0) set -- ;;
163 | 1) set -- "$args0" ;;
164 | 2) set -- "$args0" "$args1" ;;
165 | 3) set -- "$args0" "$args1" "$args2" ;;
166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;;
167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
172 | esac
173 | fi
174 |
175 | # Escape application args
176 | save () {
177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
178 | echo " "
179 | }
180 | APP_ARGS=`save "$@"`
181 |
182 | # Collect all arguments for the java command, following the shell quoting and substitution rules
183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
184 |
185 | exec "$JAVACMD" "$@"
186 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @rem
2 | @rem Copyright 2015 the original author or authors.
3 | @rem
4 | @rem Licensed under the Apache License, Version 2.0 (the "License");
5 | @rem you may not use this file except in compliance with the License.
6 | @rem You may obtain a copy of the License at
7 | @rem
8 | @rem https://www.apache.org/licenses/LICENSE-2.0
9 | @rem
10 | @rem Unless required by applicable law or agreed to in writing, software
11 | @rem distributed under the License is distributed on an "AS IS" BASIS,
12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | @rem See the License for the specific language governing permissions and
14 | @rem limitations under the License.
15 | @rem
16 |
17 | @if "%DEBUG%" == "" @echo off
18 | @rem ##########################################################################
19 | @rem
20 | @rem Gradle startup script for Windows
21 | @rem
22 | @rem ##########################################################################
23 |
24 | @rem Set local scope for the variables with windows NT shell
25 | if "%OS%"=="Windows_NT" setlocal
26 |
27 | set DIRNAME=%~dp0
28 | if "%DIRNAME%" == "" set DIRNAME=.
29 | set APP_BASE_NAME=%~n0
30 | set APP_HOME=%DIRNAME%
31 |
32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter.
33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
34 |
35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
37 |
38 | @rem Find java.exe
39 | if defined JAVA_HOME goto findJavaFromJavaHome
40 |
41 | set JAVA_EXE=java.exe
42 | %JAVA_EXE% -version >NUL 2>&1
43 | if "%ERRORLEVEL%" == "0" goto execute
44 |
45 | echo.
46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
47 | echo.
48 | echo Please set the JAVA_HOME variable in your environment to match the
49 | echo location of your Java installation.
50 |
51 | goto fail
52 |
53 | :findJavaFromJavaHome
54 | set JAVA_HOME=%JAVA_HOME:"=%
55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
56 |
57 | if exist "%JAVA_EXE%" goto execute
58 |
59 | echo.
60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
61 | echo.
62 | echo Please set the JAVA_HOME variable in your environment to match the
63 | echo location of your Java installation.
64 |
65 | goto fail
66 |
67 | :execute
68 | @rem Setup the command line
69 |
70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
71 |
72 |
73 | @rem Execute Gradle
74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
75 |
76 | :end
77 | @rem End local scope for the variables with windows NT shell
78 | if "%ERRORLEVEL%"=="0" goto mainEnd
79 |
80 | :fail
81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
82 | rem the _cmd.exe /c_ return code!
83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
84 | exit /b 1
85 |
86 | :mainEnd
87 | if "%OS%"=="Windows_NT" endlocal
88 |
89 | :omega
90 |
--------------------------------------------------------------------------------
/settings.gradle.kts:
--------------------------------------------------------------------------------
1 | pluginManagement {
2 | repositories {
3 | google {
4 | content {
5 | includeGroupByRegex("com\\.android.*")
6 | includeGroupByRegex("com\\.google.*")
7 | includeGroupByRegex("androidx.*")
8 | }
9 | }
10 | mavenCentral()
11 | gradlePluginPortal()
12 | }
13 | }
14 |
15 | @Suppress("UnstableApiUsage")
16 | dependencyResolutionManagement {
17 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
18 | repositories {
19 | google()
20 | mavenCentral()
21 | maven { url = uri("https://jitpack.io") }
22 | }
23 | }
24 |
25 | rootProject.name = "SSCustomInfoBar"
26 | include(":app")
27 | include(":sscustominfobar")
28 |
--------------------------------------------------------------------------------
/sscustominfobar/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/sscustominfobar/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | alias(libs.plugins.androidLibrary)
3 | alias(libs.plugins.jetbrainsKotlinAndroid)
4 | id(libs.versions.mavenpublish.get())
5 | }
6 |
7 | afterEvaluate {
8 | publishing {
9 | publications {
10 | // Creates a Maven publication called "release".
11 | create(libs.versions.release.get()) {
12 | from(components[libs.versions.release.get()])
13 | groupId = libs.versions.group.id.get()
14 | artifactId = libs.versions.artifact.id.get()
15 | version = libs.versions.version.get()
16 | }
17 | }
18 | }
19 | }
20 |
21 | android {
22 | namespace = libs.versions.group.id.get()
23 | compileSdk = libs.versions.compileSDK.get().toInt()
24 |
25 | defaultConfig {
26 | minSdk = libs.versions.minSDK.get().toInt()
27 |
28 | testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
29 | consumerProguardFiles("consumer-rules.pro")
30 | }
31 |
32 | buildTypes {
33 | release {
34 | isMinifyEnabled = false
35 | proguardFiles(
36 | getDefaultProguardFile("proguard-android-optimize.txt"),
37 | "proguard-rules.pro"
38 | )
39 | }
40 | }
41 | compileOptions {
42 | sourceCompatibility = JavaVersion.VERSION_17
43 | targetCompatibility = JavaVersion.VERSION_17
44 | }
45 | kotlinOptions {
46 | jvmTarget = "17"
47 | }
48 | buildFeatures {
49 | compose = true
50 | }
51 | composeOptions {
52 | kotlinCompilerExtensionVersion = "1.5.11"
53 | }
54 | }
55 |
56 | dependencies {
57 |
58 | implementation(platform(libs.androidx.compose.bom))
59 | implementation(libs.androidx.core.ktx)
60 | implementation(libs.androidx.appcompat)
61 | implementation(libs.androidx.ui)
62 | implementation(libs.material.icons.extended)
63 | implementation(libs.androidx.ui.graphics)
64 | implementation(libs.androidx.ui.tooling.preview)
65 | implementation(libs.androidx.material3)
66 | implementation(libs.androidx.lifecycle.runtime.compose)
67 | testImplementation(libs.junit)
68 | androidTestImplementation(libs.androidx.junit)
69 | androidTestImplementation(libs.androidx.espresso.core)
70 | debugImplementation(libs.ui.tooling)
71 | }
--------------------------------------------------------------------------------
/sscustominfobar/consumer-rules.pro:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SimformSolutionsPvtLtd/SSCompose-CustomInfoBar/978fb86cc5af051edc976dd35d9cdbf0e5deef33/sscustominfobar/consumer-rules.pro
--------------------------------------------------------------------------------
/sscustominfobar/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/sscustominfobar/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/sscustominfobar/src/main/java/com/simform/sscustominfobar/animation/AnimationUtil.kt:
--------------------------------------------------------------------------------
1 | package com.simform.sscustominfobar.animation
2 |
3 | import androidx.compose.animation.core.animateFloatAsState
4 | import androidx.compose.animation.EnterTransition
5 | import androidx.compose.animation.ExitTransition
6 | import androidx.compose.animation.core.tween
7 | import androidx.compose.animation.expandVertically
8 | import androidx.compose.animation.fadeIn
9 | import androidx.compose.animation.fadeOut
10 | import androidx.compose.animation.scaleIn
11 | import androidx.compose.animation.scaleOut
12 | import androidx.compose.animation.shrinkVertically
13 | import androidx.compose.animation.slideInHorizontally
14 | import androidx.compose.animation.slideInVertically
15 | import androidx.compose.animation.slideOutHorizontally
16 | import androidx.compose.animation.slideOutVertically
17 | import androidx.compose.runtime.Composable
18 | import androidx.compose.runtime.State
19 | import androidx.compose.ui.platform.LocalDensity
20 | import androidx.compose.ui.res.stringResource
21 | import com.simform.sscustominfobar.R
22 | import androidx.compose.ui.Alignment
23 | import com.simform.sscustominfobar.main.SSComposeInfoBar
24 | import com.simform.sscustominfobar.main.SSComposeInfoBarDefaults
25 | import com.simform.sscustominfobar.main.SSComposeInfoBarDirection
26 |
27 | internal const val DefaultHidingAnimationDuration = 300
28 | internal const val DefaultAnimationDuration = 300
29 | internal const val ExtraDelayForNewInfoBar = 300L
30 |
31 | enum class AnimationType {
32 | SlideVertically,
33 | SlideHorizontally,
34 | Fade,
35 | Scale,
36 | ScaleVertically,
37 | ExpandShrinkVertically
38 | }
39 |
40 | /**
41 | * Internal function to get enter animation of compose info bar based on [SSComposeInfoBarDirection] provided
42 | *
43 | * Example: If the animation direction is from top to bottom then we will get slideInVertically with initial offset from top
44 | * and if the animation direction is from bottom to top then the initial offset will be set to outside of bottom of the screen
45 | * @param direction direction from which [SSComposeInfoBar] comes in and exits.
46 | * @param animationType [AnimationType] the type of animation.
47 | * @param duration duration of animation in milliseconds
48 | */
49 | internal fun getEnterAnimation(
50 | direction: SSComposeInfoBarDirection,
51 | animationType: AnimationType,
52 | duration: Int = DefaultAnimationDuration
53 | ): EnterTransition {
54 | when (animationType) {
55 | AnimationType.SlideVertically -> return slideInVertically(
56 | tween(duration),
57 | initialOffsetY = { height ->
58 | if (direction == SSComposeInfoBarDirection.Top) {
59 | -height
60 | } else {
61 | height
62 | }
63 | })
64 |
65 | AnimationType.SlideHorizontally -> return slideInHorizontally(
66 | tween(duration),
67 | initialOffsetX = { width ->
68 | -width
69 | }
70 | )
71 |
72 | AnimationType.Fade -> return fadeIn(
73 | tween(duration)
74 | )
75 |
76 | AnimationType.Scale -> return scaleIn(
77 | tween(duration)
78 | )
79 |
80 | AnimationType.ScaleVertically -> return (scaleIn(
81 | tween(duration)
82 | ) + slideInVertically(tween(duration),
83 | initialOffsetY = { height ->
84 | if (direction == SSComposeInfoBarDirection.Top) {
85 | -height
86 | } else {
87 | height
88 | }
89 | }))
90 |
91 | AnimationType.ExpandShrinkVertically -> return expandVertically(
92 | tween(duration),
93 | expandFrom = if (direction == SSComposeInfoBarDirection.Top) Alignment.Top else Alignment.Bottom
94 | )
95 | }
96 | }
97 |
98 | /**
99 | * Internal function to get exit animation of compose info bar based on [SSComposeInfoBarDirection] provided
100 | *
101 | * Example: If the animation direction is from top then we will get slideInVertically with target offset from top
102 | * and if the animation direction is from bottom then the target offset will be set to outside of bottom of the screen
103 | * @param direction direction from which [SSComposeInfoBar] comes in and exits.
104 | * @param animationType [AnimationType] the type of animation.
105 | * @param duration duration of animation in milliseconds
106 | */
107 | internal fun getExitAnimation(
108 | direction: SSComposeInfoBarDirection,
109 | animationType: AnimationType,
110 | duration: Int = DefaultAnimationDuration
111 | ): ExitTransition {
112 | when (animationType) {
113 | AnimationType.SlideVertically -> return slideOutVertically(
114 | tween(duration),
115 | targetOffsetY = { height ->
116 | if (direction == SSComposeInfoBarDirection.Top) {
117 | -height
118 | } else {
119 | height
120 | }
121 | })
122 |
123 | AnimationType.SlideHorizontally -> return slideOutHorizontally(
124 | tween(duration),
125 | targetOffsetX = { width ->
126 | -width
127 | }
128 | )
129 |
130 | AnimationType.Fade -> return fadeOut(
131 | tween(duration)
132 | )
133 |
134 | AnimationType.Scale -> return scaleOut(
135 | tween(duration)
136 | )
137 |
138 | AnimationType.ScaleVertically -> return (scaleOut(
139 | tween(duration)
140 | ) + slideOutVertically(
141 | tween(duration),
142 | targetOffsetY = { height ->
143 | if (direction == SSComposeInfoBarDirection.Top) {
144 | -height
145 | } else {
146 | height
147 | }
148 | }))
149 |
150 | AnimationType.ExpandShrinkVertically -> return shrinkVertically(
151 | tween(duration),
152 | shrinkTowards = if (direction == SSComposeInfoBarDirection.Top) Alignment.Top else Alignment.Bottom
153 | )
154 | }
155 | }
156 |
157 | /**
158 | * An animation utility method that returns animated yOffset for [SSComposeInfoBar] when
159 | * scroll to hide and show is enabled.
160 | *
161 | * @param shouldBeVisible flag that denoted whether the [SSComposeInfoBar] should be visible or not.
162 | * @param direction [SSComposeInfoBarDirection] to show and hide [SSComposeInfoBar] correctly.
163 | * @param duration Animation duration in milliseconds, default is set to 300 ms.
164 | * @return [State] object of type float that can be observed.
165 | */
166 | @Composable
167 | fun getAnimatedOffset(
168 | shouldBeVisible: Boolean,
169 | direction: SSComposeInfoBarDirection,
170 | duration: Int = DefaultHidingAnimationDuration
171 | ): State {
172 | val density = LocalDensity.current
173 | val shownOffset = 0f
174 | val hiddenOffset =
175 | if (direction == SSComposeInfoBarDirection.Top) with(density) { -SSComposeInfoBarDefaults.defaultHeight.toPx() }
176 | else with(density) { SSComposeInfoBarDefaults.defaultHeight.toPx() }
177 | return animateFloatAsState(
178 | targetValue = if (shouldBeVisible) shownOffset else hiddenOffset,
179 | animationSpec = tween(duration),
180 | label = stringResource(R.string.scroll_to_hide_animation)
181 | )
182 | }
--------------------------------------------------------------------------------
/sscustominfobar/src/main/java/com/simform/sscustominfobar/defaultInfoBars/ErrorInfoBar.kt:
--------------------------------------------------------------------------------
1 | package com.simform.sscustominfobar.defaultInfoBars
2 |
3 | import androidx.compose.material3.ButtonDefaults
4 | import androidx.compose.material3.LocalTextStyle
5 | import androidx.compose.runtime.Composable
6 | import androidx.compose.ui.Modifier
7 | import androidx.compose.ui.graphics.Shape
8 | import androidx.compose.ui.text.TextStyle
9 | import com.simform.sscustominfobar.main.SSComposeInfoBar
10 | import com.simform.sscustominfobar.main.SSComposeInfoBarColors
11 | import com.simform.sscustominfobar.main.SSComposeInfoBarData
12 | import com.simform.sscustominfobar.main.SSComposeInfoBarDefaults
13 | import com.simform.sscustominfobar.main.toSSCustomBackground
14 | import com.simform.sscustominfobar.res.errorRed
15 | import com.simform.sscustominfobar.res.white
16 |
17 | private val errorBackgroundColor = errorRed
18 | private val errorContentColor = white
19 |
20 | /**
21 | * Custom [SSComposeInfoBar] that is created to represent an error themed SSComposeInfoBar.
22 | *
23 | * @param modifier The modifier which will be applied to the [ErrorInfoBar].
24 | * @param errorData The [SSComposeInfoBarData] that contains the title and description of the error.
25 | * @param textStyle The [TextStyle] to be applied to all the text in [SSComposeInfoBar].
26 | * @param shape The [Shape] of the [ErrorInfoBar].
27 | * @param onCloseClicked Called when user clicks on the close icon in [SSComposeInfoBar].
28 | * @param isInfinite The flag that represents whether the duration of the [SSComposeInfoBar] is Infinite or not.
29 | */
30 | @Composable
31 | fun ErrorInfoBar(
32 | modifier: Modifier = Modifier,
33 | errorData: SSComposeInfoBarData,
34 | textStyle: TextStyle = LocalTextStyle.current,
35 | shape: Shape = SSComposeInfoBarDefaults.shape,
36 | onCloseClicked: () -> Unit = {},
37 | isInfinite: Boolean = false
38 | ) {
39 | SSComposeInfoBar(
40 | modifier = modifier,
41 | title = errorData.title,
42 | titleStyle = textStyle,
43 | description = errorData.description,
44 | shape = shape,
45 | icon = errorData.icon,
46 | customBackground = errorBackgroundColor.toSSCustomBackground(),
47 | contentColors = SSComposeInfoBarColors(
48 | iconColor = errorContentColor,
49 | titleColor = errorContentColor,
50 | descriptionColor = errorContentColor,
51 | dismissIconColor = errorContentColor,
52 | actionButtonColors = ButtonDefaults.elevatedButtonColors()
53 | ),
54 | onCloseClicked = onCloseClicked,
55 | isInfinite = isInfinite
56 | )
57 | }
--------------------------------------------------------------------------------
/sscustominfobar/src/main/java/com/simform/sscustominfobar/defaultInfoBars/OfflineInfoBar.kt:
--------------------------------------------------------------------------------
1 | package com.simform.sscustominfobar.defaultInfoBars
2 |
3 | import androidx.compose.material3.LocalTextStyle
4 | import androidx.compose.runtime.Composable
5 | import androidx.compose.ui.Modifier
6 | import androidx.compose.ui.graphics.RectangleShape
7 | import androidx.compose.ui.graphics.Shape
8 | import androidx.compose.ui.text.TextStyle
9 | import com.simform.sscustominfobar.main.SSComposeInfoBar
10 | import com.simform.sscustominfobar.main.SSComposeInfoBarData
11 |
12 | /**
13 | * Custom [SSComposeInfoBar] that is created to represent an offline themed SSComposeInfoBar.
14 | *
15 | * @param modifier The modifier which will be applied to the [OfflineInfoBar].
16 | * @param offlineData The [SSComposeInfoBarData] that contains the title and description of the offlineInfoBar.
17 | * @param textStyle The [TextStyle] to be applied to all the text in [SSComposeInfoBar].
18 | * @param shape The [Shape] of the [OfflineInfoBar].
19 | */
20 | @Composable
21 | fun OfflineInfoBar(
22 | modifier: Modifier = Modifier,
23 | offlineData: SSComposeInfoBarData,
24 | textStyle: TextStyle = LocalTextStyle.current,
25 | shape: Shape = RectangleShape
26 | ) {
27 | SSComposeInfoBar(
28 | modifier = modifier,
29 | title = offlineData.title,
30 | titleStyle = textStyle,
31 | description = offlineData.description,
32 | shape = shape,
33 | icon = offlineData.icon
34 | )
35 | }
--------------------------------------------------------------------------------
/sscustominfobar/src/main/java/com/simform/sscustominfobar/defaultInfoBars/SlideToPerformActionInfoBar.kt:
--------------------------------------------------------------------------------
1 | package com.simform.sscustominfobar.defaultInfoBars
2 |
3 | import androidx.compose.foundation.layout.PaddingValues
4 | import androidx.compose.foundation.shape.CircleShape
5 | import androidx.compose.material.icons.Icons
6 | import androidx.compose.material.icons.rounded.ChevronRight
7 | import androidx.compose.runtime.Composable
8 | import androidx.compose.ui.Modifier
9 | import androidx.compose.ui.graphics.Shape
10 | import androidx.compose.ui.graphics.vector.ImageVector
11 | import com.simform.sscustominfobar.main.SSComposeInfoBar
12 | import com.simform.sscustominfobar.main.SSComposeInfoBarDefaults
13 | import com.simform.sscustominfobar.utils.TextType
14 |
15 | /**
16 | * Demo Slide to perform action [SSComposeInfoBar]
17 | *
18 | * @param actionText The text of the Action in the [SSComposeInfoBar].
19 | * @param onActionDoneText The text to be shown in the [SSComposeInfoBar] when action is performed.
20 | * @param modifier The [modifier] to be applied to the [SSComposeInfoBar].
21 | * @param sliderIcon The [ImageVector] that will be displayed inside of slider.
22 | * @param backgroundShape The [Shape] of the background of the [SSComposeInfoBar].
23 | * @param sliderShape The [Shape] of the slider.
24 | * @param onSlideComplete The lambda block to be executed when the sliding is completed.
25 | */
26 | @Composable
27 | fun SlideToPerformInfoBar(
28 | actionText: TextType,
29 | onActionDoneText: TextType,
30 | modifier: Modifier = Modifier,
31 | sliderIcon: ImageVector = Icons.Rounded.ChevronRight,
32 | backgroundShape: Shape = CircleShape,
33 | sliderShape: Shape = CircleShape,
34 | onSlideComplete: () -> Unit = {}
35 | ) {
36 | SSComposeInfoBar(
37 | actionText = actionText,
38 | onActionDoneText = onActionDoneText,
39 | sliderIcon = sliderIcon,
40 | modifier = modifier,
41 | backgroundShape = backgroundShape,
42 | sliderShape = sliderShape,
43 | onSlideComplete = onSlideComplete,
44 | contentPadding = PaddingValues(SSComposeInfoBarDefaults.contentPadding.calculateTopPadding())
45 | )
46 | }
--------------------------------------------------------------------------------
/sscustominfobar/src/main/java/com/simform/sscustominfobar/defaultInfoBars/SuccessInfoBar.kt:
--------------------------------------------------------------------------------
1 | package com.simform.sscustominfobar.defaultInfoBars
2 |
3 | import androidx.compose.material3.LocalTextStyle
4 | import androidx.compose.runtime.Composable
5 | import androidx.compose.ui.Modifier
6 | import androidx.compose.ui.graphics.Shape
7 | import androidx.compose.ui.text.TextStyle
8 | import com.simform.sscustominfobar.main.SSComposeInfoBar
9 | import com.simform.sscustominfobar.main.SSComposeInfoBarColors
10 | import com.simform.sscustominfobar.main.SSComposeInfoBarData
11 | import com.simform.sscustominfobar.main.SSComposeInfoBarDefaults
12 | import com.simform.sscustominfobar.main.toSSCustomBackground
13 | import com.simform.sscustominfobar.res.successGreen
14 | import com.simform.sscustominfobar.res.white
15 |
16 | private val successBackgroundColor = successGreen
17 | private val successContentColor = white
18 |
19 | /**
20 | * Custom [SSComposeInfoBar] that is created to represent an success themed SSComposeInfoBar.
21 | *
22 | * @param modifier The modifier which will be applied to the [SuccessInfoBar].
23 | * @param successData The [SSComposeInfoBarData] that contains the title and description of the success.
24 | * @param textStyle The [TextStyle] to be applied to all the text in [SSComposeInfoBar].
25 | * @param shape The [Shape] of the [SuccessInfoBar].
26 | * @param onCloseClicked Called when user clicks on the close icon in [SSComposeInfoBar].
27 | * @param isInfinite The flag that represents whether the duration of the [SSComposeInfoBar] is Infinite or not.
28 | */
29 | @Composable
30 | fun SuccessInfoBar(
31 | modifier: Modifier = Modifier,
32 | successData: SSComposeInfoBarData,
33 | textStyle: TextStyle = LocalTextStyle.current,
34 | shape: Shape = SSComposeInfoBarDefaults.shape,
35 | onCloseClicked: () -> Unit = {},
36 | isInfinite: Boolean = false
37 | ) {
38 | SSComposeInfoBar(
39 | modifier = modifier,
40 | title = successData.title,
41 | titleStyle = textStyle,
42 | description = successData.description,
43 | shape = shape,
44 | icon = successData.icon,
45 | customBackground = successBackgroundColor.toSSCustomBackground(),
46 | contentColors = SSComposeInfoBarColors(
47 | iconColor = successContentColor,
48 | titleColor = successContentColor,
49 | descriptionColor = successContentColor,
50 | dismissIconColor = successContentColor
51 | ),
52 | onCloseClicked = onCloseClicked,
53 | isInfinite = isInfinite
54 | )
55 | }
--------------------------------------------------------------------------------
/sscustominfobar/src/main/java/com/simform/sscustominfobar/defaultInfoBars/WarningInfoBar.kt:
--------------------------------------------------------------------------------
1 | package com.simform.sscustominfobar.defaultInfoBars
2 |
3 | import androidx.compose.material3.LocalTextStyle
4 | import androidx.compose.runtime.Composable
5 | import androidx.compose.ui.Modifier
6 | import androidx.compose.ui.graphics.Shape
7 | import androidx.compose.ui.text.TextStyle
8 | import com.simform.sscustominfobar.main.SSComposeInfoBar
9 | import com.simform.sscustominfobar.main.SSComposeInfoBarColors
10 | import com.simform.sscustominfobar.main.SSComposeInfoBarData
11 | import com.simform.sscustominfobar.main.SSComposeInfoBarDefaults
12 | import com.simform.sscustominfobar.main.toSSCustomBackground
13 | import com.simform.sscustominfobar.res.warningOrange
14 | import com.simform.sscustominfobar.res.white
15 |
16 | private val warningBackgroundColor = warningOrange
17 | private val warningContentColor = white
18 |
19 | /**
20 | * Custom [SSComposeInfoBar] that is created to represent an error themed SSComposeInfoBar.
21 | *
22 | * @param modifier The modifier which will be applied to the [WarningInfoBar].
23 | * @param warningData The [SSComposeInfoBarData] that contains the title and description of the warning.
24 | * @param textStyle The [TextStyle] to be applied to all the text in [SSComposeInfoBar].
25 | * @param shape The [Shape] of the [WarningInfoBar].
26 | * @param onCloseClicked Called when user clicks on the close icon in [SSComposeInfoBar].
27 | * @param isInfinite The flag that represents whether the duration of the [SSComposeInfoBar] is Infinite or not.
28 | */
29 | @Composable
30 | fun WarningInfoBar(
31 | modifier: Modifier = Modifier,
32 | warningData: SSComposeInfoBarData,
33 | textStyle: TextStyle = LocalTextStyle.current,
34 | shape: Shape = SSComposeInfoBarDefaults.shape,
35 | onCloseClicked: () -> Unit = {},
36 | isInfinite: Boolean = false
37 | ) {
38 | SSComposeInfoBar(
39 | modifier = modifier,
40 | title = warningData.title,
41 | titleStyle = textStyle,
42 | description = warningData.description,
43 | shape = shape,
44 | icon = warningData.icon,
45 | customBackground = warningBackgroundColor.toSSCustomBackground(),
46 | contentColors = SSComposeInfoBarColors(
47 | iconColor = warningContentColor,
48 | titleColor = warningContentColor,
49 | descriptionColor = warningContentColor,
50 | dismissIconColor = warningContentColor
51 | ),
52 | onCloseClicked = onCloseClicked,
53 | isInfinite = isInfinite
54 | )
55 | }
--------------------------------------------------------------------------------
/sscustominfobar/src/main/java/com/simform/sscustominfobar/main/SSComposeInfoBar.kt:
--------------------------------------------------------------------------------
1 | package com.simform.sscustominfobar.main
2 |
3 | import androidx.compose.animation.AnimatedVisibility
4 | import androidx.compose.animation.core.tween
5 | import androidx.compose.animation.fadeIn
6 | import androidx.compose.animation.fadeOut
7 | import androidx.compose.animation.scaleOut
8 | import androidx.compose.foundation.BorderStroke
9 | import androidx.compose.foundation.ExperimentalFoundationApi
10 | import androidx.compose.foundation.background
11 | import androidx.compose.foundation.gestures.AnchoredDraggableState
12 | import androidx.compose.foundation.gestures.DraggableAnchors
13 | import androidx.compose.foundation.gestures.Orientation
14 | import androidx.compose.foundation.gestures.anchoredDraggable
15 | import androidx.compose.foundation.layout.Arrangement
16 | import androidx.compose.foundation.layout.Box
17 | import androidx.compose.foundation.layout.Column
18 | import androidx.compose.foundation.layout.PaddingValues
19 | import androidx.compose.foundation.layout.Row
20 | import androidx.compose.foundation.layout.calculateEndPadding
21 | import androidx.compose.foundation.layout.fillMaxWidth
22 | import androidx.compose.foundation.layout.height
23 | import androidx.compose.foundation.layout.offset
24 | import androidx.compose.foundation.layout.padding
25 | import androidx.compose.foundation.layout.size
26 | import androidx.compose.foundation.layout.width
27 | import androidx.compose.material.icons.Icons
28 | import androidx.compose.material.icons.filled.Clear
29 | import androidx.compose.material.icons.filled.Info
30 | import androidx.compose.material.icons.rounded.ChevronRight
31 | import androidx.compose.material3.ButtonColors
32 | import androidx.compose.material3.ButtonDefaults
33 | import androidx.compose.material3.ElevatedButton
34 | import androidx.compose.material3.Icon
35 | import androidx.compose.material3.IconButton
36 | import androidx.compose.material3.MaterialTheme
37 | import androidx.compose.material3.Surface
38 | import androidx.compose.material3.Text
39 | import androidx.compose.runtime.Composable
40 | import androidx.compose.runtime.LaunchedEffect
41 | import androidx.compose.runtime.getValue
42 | import androidx.compose.runtime.mutableStateOf
43 | import androidx.compose.runtime.remember
44 | import androidx.compose.runtime.setValue
45 | import androidx.compose.ui.Alignment
46 | import androidx.compose.ui.Modifier
47 | import androidx.compose.ui.draw.clip
48 | import androidx.compose.ui.draw.paint
49 | import androidx.compose.ui.graphics.Color
50 | import androidx.compose.ui.graphics.Shape
51 | import androidx.compose.ui.graphics.vector.ImageVector
52 | import androidx.compose.ui.layout.ContentScale
53 | import androidx.compose.ui.platform.LocalConfiguration
54 | import androidx.compose.ui.platform.LocalDensity
55 | import androidx.compose.ui.platform.LocalLayoutDirection
56 | import androidx.compose.ui.text.TextStyle
57 | import androidx.compose.ui.text.style.TextOverflow
58 | import androidx.compose.ui.unit.Dp
59 | import androidx.compose.ui.unit.IntOffset
60 | import androidx.compose.ui.unit.dp
61 | import com.simform.sscustominfobar.main.components.CustomText
62 | import com.simform.sscustominfobar.res.Dimens.DpFortyEight
63 | import com.simform.sscustominfobar.res.Dimens.DpMedium
64 | import com.simform.sscustominfobar.res.Dimens.DpSixtyFour
65 | import com.simform.sscustominfobar.res.Dimens.DpSmall
66 | import com.simform.sscustominfobar.res.Dimens.DpThirtyFour
67 | import com.simform.sscustominfobar.res.Dimens.SpMedium
68 | import com.simform.sscustominfobar.res.SliderColor
69 | import com.simform.sscustominfobar.utils.TextType
70 | import kotlinx.coroutines.delay
71 | import kotlin.math.roundToInt
72 |
73 | /**
74 | * Wrapper data class for elevations used in [SSComposeInfoBar].
75 | *
76 | * @property tonalElevation
77 | * @property shadowElevation
78 | */
79 | data class SSComposeInfoBarElevation(
80 | val tonalElevation: Dp,
81 | val shadowElevation: Dp
82 | )
83 |
84 | /**
85 | * Wrapper data class for colors used in [SSComposeInfoBar].
86 | *
87 | * @property iconColor
88 | * @property titleColor
89 | * @property descriptionColor
90 | */
91 | data class SSComposeInfoBarColors(
92 | val iconColor: Color,
93 | val titleColor: Color,
94 | val descriptionColor: Color,
95 | val dismissIconColor: Color,
96 | val actionButtonColors: ButtonColors? = null
97 | )
98 |
99 | /**
100 | * Enum class for [SSComposeInfoBar] drag anchors to be used for Slide to Perform Action.
101 | */
102 | enum class DragAnchors {
103 | Start,
104 | End,
105 | }
106 |
107 | /**
108 | * Compose Info Bar with an icon, title, and a description.
109 | *
110 | * @param modifier The [modifier] to be applied to the [SSComposeInfoBar].
111 | * @param title The text to be shown in the [SSComposeInfoBar] as title.
112 | * @param titleStyle The [TextStyle] that will be applied to the title of the [SSComposeInfoBar].
113 | * @param description The text to be shown in the [SSComposeInfoBar] as Description.
114 | * @param descriptionStyle The [TextStyle] that will be applied to the description of the [SSComposeInfoBar].
115 | * @param icon The [ImageVector] that will be displayed along with the title and description.
116 | * @param shape The [Shape] that will be applied to the [SSComposeInfoBar].
117 | * @param elevations The [SSComposeInfoBarElevation] that represents the tonal and shadow elevation used in [SSComposeInfoBar].
118 | * @param contentColors The [SSComposeInfoBarColors] that represents the container and content color used in [SSComposeInfoBar].
119 | * @param contentPadding The [PaddingValues] that will be used to give padding to the contents of the [SSComposeInfoBar].
120 | * @param height The Height of the [SSComposeInfoBar].
121 | * @param actionText The text of the Action in the [SSComposeInfoBar].
122 | * @param onActionClicked Called when the user clicks the action button.
123 | * @param onCloseClicked Called when the user clicks the clear button on [SSComposeInfoBar].
124 | * @param isInfinite The flag that denotes whether the [SSComposeInfoBar]'s Display Duration is infinite or not, based on this flag the clear button will be shown and hidden.
125 | */
126 | @Composable
127 | fun SSComposeInfoBar(
128 | modifier: Modifier = Modifier,
129 | title: TextType,
130 | titleStyle: TextStyle = SSComposeInfoBarDefaults.defaultTitleStyle,
131 | description: TextType? = null,
132 | descriptionStyle: TextStyle = SSComposeInfoBarDefaults.defaultDescriptionStyle,
133 | customBackground: SSCustomBackground = SSComposeInfoBarDefaults.defaultSSCustomBackground,
134 | icon: ImageVector = Icons.Default.Info,
135 | shape: Shape = SSComposeInfoBarDefaults.shape,
136 | elevations: SSComposeInfoBarElevation = SSComposeInfoBarDefaults.elevations,
137 | contentColors: SSComposeInfoBarColors = SSComposeInfoBarDefaults.colors,
138 | contentPadding: PaddingValues = SSComposeInfoBarDefaults.contentPadding,
139 | height: Dp = SSComposeInfoBarDefaults.defaultHeight,
140 | actionText: String = SSComposeInfoBarDefaults.defaultActionTitle,
141 | onActionClicked: (() -> Unit)? = null,
142 | onCloseClicked: () -> Unit = {},
143 | isInfinite: Boolean = false
144 | ) {
145 | Surface(
146 | modifier = modifier
147 | .then(
148 | // NOTE: here this default modifier is added inside of then because we want to override any width and height related modifier given from outside
149 | Modifier
150 | .fillMaxWidth()
151 | .height(height)
152 | ),
153 | shape = shape,
154 | color = if (customBackground is SSCustomBackground.SolidColor) {
155 | customBackground.color
156 | } else {
157 | MaterialTheme.colorScheme.surface
158 | },
159 | tonalElevation = elevations.tonalElevation,
160 | shadowElevation = elevations.shadowElevation
161 | ) {
162 | Row(
163 | modifier = when (customBackground) {
164 | is SSCustomBackground.DrawableBackground -> {
165 | Modifier
166 | .paint(
167 | painter = customBackground.image,
168 | contentScale = ContentScale.Crop
169 | )
170 | .fillMaxWidth()
171 | .padding(contentPadding)
172 | }
173 |
174 | is SSCustomBackground.GradientBrush -> {
175 | Modifier
176 | .background(customBackground.gradientBrush)
177 | .fillMaxWidth()
178 | .padding(contentPadding)
179 | }
180 |
181 | else -> {
182 | Modifier
183 | .fillMaxWidth()
184 | .padding(contentPadding)
185 | }
186 | },
187 | horizontalArrangement = Arrangement.SpaceBetween,
188 | verticalAlignment = Alignment.CenterVertically
189 | ) {
190 | Icon(
191 | imageVector = icon,
192 | contentDescription = null,
193 | modifier = Modifier.size(DpThirtyFour),
194 | tint = contentColors.iconColor
195 | )
196 | Column(
197 | modifier = Modifier
198 | .padding(horizontal = DpMedium)
199 | .weight(1f),
200 | verticalArrangement = Arrangement.Center
201 | ) {
202 | CustomText(
203 | text = title,
204 | maxLines = SSComposeInfoBarDefaults.titleMaxLine,
205 | overflow = TextOverflow.Ellipsis,
206 | style = titleStyle,
207 | color = contentColors.titleColor
208 | )
209 | if (description != null) {
210 | CustomText(
211 | text = description,
212 | style = descriptionStyle,
213 | maxLines = SSComposeInfoBarDefaults.descriptionMaxLine,
214 | overflow = TextOverflow.Ellipsis,
215 | color = contentColors.descriptionColor
216 | )
217 | }
218 | }
219 | if (onActionClicked != null) {
220 | ElevatedButton(
221 | onClick = onActionClicked,
222 | colors = contentColors.actionButtonColors
223 | ?: ButtonDefaults.elevatedButtonColors()
224 | ) {
225 | Text(text = actionText)
226 | }
227 | }
228 | if (isInfinite) {
229 | IconButton(onClick = onCloseClicked) {
230 | Icon(
231 | imageVector = Icons.Default.Clear,
232 | contentDescription = null,
233 | tint = contentColors.dismissIconColor
234 | )
235 | }
236 | }
237 | }
238 | }
239 | }
240 |
241 | /**
242 | * Slide to perform action defaults
243 | *
244 | * Contains all the default values for slide to perform action info bar's properties.
245 | */
246 | internal object SlideToPerformActionDefaults {
247 | val defaultSliderIcon = Icons.Rounded.ChevronRight
248 | val defaultActionTextStyle
249 | @Composable get() = MaterialTheme.typography.titleMedium.copy(
250 | fontSize = SpMedium
251 | )
252 | val defaultDoneTitleTextStyle
253 | @Composable get() = MaterialTheme.typography.titleMedium.copy(
254 | fontSize = SpMedium,
255 | color = Color.White
256 | )
257 | val defaultBackground @Composable get() = MaterialTheme.colorScheme.primary.toSSCustomBackground()
258 | val defaultBackgroundShape @Composable get() = MaterialTheme.shapes.medium
259 | val defaultSliderShape @Composable get() = MaterialTheme.shapes.medium
260 | val defaultElevations = SSComposeInfoBarDefaults.elevations
261 | val defaultContentPadding = PaddingValues(DpSmall)
262 | val defaultSliderIconColor @Composable get() = SliderColor
263 | val defaultSliderBackgroundColor = Color.White
264 | val defaultLoadingBackgroundColor @Composable get() = SliderColor
265 |
266 | val defaultInfoBarHeight = DpSixtyFour
267 | val defaultMarginTop = DpSmall
268 | val defaultSliderSize = DpFortyEight
269 | val defaultSlideIconSize = DpFortyEight
270 | val defaultVelocityThreshold = 1000
271 | val defaultDelayBeforeDismiss = 1200L
272 | val defaultDelayBeforeSliderDisappers = 100
273 | val defaultBorderStroke = BorderStroke(1.dp, Color.White)
274 | val defaultActionTitleMaxLines = 1
275 | val defaultDoneTitleMaxLines = 1
276 | }
277 |
278 | /**
279 | * Slide to perform action [SSComposeInfoBar]
280 | *
281 | * @param actionText The text of the Action in the [SSComposeInfoBar].
282 | * @param onActionDoneText The text to be shown in the [SSComposeInfoBar] when action is performed.
283 | * @param modifier The [modifier] to be applied to the [SSComposeInfoBar].
284 | * @param sliderIcon The [ImageVector] that will be displayed inside of slider.
285 | * @param actionTextStyle The [TextStyle] that will be applied to the action text.
286 | * @param customBackground The [SSCustomBackground] that will be applied to the [SSComposeInfoBar].
287 | * @param borderStroke The [BorderStroke] that will be applied to the [SSComposeInfoBar].
288 | * @param backgroundShape The [Shape] that will be applied to the background of the [SSComposeInfoBar].
289 | * @param sliderShape The [Shape] that will be applied to the background of the slider.
290 | * @param elevations The [SSComposeInfoBarElevation] that represents the tonal and shadow elevation used in [SSComposeInfoBar].
291 | * @param contentPadding The [PaddingValues] that will be used to give padding to the contents of the [SSComposeInfoBar].
292 | * @param sliderIconTintColor The [Color] that will be applied to the slider icon.
293 | * @param sliderBackgroundColor The [Color] that will be applied to the background of the slider.
294 | * @param loadingBackgroundColor The [Color] that will be applied to the background of the loading indicator.
295 | * @param infoBarHeight The Height of the [SSComposeInfoBar].
296 | * @param verticalMargin The vertical margin of the [SSComposeInfoBar].
297 | * @param sliderSize The size of the slider.
298 | * @param sliderIconSize The size of the slider icon.
299 | * @param onSlideComplete Called when the user completes the slide to perform action.
300 | */
301 | @OptIn(ExperimentalFoundationApi::class)
302 | @Composable
303 | fun SSComposeInfoBar(
304 | actionText: TextType,
305 | onActionDoneText: TextType,
306 | modifier: Modifier = Modifier,
307 | sliderIcon: ImageVector = SlideToPerformActionDefaults.defaultSliderIcon,
308 | actionTextStyle: TextStyle = SlideToPerformActionDefaults.defaultActionTextStyle,
309 | customBackground: SSCustomBackground = SlideToPerformActionDefaults.defaultBackground,
310 | borderStroke: BorderStroke = SlideToPerformActionDefaults.defaultBorderStroke,
311 | backgroundShape: Shape = SlideToPerformActionDefaults.defaultBackgroundShape,
312 | sliderShape: Shape = SlideToPerformActionDefaults.defaultSliderShape,
313 | elevations: SSComposeInfoBarElevation = SlideToPerformActionDefaults.defaultElevations,
314 | contentPadding: PaddingValues = SlideToPerformActionDefaults.defaultContentPadding,
315 | sliderIconTintColor: Color = SlideToPerformActionDefaults.defaultSliderIconColor,
316 | sliderBackgroundColor: Color = SlideToPerformActionDefaults.defaultSliderBackgroundColor,
317 | loadingBackgroundColor: Color = SlideToPerformActionDefaults.defaultLoadingBackgroundColor,
318 | infoBarHeight: Dp = SlideToPerformActionDefaults.defaultInfoBarHeight,
319 | verticalMargin: Dp = SlideToPerformActionDefaults.defaultMarginTop,
320 | sliderSize: Dp = SlideToPerformActionDefaults.defaultSliderSize,
321 | sliderIconSize: Dp = SlideToPerformActionDefaults.defaultSlideIconSize,
322 | onSlideComplete: () -> Unit = {}
323 | ) {
324 | val density = LocalDensity.current
325 | val slideWidth = LocalConfiguration.current.screenWidthDp.dp - sliderSize -
326 | contentPadding.calculateEndPadding(LocalLayoutDirection.current) -
327 | contentPadding.calculateEndPadding(LocalLayoutDirection.current)
328 | val widthInPx = with(density) {
329 | slideWidth.toPx()
330 | }
331 | val state = remember {
332 | AnchoredDraggableState(
333 | initialValue = DragAnchors.Start,
334 | positionalThreshold = { distance: Float -> distance / 2 },
335 | velocityThreshold = { with(density) { SlideToPerformActionDefaults.defaultVelocityThreshold.dp.toPx() } },
336 | animationSpec = tween()
337 | ).apply {
338 | updateAnchors(
339 | DraggableAnchors {
340 | DragAnchors.Start at 0f
341 | DragAnchors.End at widthInPx
342 | }
343 | )
344 | }
345 | }
346 |
347 | var isSlidingComplete by remember {
348 | mutableStateOf(false)
349 | }
350 |
351 | LaunchedEffect(key1 = state.currentValue) {
352 | if (state.currentValue == state.targetValue && state.currentValue == DragAnchors.End) {
353 | isSlidingComplete = true
354 | delay(SlideToPerformActionDefaults.defaultDelayBeforeDismiss)
355 | onSlideComplete()
356 | }
357 | }
358 |
359 | Surface(
360 | modifier = modifier
361 | .then(
362 | // NOTE: here this default modifier is added inside of then because we want to override any width and height related modifier given from outside
363 | Modifier
364 | .fillMaxWidth()
365 | .padding(vertical = verticalMargin)
366 | .height(infoBarHeight)
367 | ),
368 | shape = backgroundShape,
369 | color = if (customBackground is SSCustomBackground.SolidColor) {
370 | customBackground.color
371 | } else {
372 | MaterialTheme.colorScheme.surface
373 | },
374 | tonalElevation = elevations.tonalElevation,
375 | shadowElevation = elevations.shadowElevation,
376 | border = borderStroke
377 | ) {
378 | Box(
379 | modifier = when (customBackground) {
380 | is SSCustomBackground.DrawableBackground -> {
381 | Modifier
382 | .paint(
383 | painter = customBackground.image,
384 | contentScale = ContentScale.Crop
385 | )
386 | .fillMaxWidth()
387 | }
388 |
389 | is SSCustomBackground.GradientBrush -> {
390 | Modifier
391 | .background(customBackground.gradientBrush)
392 | .fillMaxWidth()
393 | }
394 |
395 | else -> {
396 | Modifier
397 | .fillMaxWidth()
398 | }
399 | }
400 | ) {
401 | CustomText(
402 | text = actionText,
403 | modifier = Modifier
404 | .align(Alignment.Center),
405 | style = actionTextStyle,
406 | maxLines = SlideToPerformActionDefaults.defaultActionTitleMaxLines
407 | )
408 | LoadingBackground(
409 | modifier = Modifier
410 | .height(infoBarHeight)
411 | .width(with(density) {
412 | // Here height is also used as initial width since we want a circle.
413 | infoBarHeight + state
414 | .requireOffset()
415 | .toDp()
416 | })
417 | .clip(backgroundShape)
418 | .background(loadingBackgroundColor)
419 | )
420 | AnimatedVisibility(!isSlidingComplete, exit = scaleOut() + fadeOut()) {
421 | SwipeIndicator(
422 | modifier = Modifier
423 | .padding(contentPadding)
424 | .size(sliderSize)
425 | .offset {
426 | IntOffset(
427 | x = state
428 | .requireOffset()
429 | .roundToInt(),
430 | y = 0
431 | )
432 | }
433 | .anchoredDraggable(state, Orientation.Horizontal)
434 | .clip(sliderShape),
435 | sliderIcon = sliderIcon,
436 | sliderIconSize = sliderIconSize,
437 | sliderIconTintColor = sliderIconTintColor,
438 | backgroundColor = sliderBackgroundColor
439 | )
440 | }
441 | AnimatedVisibility(
442 | visible = isSlidingComplete,
443 | modifier = Modifier.align(Alignment.Center),
444 | enter = fadeIn(tween(delayMillis = SlideToPerformActionDefaults.defaultDelayBeforeSliderDisappers))
445 | ) {
446 | CustomText(
447 | text = onActionDoneText,
448 | modifier = Modifier
449 | .align(Alignment.Center),
450 | style = SlideToPerformActionDefaults.defaultDoneTitleTextStyle,
451 | maxLines = SlideToPerformActionDefaults.defaultDoneTitleMaxLines
452 | )
453 | }
454 | }
455 | }
456 | }
457 |
458 | @Composable
459 | internal fun SwipeIndicator(
460 | modifier: Modifier = Modifier,
461 | sliderIcon: ImageVector,
462 | sliderIconSize: Dp,
463 | sliderIconTintColor: Color,
464 | backgroundColor: Color
465 | ) {
466 | Box(
467 | contentAlignment = Alignment.Center,
468 | modifier = modifier.background(backgroundColor)
469 | ) {
470 | Icon(
471 | imageVector = sliderIcon,
472 | contentDescription = null,
473 | tint = sliderIconTintColor,
474 | modifier = Modifier.size(sliderIconSize)
475 | )
476 | }
477 | }
478 |
479 | @Composable
480 | fun LoadingBackground(
481 | modifier: Modifier = Modifier,
482 | ) {
483 | Box(
484 | contentAlignment = Alignment.Center,
485 | modifier = modifier
486 | ) {}
487 | }
--------------------------------------------------------------------------------
/sscustominfobar/src/main/java/com/simform/sscustominfobar/main/SSComposeInfoHost.kt:
--------------------------------------------------------------------------------
1 | package com.simform.sscustominfobar.main
2 |
3 | import androidx.compose.animation.AnimatedVisibility
4 | import androidx.compose.animation.core.MutableTransitionState
5 | import androidx.compose.foundation.layout.Box
6 | import androidx.compose.foundation.layout.PaddingValues
7 | import androidx.compose.foundation.lazy.LazyListState
8 | import androidx.compose.foundation.shape.RoundedCornerShape
9 | import androidx.compose.material.icons.Icons
10 | import androidx.compose.material.icons.filled.Info
11 | import androidx.compose.material3.ButtonDefaults
12 | import androidx.compose.material3.LocalTextStyle
13 | import androidx.compose.material3.MaterialTheme
14 | import androidx.compose.runtime.Composable
15 | import androidx.compose.runtime.LaunchedEffect
16 | import androidx.compose.runtime.MutableState
17 | import androidx.compose.runtime.State
18 | import androidx.compose.runtime.derivedStateOf
19 | import androidx.compose.runtime.getValue
20 | import androidx.compose.runtime.mutableStateOf
21 | import androidx.compose.runtime.remember
22 | import androidx.compose.ui.Alignment
23 | import androidx.compose.ui.Modifier
24 | import androidx.compose.ui.graphics.Shape
25 | import androidx.compose.ui.graphics.graphicsLayer
26 | import androidx.compose.ui.graphics.vector.ImageVector
27 | import androidx.compose.ui.platform.LocalContext
28 | import androidx.compose.ui.res.stringResource
29 | import androidx.compose.ui.text.font.FontWeight
30 | import androidx.lifecycle.compose.collectAsStateWithLifecycle
31 | import com.simform.sscustominfobar.R
32 | import com.simform.sscustominfobar.animation.AnimationType
33 | import com.simform.sscustominfobar.animation.DefaultAnimationDuration
34 | import com.simform.sscustominfobar.animation.ExtraDelayForNewInfoBar
35 | import com.simform.sscustominfobar.animation.getAnimatedOffset
36 | import com.simform.sscustominfobar.animation.getEnterAnimation
37 | import com.simform.sscustominfobar.animation.getExitAnimation
38 | import com.simform.sscustominfobar.defaultInfoBars.OfflineInfoBar
39 | import com.simform.sscustominfobar.main.SSComposeInfoBarDirection.Bottom
40 | import com.simform.sscustominfobar.main.SSComposeInfoBarDirection.Top
41 | import com.simform.sscustominfobar.main.SSComposeInfoBarShapes.roundedBottom
42 | import com.simform.sscustominfobar.main.SSComposeInfoBarShapes.roundedTop
43 | import com.simform.sscustominfobar.main.SSComposeInfoBarState.Hidden
44 | import com.simform.sscustominfobar.main.SSComposeInfoBarState.Visible
45 | import com.simform.sscustominfobar.main.SSComposeInfoDuration.Indefinite
46 | import com.simform.sscustominfobar.main.SSComposeInfoDuration.Long
47 | import com.simform.sscustominfobar.main.SSComposeInfoDuration.Short
48 | import com.simform.sscustominfobar.res.Dimens
49 | import com.simform.sscustominfobar.res.Dimens.DpEighty
50 | import com.simform.sscustominfobar.res.Dimens.DpMedium
51 | import com.simform.sscustominfobar.res.Dimens.DpSmall
52 | import com.simform.sscustominfobar.res.Dimens.DpTwelve
53 | import com.simform.sscustominfobar.res.Dimens.DpZero
54 | import com.simform.sscustominfobar.utils.ConnectivityObserver
55 | import com.simform.sscustominfobar.utils.DirectionalLazyListState
56 | import com.simform.sscustominfobar.utils.SCROLL_THRESHOLD
57 | import com.simform.sscustominfobar.utils.ScrollDirection
58 | import com.simform.sscustominfobar.utils.TextType
59 | import com.simform.sscustominfobar.utils.getShapeByDirection
60 | import com.simform.sscustominfobar.utils.rememberDirectionalLazyListState
61 | import com.simform.sscustominfobar.utils.swipeable
62 | import com.simform.sscustominfobar.utils.toMillis
63 | import com.simform.sscustominfobar.utils.toTextType
64 | import kotlinx.coroutines.delay
65 | import java.util.LinkedList
66 | import java.util.Queue
67 |
68 | /**
69 | * Max num of lines for description and title in [SSComposeInfoBar]
70 | */
71 | private const val DESC_MAX_LINE = 2
72 | private const val TITLE_MAX_LINE = 2
73 |
74 | /**
75 | * Wrapper data class for information that will be displayed in [SSComposeInfoBar].
76 | *
77 | * @property title
78 | * @property description
79 | * @property icon
80 | */
81 | data class SSComposeInfoBarData(
82 | val title: TextType,
83 | val description: TextType? = null,
84 | val icon: ImageVector = Icons.Default.Info
85 | )
86 |
87 | /**
88 | * Enum class that represents the 3 types of duration of [SSComposeInfoBar]
89 | * @property Short 4 seconds
90 | * @property Long 10 seconds
91 | * @property Indefinite [SSComposeInfoBar] won't be dismissed until dismissed by user.
92 | */
93 | enum class SSComposeInfoDuration {
94 | Short,
95 | Long,
96 | Indefinite
97 | }
98 |
99 | /**
100 | * Enum class that represents the two states of [SSComposeInfoBar]
101 | * - Visible
102 | * - Hidden
103 | *
104 | * @property value Boolean value that is wrapped by this enum class.
105 | * (true for [Visible] and false for [Hidden])
106 | */
107 | enum class SSComposeInfoBarState(val value: Boolean) {
108 | Visible(true), Hidden(false)
109 | }
110 |
111 | /**
112 | * Enum class that represents the direction from which the [SSComposeInfoBar] will be presented.
113 | * @property Top
114 | * @property Bottom
115 | */
116 | enum class SSComposeInfoBarDirection(internal val alignment: Alignment) {
117 | Top(Alignment.TopCenter), Bottom(Alignment.BottomCenter)
118 | }
119 |
120 | /**
121 | * State of the [SSComposeInfoHost], which controls the current [SSComposeInfoBar] being shown
122 | * inside the [SSComposeInfoHost].
123 | *
124 | * This state is usually [remember]ed and used to provide to a [SSComposeInfoHost].
125 | */
126 | class SSComposeInfoHostState {
127 | internal var previousState = Hidden
128 | internal var onDismissCallback: (() -> Unit)? = null
129 | fun setOnInfoBarDismiss(callback: () -> Unit) {
130 | onDismissCallback = callback
131 | }
132 |
133 | // TODO: If everything goes fine add one parameter in the class's constructor for the queue
134 | // size and then use that here to create a fixed size linked list.
135 | private var infoBarDataQueue: Queue = LinkedList()
136 | private var isQueueBeingProcessed = false
137 |
138 | /**
139 | * [MutableTransitionState] which is used internally by [SSComposeInfoHost] to show and hide [SSComposeInfoBar]
140 | */
141 | internal var visibilityState = MutableTransitionState(Hidden.value)
142 |
143 | private var _direction = mutableStateOf(Top)
144 | val direction: State = _direction
145 |
146 | /**
147 | * Function that is used in [SSComposeInfoHost] internally to set the direction given by the user.
148 | */
149 | internal fun setDirection(direction: SSComposeInfoBarDirection) {
150 | this._direction.value = direction
151 | }
152 |
153 | /**
154 | * Private backing property for isInfinite.
155 | */
156 | internal var _isInfinite = mutableStateOf(false)
157 |
158 | /**
159 | * A read only [State] property of type [Boolean] which is used to represent whether the current [SSComposeInfoBar]'s Duration is Infinite or not.
160 | */
161 | val isInfinite: State = _isInfinite
162 |
163 | /**
164 | * A read only property that represents whether a [SSComposeInfoBar] is currently being shown or not.
165 | */
166 | val isVisible: Boolean
167 | get() = visibilityState.currentState == Visible.value
168 |
169 | /**
170 | * Private backing property for [currentComposeInfoBarData].
171 | */
172 | private var _currentComposeInfoBarData: MutableState =
173 | mutableStateOf(null)
174 |
175 | /**
176 | * A read only property that represents the data(title, description and icon) that should be displayed in the [SSComposeInfoBar].
177 | */
178 | val currentComposeInfoBarData: State = _currentComposeInfoBarData
179 |
180 | /**
181 | * Private backing property for offline [currentComposeInfoBarData].
182 | */
183 | private var _offlineInfoBarData: MutableState = mutableStateOf(null)
184 |
185 | /**
186 | * A read only property that represents the data(title, description and icon)
187 | * that should be displayed in the offline [SSComposeInfoBar].
188 | */
189 | val offlineInfoBarData: State = _offlineInfoBarData
190 |
191 | fun setOfflineInfoBarData(offlineInfoBarData: SSComposeInfoBarData) {
192 | _offlineInfoBarData.value = offlineInfoBarData
193 | }
194 |
195 | /**
196 | * Function that is used to manually hide the currently displayed [SSComposeInfoBar].
197 | */
198 | fun dismiss() {
199 | visibilityState.targetState = Hidden.value
200 | previousState = Visible
201 | }
202 |
203 | /**
204 | * Scroll threshold to use for scroll to hide feature.
205 | */
206 | private var scrollThreshold = SCROLL_THRESHOLD
207 |
208 | /**
209 | * setter function for scrollThreshold.
210 | *
211 | * @param threshold
212 | */
213 | fun setScrollThreshold(threshold: Int) {
214 | scrollThreshold = threshold
215 | directionalLazyListState?.updateScrollThreshold(scrollThreshold)
216 | }
217 |
218 | // For Scroll-to-hide feature
219 | internal var directionalLazyListState: DirectionalLazyListState? = null
220 |
221 | // Initialize the directionalLazyListState using contentScrollState
222 | @Composable
223 | internal fun InitializeDirectionalLazyListState(contentScrollState: LazyListState?) {
224 | if (contentScrollState != null && directionalLazyListState == null) {
225 | directionalLazyListState =
226 | rememberDirectionalLazyListState(
227 | lazyListState = contentScrollState,
228 | scrollThreshold = scrollThreshold
229 | )
230 | }
231 | }
232 |
233 | /**
234 | * This suspend function is used to show [SSComposeInfoBar] in [SSComposeInfoHost].
235 | *
236 | * @param infoBarData [SSComposeInfoBarData] which will be used to display content(title, description) in [SSComposeInfoBar].
237 | * @param duration [SSComposeInfoDuration] which will determine how long the [SSComposeInfoBar] stays visible.
238 | */
239 | suspend fun show(
240 | infoBarData: SSComposeInfoBarData,
241 | duration: SSComposeInfoDuration
242 | ) {
243 | if (duration == Indefinite) {
244 | if (!isVisible) {
245 | _currentComposeInfoBarData.value = infoBarData
246 | _isInfinite.value = true
247 | visibilityState.targetState = Visible.value
248 | previousState = Hidden
249 | }
250 | } else {
251 | if (!isInfinite.value) {
252 | infoBarDataQueue.add(infoBarData)
253 | if (!isQueueBeingProcessed) {
254 | isQueueBeingProcessed = true
255 | while (infoBarDataQueue.isNotEmpty()) {
256 | _currentComposeInfoBarData.value = infoBarDataQueue.remove()
257 | visibilityState.targetState = Visible.value
258 | // Wait for the given duration
259 | delay(duration.toMillis())
260 | visibilityState.targetState = Hidden.value
261 | previousState = Visible
262 | // Here we are using the default value of exit animation but when we will give custom animations we will have to use that duration.
263 | // Store that exit animation in the SSComposeHostState.
264 | delay(DefaultAnimationDuration.toLong() + ExtraDelayForNewInfoBar)
265 | }
266 | isQueueBeingProcessed = false
267 | }
268 | }
269 | }
270 | }
271 | }
272 |
273 | /**
274 | * Contains the default values used by [SSComposeInfoBar].
275 | */
276 | object SSComposeInfoBarDefaults {
277 | /**
278 | * Default horizontalPadding and verticalPadding.
279 | */
280 | private val ComposeInfoBarHorizontalPadding = DpMedium
281 | private val ComposeInfoBarVerticalPadding = DpSmall
282 |
283 | /**
284 | * Default action title
285 | */
286 | val defaultActionTitle = "Action"
287 |
288 | /**
289 | * Default content padding for [SSComposeInfoBar].
290 | */
291 | val contentPadding = PaddingValues(
292 | start = ComposeInfoBarHorizontalPadding,
293 | end = ComposeInfoBarHorizontalPadding,
294 | top = ComposeInfoBarVerticalPadding,
295 | bottom = ComposeInfoBarVerticalPadding
296 | )
297 |
298 | /**
299 | * Default height of [SSComposeInfoBar].
300 | */
301 | internal val defaultHeight = DpEighty
302 |
303 | /**
304 | * Default Max lines for description and title in [SSComposeInfoBar].
305 | */
306 | internal const val descriptionMaxLine = DESC_MAX_LINE
307 | internal const val titleMaxLine = TITLE_MAX_LINE
308 |
309 | /**
310 | * Default [Shape] of [SSComposeInfoBar].
311 | */
312 | val shape: Shape = roundedBottom
313 |
314 | /**
315 | * Creates default [SSComposeInfoBarColors] for [SSComposeInfoBar].
316 | */
317 | val colors: SSComposeInfoBarColors
318 | @Composable get() = SSComposeInfoBarColors(
319 | iconColor = MaterialTheme.colorScheme.onPrimary,
320 | titleColor = MaterialTheme.colorScheme.onPrimary,
321 | descriptionColor = MaterialTheme.colorScheme.onPrimary,
322 | dismissIconColor = MaterialTheme.colorScheme.onPrimary,
323 | actionButtonColors = ButtonDefaults.elevatedButtonColors()
324 | )
325 |
326 | /**
327 | * Default [SSCustomBackground] for [SSComposeInfoBar].
328 | */
329 | val defaultSSCustomBackground @Composable get() = MaterialTheme.colorScheme.primary.toSSCustomBackground()
330 |
331 | /**
332 | * Creates default [SSComposeInfoBarElevation] for [SSComposeInfoBar]
333 | */
334 | val elevations =
335 | SSComposeInfoBarElevation(tonalElevation = DpSmall, shadowElevation = DpSmall)
336 |
337 | /**
338 | * Default title style of [SSComposeInfoBar].
339 | */
340 | val defaultTitleStyle
341 | @Composable get() = LocalTextStyle.current.copy(
342 | fontWeight = FontWeight.SemiBold,
343 | fontSize = Dimens.SpEighteen
344 | )
345 |
346 | /**
347 | * Default description style of [SSComposeInfoBar].
348 | */
349 | val defaultDescriptionStyle
350 | @Composable get() = LocalTextStyle.current.copy(
351 | fontWeight = FontWeight.Light,
352 | fontSize = Dimens.SpFourteen
353 | )
354 | }
355 |
356 | /**
357 | * Object that contains the shapes for [SSComposeInfoBar] depending upon the [SSComposeInfoBarDirection].
358 | *
359 | * @property roundedBottom Used when the [SSComposeInfoBarDirection] is [SSComposeInfoBarDirection.Top]
360 | * @property roundedTop Used when the [SSComposeInfoBarDirection] is [SSComposeInfoBarDirection.Bottom]
361 | */
362 | object SSComposeInfoBarShapes {
363 | val roundedBottom =
364 | RoundedCornerShape(
365 | topStart = DpZero,
366 | topEnd = DpZero,
367 | bottomStart = DpTwelve,
368 | bottomEnd = DpTwelve
369 | )
370 | val roundedTop =
371 | RoundedCornerShape(
372 | topStart = DpTwelve,
373 | topEnd = DpTwelve,
374 | bottomStart = DpZero,
375 | bottomEnd = DpZero
376 | )
377 | }
378 |
379 | /**
380 | * Host for [SSComposeInfoBar]s to properly show, hide and dismiss items base on [SSComposeInfoHostState].
381 | *
382 | * @param modifier The Modifier to be applied to [SSComposeInfoHost].
383 | * @param composeHostState The state of the current [SSComposeInfoHost].
384 | * @param direction The direction from which the [SSComposeInfoBar] will be shown.
385 | * @param animationType The Animation with the [SSComposeInfoBar] will be shown.
386 | * @param contentScrollState The LazyListState which wil be used to show and hide the [SSComposeInfoBar] on scrolling.
387 | * @param enableNetworkMonitoring The flag that will decide whether the network monitoring feature is enabled or not.
388 | * @param composeInfoBar The [SSComposeInfoBar] that will be displayed in [SSComposeInfoHost].
389 | * @param content content of the screen on which the [SSComposeInfoBar] will be shown.
390 | */
391 | @Composable
392 | fun SSComposeInfoHost(
393 | modifier: Modifier = Modifier,
394 | composeHostState: SSComposeInfoHostState,
395 | direction: SSComposeInfoBarDirection = Top,
396 | animationType: AnimationType = AnimationType.SlideVertically,
397 | contentScrollState: LazyListState? = null,
398 | enableNetworkMonitoring: Boolean = false,
399 | isSwipeToDismissEnabled: Boolean = false,
400 | composeInfoBar: @Composable (SSComposeInfoBarData) -> Unit,
401 | content: @Composable () -> Unit
402 | ) {
403 | composeHostState.setDirection(direction)
404 | composeHostState.InitializeDirectionalLazyListState(contentScrollState = contentScrollState)
405 | val exitAnimation = getExitAnimation(composeHostState.direction.value, animationType)
406 | val enterAnimation = getEnterAnimation(composeHostState.direction.value, animationType)
407 |
408 | // For dismiss callback
409 | LaunchedEffect(key1 = composeHostState.isVisible) {
410 | // Here we are checking whether the info bar was first visible and then it went into dismissed state
411 | // This will help in when we don't want the callback to be called initially when the infoBar is not visible and the launched is called initially.
412 | if (composeHostState.previousState == Visible && !composeHostState.isVisible) {
413 | composeHostState.onDismissCallback?.let { it() }
414 | if (composeHostState.isInfinite.value) {
415 | composeHostState._isInfinite.value = false
416 | }
417 | }
418 | }
419 |
420 | // For network monitoring
421 | val context = LocalContext.current
422 | val monitor = remember { ConnectivityObserver(context) }
423 | var isOnline: State? = null
424 | if (enableNetworkMonitoring) {
425 | isOnline = monitor.isOnline.collectAsStateWithLifecycle(initialValue = true)
426 | }
427 | val shouldBeVisible by remember(composeHostState.isVisible) {
428 | derivedStateOf {
429 | if (composeHostState.isVisible) {
430 | // The scroll to hide behaviour should only be allowed when a SSInfoBar is currently being shown.
431 | (composeHostState.directionalLazyListState?.scrollDirection == ScrollDirection.SettledAtTop
432 | || composeHostState.directionalLazyListState?.scrollDirection == ScrollDirection.SettleAfterUpScroll)
433 | } else true
434 | }
435 | }
436 | val animatedYOffset by getAnimatedOffset(
437 | shouldBeVisible = shouldBeVisible,
438 | direction = direction
439 | )
440 | Box(
441 | modifier = modifier
442 | ) {
443 | content()
444 | AnimatedVisibility(
445 | visibleState = composeHostState.visibilityState,
446 | modifier = Modifier
447 | .align(composeHostState.direction.value.alignment)
448 | .then(
449 | if (isSwipeToDismissEnabled && composeHostState.isInfinite.value) Modifier.swipeable { composeHostState.dismiss() } else Modifier
450 | )
451 | .then(
452 | if (composeHostState.directionalLazyListState != null) Modifier
453 | .graphicsLayer {
454 | translationY = animatedYOffset
455 | } else Modifier
456 | ),
457 | enter = enterAnimation,
458 | exit = exitAnimation
459 | ) {
460 | composeHostState.currentComposeInfoBarData.value?.let { content ->
461 | composeInfoBar(content)
462 | }
463 | }
464 | if (enableNetworkMonitoring) {
465 | isOnline?.value?.let {
466 | AnimatedVisibility(
467 | visible = !it,
468 | modifier = Modifier
469 | .align(composeHostState.direction.value.alignment),
470 | enter = enterAnimation,
471 | exit = exitAnimation
472 | ) {
473 | val offlineInfoBarData = composeHostState.offlineInfoBarData.value
474 | if (offlineInfoBarData != null) {
475 | OfflineInfoBar(offlineData = offlineInfoBarData)
476 | } else {
477 | OfflineInfoBar(
478 | offlineData = SSComposeInfoBarData(
479 | title = stringResource(R.string.offline_info_bar_title).toTextType(),
480 | description = stringResource(R.string.offline_info_bar_description).toTextType()
481 | )
482 | )
483 | }
484 | }
485 | }
486 | }
487 | }
488 | }
489 |
490 | /**
491 | * Host for [SSComposeInfoBar]s to properly show, hide and dismiss items base on [SSComposeInfoHostState].
492 | *
493 | * Note: This SSComposeInfoHost does not provide a way to show custom [SSComposeInfoBar]. To provide custom [SSComposeInfoBar] checkout other SSComposeInfoHost overloads.
494 | *
495 | * @param modifier The Modifier to be applied to [SSComposeInfoHost].
496 | * @param composeHostState The state of the current [SSComposeInfoHost].
497 | * @param direction The direction from which the [SSComposeInfoBar] will be shown.
498 | * @param animationType The Animation with the [SSComposeInfoBar] will be shown.
499 | * @param contentScrollState The LazyListState which wil be used to show and hide the [SSComposeInfoBar] on scrolling.
500 | * @param enableNetworkMonitoring The flag that will decide whether the network monitoring feature is enabled or not.
501 | * @param content content of the screen on which the [SSComposeInfoBar] will be shown.
502 | */
503 | @Composable
504 | fun SSComposeInfoHost(
505 | modifier: Modifier = Modifier,
506 | composeHostState: SSComposeInfoHostState,
507 | direction: SSComposeInfoBarDirection = Top,
508 | animationType: AnimationType = AnimationType.SlideVertically,
509 | contentScrollState: LazyListState? = null,
510 | enableNetworkMonitoring: Boolean = false,
511 | isSwipeToDismissEnabled: Boolean = false,
512 | content: @Composable () -> Unit
513 | ) {
514 | composeHostState.setDirection(direction)
515 | composeHostState.InitializeDirectionalLazyListState(contentScrollState = contentScrollState)
516 | val exitAnimation = getExitAnimation(composeHostState.direction.value, animationType)
517 | val enterAnimation = getEnterAnimation(composeHostState.direction.value, animationType)
518 |
519 | // For dismiss callback
520 | LaunchedEffect(key1 = composeHostState.isVisible) {
521 | // Here we are checking whether the info bar was first visible and then it went into dismissed state
522 | // This will help in when we don't want the callback to be called initially when the infoBar is not visible and the launched is called initially.
523 | if (composeHostState.previousState == Visible && !composeHostState.isVisible) {
524 | composeHostState.onDismissCallback?.let { it() }
525 | if (composeHostState.isInfinite.value) {
526 | composeHostState._isInfinite.value = false
527 | }
528 | }
529 | }
530 |
531 | // For network monitoring
532 | val context = LocalContext.current
533 | val monitor = remember { ConnectivityObserver(context) }
534 | var isOnline: State? = null
535 | if (enableNetworkMonitoring) {
536 | isOnline = monitor.isOnline.collectAsStateWithLifecycle(initialValue = true)
537 | }
538 |
539 | val shouldBeVisible by remember(composeHostState.isVisible) {
540 | derivedStateOf {
541 | if (composeHostState.isVisible) {
542 | // The scroll to hide behaviour should only be allowed when a SSInfoBar is currently being shown.
543 | (composeHostState.directionalLazyListState?.scrollDirection == ScrollDirection.SettledAtTop
544 | || composeHostState.directionalLazyListState?.scrollDirection == ScrollDirection.SettleAfterUpScroll)
545 | } else true
546 | }
547 | }
548 | val animatedYOffset by getAnimatedOffset(
549 | shouldBeVisible = shouldBeVisible,
550 | direction = direction
551 | )
552 | Box(
553 | modifier = modifier
554 | ) {
555 | content()
556 | AnimatedVisibility(
557 | visibleState = composeHostState.visibilityState,
558 | modifier = Modifier
559 | .align(composeHostState.direction.value.alignment)
560 | .then(
561 | if (isSwipeToDismissEnabled && composeHostState.isInfinite.value) Modifier.swipeable { composeHostState.dismiss() } else Modifier
562 | )
563 | .then(
564 | if (composeHostState.directionalLazyListState != null) Modifier
565 | .graphicsLayer {
566 | translationY = animatedYOffset
567 | } else Modifier
568 | ),
569 | enter = enterAnimation,
570 | exit = exitAnimation
571 | ) {
572 | composeHostState.currentComposeInfoBarData.value?.let { infoBarData ->
573 | SSComposeInfoBar(
574 | title = infoBarData.title,
575 | description = infoBarData.description,
576 | shape = getShapeByDirection(composeHostState.direction.value),
577 | isInfinite = composeHostState.isInfinite.value,
578 | onCloseClicked = {
579 | composeHostState.dismiss()
580 | }
581 | )
582 | }
583 | }
584 | if (enableNetworkMonitoring) {
585 | isOnline?.value?.let {
586 | AnimatedVisibility(
587 | visible = !it,
588 | modifier = Modifier
589 | .align(composeHostState.direction.value.alignment),
590 | enter = enterAnimation,
591 | exit = exitAnimation
592 | ) {
593 | val offlineInfoBarData = composeHostState.offlineInfoBarData.value
594 | if (offlineInfoBarData != null) {
595 | OfflineInfoBar(offlineData = offlineInfoBarData)
596 | } else {
597 | OfflineInfoBar(
598 | offlineData = SSComposeInfoBarData(
599 | title = stringResource(R.string.offline_info_bar_title).toTextType(),
600 | description = stringResource(R.string.offline_info_bar_description).toTextType()
601 | )
602 | )
603 | }
604 | }
605 | }
606 | }
607 | }
608 | }
--------------------------------------------------------------------------------
/sscustominfobar/src/main/java/com/simform/sscustominfobar/main/SSInfoBarBackground.kt:
--------------------------------------------------------------------------------
1 | package com.simform.sscustominfobar.main
2 |
3 | import androidx.compose.ui.graphics.Brush
4 | import androidx.compose.ui.graphics.Color
5 | import androidx.compose.ui.graphics.painter.Painter
6 |
7 | /**
8 | * Sealed Interface of supported background types in [SSComposeInfoBar].
9 | */
10 | sealed interface SSCustomBackground {
11 | class SolidColor(val color: Color) : SSCustomBackground
12 |
13 | class GradientBrush(val gradientBrush: Brush) : SSCustomBackground
14 |
15 | class DrawableBackground(val image: Painter) : SSCustomBackground
16 | }
17 |
18 | /**
19 | * Extension method to convert [Color] to [SSCustomBackground].
20 | */
21 | fun Color.toSSCustomBackground() = SSCustomBackground.SolidColor(this)
22 |
23 | /**
24 | * Extension method to convert [Brush] to [SSCustomBackground].
25 | */
26 | fun Brush.toSSCustomBackground() = SSCustomBackground.GradientBrush(this)
27 |
28 | /**
29 | * Extension method to convert [Painter] to [SSCustomBackground].
30 | */
31 | fun Painter.toSSCustomBackground() = SSCustomBackground.DrawableBackground(this)
--------------------------------------------------------------------------------
/sscustominfobar/src/main/java/com/simform/sscustominfobar/main/components/CustomText.kt:
--------------------------------------------------------------------------------
1 | package com.simform.sscustominfobar.main.components
2 |
3 | import androidx.compose.material3.LocalTextStyle
4 | import androidx.compose.material3.Text
5 | import androidx.compose.runtime.Composable
6 | import androidx.compose.ui.Modifier
7 | import androidx.compose.ui.graphics.Color
8 | import androidx.compose.ui.text.TextStyle
9 | import androidx.compose.ui.text.style.TextAlign
10 | import androidx.compose.ui.text.style.TextOverflow
11 | import com.simform.sscustominfobar.main.SSComposeInfoBar
12 | import com.simform.sscustominfobar.utils.TextType
13 |
14 | /**
15 | * CustomText composable to use inside [SSComposeInfoBar] to show either normal string or an
16 | * annotated string.
17 | *
18 | * @param text of type [TextType].
19 | * @param modifier modifier which should be applied to CustomText.
20 | * @param style [TextStyle] for the text.
21 | * @param color [Color] of the text.
22 | * @param textAlign [TextAlign] of text.
23 | * @param overflow TextOverFlow.
24 | * @param maxLines maxLines for the text to display.
25 | */
26 | @Composable
27 | internal fun CustomText(
28 | text: TextType,
29 | modifier: Modifier = Modifier,
30 | style: TextStyle = LocalTextStyle.current,
31 | color: Color = Color.Unspecified,
32 | textAlign: TextAlign? = null,
33 | overflow: TextOverflow = TextOverflow.Clip,
34 | maxLines: Int
35 | ) {
36 | when (text) {
37 | is TextType.AnnotatedStr -> {
38 | Text(
39 | text = text.annotatedStr,
40 | color = color,
41 | modifier = modifier,
42 | style = style,
43 | textAlign = textAlign,
44 | overflow = overflow,
45 | maxLines = maxLines
46 | )
47 | }
48 |
49 | is TextType.Str -> {
50 | Text(
51 | text = text.str,
52 | color = color,
53 | modifier = modifier,
54 | style = style,
55 | textAlign = textAlign,
56 | overflow = overflow,
57 | maxLines = maxLines
58 | )
59 | }
60 | }
61 | }
--------------------------------------------------------------------------------
/sscustominfobar/src/main/java/com/simform/sscustominfobar/res/Colors.kt:
--------------------------------------------------------------------------------
1 | package com.simform.sscustominfobar.res
2 |
3 | import androidx.compose.ui.graphics.Color
4 |
5 | internal val errorRed = Color(0xFFC72C41)
6 | internal val warningOrange = Color(0xFFCC561E)
7 | internal val successGreen = Color(0xFF0C7040)
8 | internal val white = Color.White
9 | val SliderColor = Color(0xFF5A2A2A)
--------------------------------------------------------------------------------
/sscustominfobar/src/main/java/com/simform/sscustominfobar/res/Dimens.kt:
--------------------------------------------------------------------------------
1 | package com.simform.sscustominfobar.res
2 |
3 | import androidx.compose.ui.unit.dp
4 | import androidx.compose.ui.unit.sp
5 |
6 | internal object Dimens {
7 | // Dp Dimensions
8 | val DpZero = 0.dp
9 | val DpExtraSmall = 4.dp
10 | val DpSmall = 8.dp
11 | val DpMedium = 16.dp
12 | val DpLarge = 20.dp
13 | val DpExtraLarge = 24.dp
14 |
15 | val DpTwentyEight = 28.dp
16 | val DpTwelve = 12.dp
17 | val DpEighty = 80.dp
18 | val DpFortyEight = 48.dp
19 | val DpThirtyFour = 34.dp
20 | val DpSixtyFour = 64.dp
21 | val SpFourteen = 14.sp
22 | val SpEighteen = 18.sp
23 |
24 | // Sp Dimensions
25 | val SpExtraSmall = 4.sp
26 | val SpSmall = 8.sp
27 | val SpExtraMedium = 12.sp
28 | val SpMedium = 16.sp
29 | val SpLarge = 20.sp
30 | val SpExtraLarge = 24.sp
31 | }
--------------------------------------------------------------------------------
/sscustominfobar/src/main/java/com/simform/sscustominfobar/utils/ConnectivityObserver.kt:
--------------------------------------------------------------------------------
1 | package com.simform.sscustominfobar.utils
2 |
3 | import android.content.Context
4 | import android.net.ConnectivityManager
5 | import android.net.Network
6 | import android.net.NetworkCapabilities
7 | import android.net.NetworkRequest
8 | import android.os.Build
9 | import androidx.core.content.getSystemService
10 | import kotlinx.coroutines.channels.awaitClose
11 | import kotlinx.coroutines.flow.Flow
12 | import kotlinx.coroutines.flow.callbackFlow
13 | import kotlinx.coroutines.flow.conflate
14 | import kotlinx.coroutines.flow.distinctUntilChanged
15 |
16 | internal interface NetworkObserver {
17 | val isOnline: Flow
18 | }
19 |
20 | /**
21 | * Connectivity observer class that can be used to observe the internet status.
22 | *
23 | * @constructor
24 | * [Context]
25 | */
26 | class ConnectivityObserver(context: Context) : NetworkObserver {
27 |
28 | override val isOnline: Flow = callbackFlow {
29 | val connectivityManager = context.getSystemService()
30 | if (connectivityManager == null) {
31 | channel.trySend(false)
32 | channel.close()
33 | return@callbackFlow
34 | }
35 |
36 | // Callback for network changes
37 | val callback = object : ConnectivityManager.NetworkCallback() {
38 | private val networks = mutableSetOf()
39 |
40 | override fun onAvailable(network: Network) {
41 | networks += network
42 | channel.trySend(true)
43 | }
44 |
45 | override fun onLost(network: Network) {
46 | networks -= network
47 | channel.trySend(networks.isNotEmpty())
48 | }
49 | }
50 |
51 | val request =
52 | NetworkRequest.Builder()
53 | .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
54 | .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
55 | .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
56 | .build()
57 |
58 | connectivityManager.registerNetworkCallback(request, callback)
59 |
60 | // Initially checks the internet when the app is launched and this class is created.
61 | channel.trySend(connectivityManager.isInternetAvailable())
62 |
63 | // When the flow is removed from the composition it unregisters the network callback.
64 | awaitClose {
65 | connectivityManager.unregisterNetworkCallback(callback)
66 | }
67 | }.distinctUntilChanged().conflate()
68 | }
69 |
70 | private fun ConnectivityManager.isInternetAvailable(): Boolean {
71 | var result = false
72 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
73 | getNetworkCapabilities(activeNetwork)?.run {
74 | result = when {
75 | hasTransport(NetworkCapabilities.TRANSPORT_WIFI) -> true
76 | hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) -> true
77 | hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET) -> true
78 | else -> false
79 | }
80 | }
81 | } else {
82 | activeNetworkInfo?.run {
83 | if (type == ConnectivityManager.TYPE_WIFI) {
84 | result = true
85 | } else if (type == ConnectivityManager.TYPE_MOBILE) {
86 | result = true
87 | }
88 | }
89 | }
90 | return result
91 | }
--------------------------------------------------------------------------------
/sscustominfobar/src/main/java/com/simform/sscustominfobar/utils/DirectionalLazyListState.kt:
--------------------------------------------------------------------------------
1 | package com.simform.sscustominfobar.utils
2 |
3 | import androidx.compose.foundation.lazy.LazyListState
4 | import androidx.compose.runtime.Composable
5 | import androidx.compose.runtime.derivedStateOf
6 | import androidx.compose.runtime.getValue
7 | import androidx.compose.runtime.remember
8 | import kotlin.math.absoluteValue
9 |
10 | /**
11 | * Scroll Threshold To reduce the show/hide behaviour when user scroll very little.
12 | */
13 | internal const val SCROLL_THRESHOLD = 50
14 |
15 | /**
16 | * Enum class for direction of Scroll.
17 | */
18 | internal enum class ScrollDirection {
19 | SettledAtTop, SettleAfterUpScroll, SettledAfterDownScroll,
20 | }
21 |
22 | /**
23 | * Creates a [DirectionalLazyListState] that is remembered across compositions.
24 | *
25 | * @param lazyListState [LazyListState] that will be used internally in [DirectionalLazyListState].
26 | * @return [DirectionalLazyListState]
27 | */
28 | @Composable
29 | internal fun rememberDirectionalLazyListState(
30 | lazyListState: LazyListState,
31 | scrollThreshold: Int = SCROLL_THRESHOLD
32 | ): DirectionalLazyListState {
33 | return remember {
34 | DirectionalLazyListState(lazyListState, scrollThreshold)
35 | }
36 | }
37 |
38 | /**
39 | * Class that extends the functionality of [LazyListState] by providing an extra observable variable scrollDirection
40 | * of type [ScrollDirection].
41 | *
42 | * @property lazyListState of type [LazyListState].
43 | */
44 | internal class DirectionalLazyListState(
45 | private val lazyListState: LazyListState,
46 | private var scrollThreshold: Int = SCROLL_THRESHOLD
47 | ) {
48 | private var positionY = lazyListState.firstVisibleItemScrollOffset
49 | private var visibleItem = lazyListState.firstVisibleItemIndex
50 | private var scrollPosition = ScrollDirection.SettledAtTop
51 |
52 | internal fun updateScrollThreshold(threshold: Int) {
53 | scrollThreshold = threshold
54 | }
55 |
56 | val scrollDirection by derivedStateOf {
57 | if (lazyListState.isScrollInProgress.not()) {
58 | scrollPosition
59 | } else {
60 | val firstVisibleItemIndex = lazyListState.firstVisibleItemIndex
61 | val firstVisibleItemScrollOffset =
62 | lazyListState.firstVisibleItemScrollOffset
63 |
64 | // We are scrolling while first visible item hasn't changed yet
65 | if (firstVisibleItemIndex == visibleItem) {
66 | val direction = if (firstVisibleItemScrollOffset > positionY) {
67 | // Only update the value when the scroll has passed the threshold value.
68 | if ((firstVisibleItemScrollOffset - positionY).absoluteValue > scrollThreshold) {
69 | // User scrolls downward
70 | scrollPosition =
71 | ScrollDirection.SettledAfterDownScroll
72 | positionY = firstVisibleItemScrollOffset
73 | }
74 | scrollPosition
75 | } else {
76 | // User scrolls upward
77 | if ((firstVisibleItemScrollOffset - positionY).absoluteValue > scrollThreshold) {
78 | scrollPosition =
79 | ScrollDirection.SettleAfterUpScroll
80 | positionY = firstVisibleItemScrollOffset
81 | }
82 | scrollPosition
83 | }
84 | direction
85 | } else {
86 | // We are scrolling and the very first item is out of the screen.
87 | val direction = if (firstVisibleItemIndex > visibleItem) {
88 | scrollPosition =
89 | ScrollDirection.SettledAfterDownScroll
90 | ScrollDirection.SettledAfterDownScroll
91 | } else {
92 | scrollPosition =
93 | ScrollDirection.SettleAfterUpScroll
94 | ScrollDirection.SettleAfterUpScroll
95 | }
96 | positionY = firstVisibleItemScrollOffset
97 | visibleItem = firstVisibleItemIndex
98 | direction
99 | }
100 | }
101 | }
102 | }
--------------------------------------------------------------------------------
/sscustominfobar/src/main/java/com/simform/sscustominfobar/utils/LibUtils.kt:
--------------------------------------------------------------------------------
1 | package com.simform.sscustominfobar.utils
2 |
3 | import com.simform.sscustominfobar.main.SSComposeInfoBar
4 | import com.simform.sscustominfobar.main.SSComposeInfoBarDirection
5 | import com.simform.sscustominfobar.main.SSComposeInfoBarShapes
6 | import com.simform.sscustominfobar.main.SSComposeInfoDuration
7 |
8 | /**
9 | * Duration in milliseconds
10 | */
11 | private const val DURATION_LONG = 10000L
12 | private const val DURATION_SHORT = 4000L
13 |
14 | /**
15 | * Internal function to create shape for [SSComposeInfoBar] based on the [SSComposeInfoBarDirection] provided.
16 | *
17 | * @param direction The [SSComposeInfoBarDirection] type provided by user.
18 | */
19 | internal fun getShapeByDirection(direction: SSComposeInfoBarDirection) = when (direction) {
20 | SSComposeInfoBarDirection.Top -> SSComposeInfoBarShapes.roundedBottom
21 | SSComposeInfoBarDirection.Bottom -> SSComposeInfoBarShapes.roundedTop
22 | }
23 |
24 | /**
25 | * Internal util function to get Duration of [SSComposeInfoBar] based on [SSComposeInfoDuration].
26 | *
27 | * @return Durations in milli Seconds.
28 | */
29 | internal fun SSComposeInfoDuration.toMillis(): Long {
30 | val original = when (this) {
31 | SSComposeInfoDuration.Indefinite -> Long.MAX_VALUE
32 | SSComposeInfoDuration.Long -> DURATION_LONG
33 | SSComposeInfoDuration.Short -> DURATION_SHORT
34 | }
35 | return original
36 | }
--------------------------------------------------------------------------------
/sscustominfobar/src/main/java/com/simform/sscustominfobar/utils/SwipeToDismissModifier.kt:
--------------------------------------------------------------------------------
1 | package com.simform.sscustominfobar.utils
2 |
3 |
4 | import androidx.compose.animation.core.Animatable
5 | import androidx.compose.animation.core.calculateTargetValue
6 | import androidx.compose.animation.splineBasedDecay
7 | import androidx.compose.foundation.gestures.awaitFirstDown
8 | import androidx.compose.foundation.gestures.horizontalDrag
9 | import androidx.compose.foundation.layout.offset
10 | import androidx.compose.runtime.remember
11 | import androidx.compose.ui.Modifier
12 | import androidx.compose.ui.composed
13 | import androidx.compose.ui.geometry.Offset
14 | import androidx.compose.ui.input.pointer.pointerInput
15 | import androidx.compose.ui.input.pointer.positionChange
16 | import androidx.compose.ui.input.pointer.util.VelocityTracker
17 | import androidx.compose.ui.unit.IntOffset
18 | import kotlinx.coroutines.coroutineScope
19 | import kotlinx.coroutines.launch
20 | import kotlin.math.absoluteValue
21 | import kotlin.math.roundToInt
22 |
23 | /**
24 | * Custom modifier to enable swipe to dismiss
25 | *
26 | * @param onDismissed The callback to be called when the item has been successfully dismissed.
27 | * @return Modifier
28 | */
29 | fun Modifier.swipeable(
30 | onDismissed: () -> Unit
31 | ): Modifier = composed {
32 | val offsetX = remember { Animatable(0f) }
33 | // Create a modifier for processing pointer input within the region of the modified element.
34 | pointerInput(Unit) {
35 | val decay = splineBasedDecay(this)
36 | coroutineScope {
37 | // Create an infinite loop in a coroutine scope to detect any touch activity.
38 | while (true) {
39 | val pointerId =
40 | awaitPointerEventScope { awaitFirstDown().id } // Observe for a touch event and if a touch is detected take its id.
41 | offsetX.stop()
42 | val velocityTracker = VelocityTracker()
43 | awaitPointerEventScope { // Then observe for a drag gesture for that specific touch id.
44 | horizontalDrag(pointerId) { change ->
45 | val horizontalDragOffset = offsetX.value + change.positionChange().x
46 | launch {
47 | offsetX.snapTo(horizontalDragOffset) // Update the offsetX with horizontalDragOffset without any animation and also cancelling any on-going animation
48 | }
49 | velocityTracker.addPosition(change.uptimeMillis, change.position) // keep track of each position when dragging with timestamp
50 | if (change.positionChange() != Offset.Zero) change.consume()
51 | }
52 | }
53 | val velocity = velocityTracker.calculateVelocity().x
54 | // calculate target offset based on the velocity to show fling behaviour
55 | val targetOffsetX = decay.calculateTargetValue(offsetX.value, velocity)
56 | offsetX.updateBounds(
57 | lowerBound = -size.width.toFloat(),
58 | upperBound = size.width.toFloat()
59 | )
60 | launch {
61 | // If the target offset after fling behaviour is less than the items width then animate back to normal position
62 | if (targetOffsetX.absoluteValue <= size.width) {
63 | offsetX.animateTo(targetValue = 0f, initialVelocity = velocity)
64 | } else {
65 | // Else animate to the target offset and after the animation call the onDismissed
66 | offsetX.animateDecay(velocity, decay)
67 | onDismissed()
68 | }
69 | }
70 | }
71 | }
72 | }
73 | // Apply the horizontal offset to the element.
74 | .offset { IntOffset(offsetX.value.roundToInt(), 0) }
75 | }
--------------------------------------------------------------------------------
/sscustominfobar/src/main/java/com/simform/sscustominfobar/utils/TextType.kt:
--------------------------------------------------------------------------------
1 | package com.simform.sscustominfobar.utils
2 |
3 | import androidx.compose.ui.text.AnnotatedString
4 |
5 | /**
6 | * Wrapper class around all ways to show text in Text composable.
7 | * e.g [String] and [AnnotatedString]
8 | */
9 | sealed interface TextType {
10 | class Str(val str: String) : TextType
11 |
12 | class AnnotatedStr(val annotatedStr: AnnotatedString) : TextType
13 | }
14 |
15 | fun String.toTextType() = TextType.Str(this)
16 | fun AnnotatedString.toTextType() = TextType.AnnotatedStr(this)
--------------------------------------------------------------------------------
/sscustominfobar/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | ComposeInfoBar YOffSet Animation
4 | Scroll to hide animation
5 | Oops!!, Seems like you are offline!
6 | Kindly check your network connection
7 |
--------------------------------------------------------------------------------