{"id":48341510,"url":"https://github.com/ivamsi/snapnotify","last_synced_at":"2026-04-05T05:03:14.501Z","repository":{"id":313113732,"uuid":"1050039525","full_name":"iVamsi/SnapNotify","owner":"iVamsi","description":"🚀 A lightweight, thread-safe Snackbar library for Jetpack Compose with zero-ceremony setup. Show snackbars from anywhere with beautiful theming, queue management, and optional Hilt integration. 100% Kotlin.","archived":false,"fork":false,"pushed_at":"2025-09-22T01:13:55.000Z","size":1238,"stargazers_count":22,"open_issues_count":0,"forks_count":1,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-09-22T03:12:06.771Z","etag":null,"topics":["android","android-library","androidx","compose-library","compose-ui","jetpack-compose","kotlin","lightweight","material-design","material3","snackbar","thread-safe","toast","ui-library"],"latest_commit_sha":null,"homepage":"https://levelup.gitconnected.com/snapnotify-how-i-simplified-jetpack-compose-snackbars-from-15-lines-to-one-line-fdf93fe7d9a5","language":"Kotlin","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/iVamsi.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-09-03T21:24:46.000Z","updated_at":"2025-09-22T01:13:58.000Z","dependencies_parsed_at":"2025-09-22T03:09:30.118Z","dependency_job_id":"e3cf6143-10aa-4a05-ad9b-2d1e92703968","html_url":"https://github.com/iVamsi/SnapNotify","commit_stats":null,"previous_names":["ivamsi/snapnotify"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/iVamsi/SnapNotify","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/iVamsi%2FSnapNotify","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/iVamsi%2FSnapNotify/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/iVamsi%2FSnapNotify/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/iVamsi%2FSnapNotify/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/iVamsi","download_url":"https://codeload.github.com/iVamsi/SnapNotify/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/iVamsi%2FSnapNotify/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31424932,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-05T02:22:46.605Z","status":"ssl_error","status_checked_at":"2026-04-05T02:22:33.263Z","response_time":75,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["android","android-library","androidx","compose-library","compose-ui","jetpack-compose","kotlin","lightweight","material-design","material3","snackbar","thread-safe","toast","ui-library"],"created_at":"2026-04-05T05:02:58.646Z","updated_at":"2026-04-05T05:03:14.491Z","avatar_url":"https://github.com/iVamsi.png","language":"Kotlin","funding_links":[],"categories":[],"sub_categories":[],"readme":"# SnapNotify\n\n[![Android Weekly](https://androidweekly.net/issues/issue-692/badge)](https://androidweekly.net/issues/issue-692)\n[![Kotlin](https://img.shields.io/badge/Kotlin-2.3+-purple.svg)](https://kotlinlang.org)\n[![Compose](https://img.shields.io/badge/Compose-Jetpack%20BOM%202026-blue.svg)](https://developer.android.com/develop/ui/compose/bom)\n[![Android](https://img.shields.io/badge/Android-API%2024+-green.svg)](https://android-arsenal.com/api?level=24)\n[![License](https://img.shields.io/badge/License-Apache%202.0-orange.svg)](https://opensource.org/licenses/Apache-2.0)\n[![Maven Central](https://img.shields.io/badge/Maven%20Central-1.0.6-red.svg)](https://central.sonatype.com/artifact/io.github.ivamsi/snapnotify/1.0.6)\n[![Tests](https://img.shields.io/badge/Tests-74%2B%20passing-brightgreen.svg)](#-testing)\n[![Coverage](https://img.shields.io/badge/Coverage-100%25%20Public%20API-brightgreen.svg)](#-testing)\n\n\u003e A drop-in Snackbar solution for Jetpack Compose that brings back the simplicity of the View system while leveraging modern Compose patterns.\n\n## 🎬 Demo\n\n\u003cdiv align=\"center\"\u003e\n\n![SnapNotify Demo](demo.gif)\n\n*SnapNotify in action: Simple messages, themed styling, custom designs, and queue management*\n\n\u003c/div\u003e\n\n## 🚀 The Problem\n\nJetpack Compose's built-in Snackbar system requires significant boilerplate:\n- Manual `SnackbarHostState` management\n- Passing `CoroutineScope` everywhere \n- Complex setup in every screen\n- Thread-safety concerns when calling from background threads\n- Cumbersome queue management for multiple messages\n\n```kotlin\n// Traditional Compose approach - lots of boilerplate 😔\n@Composable\nfun MyScreen() {\n    val snackbarHostState = remember { SnackbarHostState() }\n    val scope = rememberCoroutineScope()\n    \n    Scaffold(\n        snackbarHost = { SnackbarHost(snackbarHostState) }\n    ) {\n        // Your content\n        Button(\n            onClick = {\n                scope.launch {\n                    snackbarHostState.showSnackbar(\"Message\")\n                }\n            }\n        ) {\n            Text(\"Show Snackbar\")\n        }\n    }\n}\n```\n\n## ✨ The Solution\n\nSnapNotify eliminates all the boilerplate with a clean, thread-safe API:\n\n```kotlin\n// SnapNotify approach - zero ceremony! 🎉\n@Composable\nfun MyScreen() {\n    Button(\n        onClick = { SnapNotify.show(\"Message\") }\n    ) {\n        Text(\"Show Snackbar\")\n    }\n}\n```\n\n## 🛠 Installation\n\nAdd to your `build.gradle.kts`:\n\n```kotlin\ndependencies {\n    implementation(\"io.github.ivamsi:snapnotify:1.0.6\")\n}\n```\n\n**Hilt Integration (Optional):**\nSnapNotify works with or without Hilt! If you use Hilt, no additional setup needed.\n\n## Changelog\n\nRelease history is in [CHANGELOG.md](CHANGELOG.md).\n\n## Developing\n\nThis repository’s sample app depends on `project(\":snapnotify\")` so `./gradlew check` works on a clean clone without publishing. To validate the same artifact consumers get from Maven, run `./gradlew :snapnotify:publishToMavenLocal` and point a module to `io.github.ivamsi:snapnotify:1.0.6` with `mavenLocal()` in `dependencyResolutionManagement` (or your module repositories).\n\n## Releasing (maintainers)\n\n1. Bump the library version in `snapnotify/build.gradle.kts` (`mavenPublishing { coordinates(...) }`), demo `versionCode` / `versionName`, [CHANGELOG.md](CHANGELOG.md), and the installation/coordinates snippets in this README.\n2. Run `./gradlew check`.\n3. Commit, create an annotated tag `vX.Y.Z`, and push the branch and tag to GitHub.\n4. Publish to Maven Central: `./gradlew :snapnotify:publishAndReleaseToMavenCentral` with [Central Portal credentials and signing](https://vanniktech.github.io/gradle-maven-publish-plugin/central/) (see comments in `gradle.properties`).\n5. On GitHub, create a **Release** from that tag and attach release notes (can mirror the changelog section).\n\n## 🎯 Quick Start\n\n### 1. Setup (Flexible Usage)\n\nWrap your content with `SnapNotifyProvider` where you want snackbar functionality:\n\n**App-wide snackbars:**\n```kotlin\nclass MainActivity : ComponentActivity() {\n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n        setContent {\n            SnapNotifyProvider {\n                MyAppContent() // Entire app has snackbar access\n            }\n        }\n    }\n}\n```\n\n**Screen-specific snackbars:**\n```kotlin\n@Composable\nfun MyScreen() {\n    SnapNotifyProvider {\n        // Only this screen and its children can show snackbars\n        ScreenContent()\n    }\n}\n```\n\n**Feature-scoped snackbars:**\n```kotlin\n@Composable\nfun ShoppingCart() {\n    Card {\n        SnapNotifyProvider {\n            // Snackbars appear within this card's bounds\n            CartItems()\n            AddToCartButton()\n        }\n    }\n}\n```\n\n**Custom styled snackbars:**\n```kotlin\n@Composable\nfun MyScreen() {\n    SnapNotifyProvider(\n        style = SnackbarStyle.error() // Pre-built error styling\n    ) {\n        ScreenContent()\n    }\n}\n```\n\n### 2. Usage (Anywhere in your app)\n\n```kotlin\n// Simple message\nSnapNotify.show(\"Operation completed successfully!\")\n\n// With action button\nSnapNotify.show(\"Error occurred\", \"Retry\") { \n    retryOperation() \n}\n\n// With custom duration\nSnapNotify.show(\"Long message\", duration = SnackbarDuration.Long)\n\n// With custom duration in milliseconds\nSnapNotify.show(\"7 second message\", durationMillis = 7000)\n\n// Clear all pending messages\nSnapNotify.clearAll()\n\n// Themed styling methods\nSnapNotify.showSuccess(\"Operation completed!\")\nSnapNotify.showError(\"Something went wrong!\")\nSnapNotify.showWarning(\"Please check your input!\")\nSnapNotify.showInfo(\"Here's some information!\")\n\n// Themed methods with actions\nSnapNotify.showSuccess(\"File saved!\", \"View\") { openFile() }\nSnapNotify.showError(\"Upload failed\", \"Retry\") { retryUpload() }\n\n// Themed methods with custom duration\nSnapNotify.showError(\"Network timeout\", \"Retry\", { retry() }, durationMillis = 8000)\nSnapNotify.showSuccess(\"Quick success!\", durationMillis = 2000)\n\n// Custom styling for specific messages\nval customStyle = SnackbarStyle(\n    containerColor = Color(0xFF6A1B9A),\n    contentColor = Color.White,\n    actionColor = Color(0xFFE1BEE7)\n)\nSnapNotify.showStyled(\"Custom styled message!\", customStyle)\nSnapNotify.showStyled(\"10 second custom style\", customStyle, durationMillis = 10000)\n```\n\n## 🌟 Key Features\n\n### 🆕 Latest Updates (v1.0.6)\n- **Toolchain**: Android Gradle Plugin 9.1 / Gradle 9.3.1, Kotlin 2.3.20 with [built-in Kotlin in AGP 9](https://developer.android.com/build/migrate-to-built-in-kotlin), updated AndroidX / Compose BOM / Hilt\n- **Queue Configuration**: Configure max queue size and get notified when messages are dropped\n- **Enhanced Keyboard Handling**: Snackbars now automatically avoid IME (keyboard) and work with navigation bars\n- **Provider Flexibility**: New parameters for alignment, insets, and custom host rendering\n- **Architecture Improvements**: Non-blocking mutex, proper dispatcher management, optimized style reuse\n- **Custom Duration Support**: Precise millisecond timing control for all methods\n- **Expanded Test Coverage**: 74+ tests with 100% public API coverage\n\n### ✅ Flexible Setup\n- Use `SnapNotifyProvider` at any level of your app hierarchy\n- App-wide, screen-specific, or feature-scoped snackbars\n- No `SnackbarHostState` or `CoroutineScope` management needed\n\n### ✅ Thread-Safe\n- Call from ViewModels and Composables safely\n- Background thread support with proper synchronization\n- No more `IllegalStateException` crashes\n\n### ✅ Smart Queue Management  \n- Handles multiple rapid Snackbar triggers gracefully\n- No overlapping or lost messages\n- Singleton architecture prevents duplicate displays\n\n### ✅ Lifecycle-Aware\n- Survives configuration changes\n- Prevents memory leaks\n- Automatic provider deduplication\n\n### ✅ Action Support\n- Optional action buttons with callbacks\n- Seamless integration with your business logic\n\n### ✅ Comprehensive Styling\n- **Pre-built themes**: Success, Error, Warning, Info with semantic colors\n- **Themed methods**: `showSuccess()`, `showError()`, `showWarning()`, `showInfo()`\n- **Custom styling**: `showStyled()` for per-message customization\n- **Full customization**: Colors, shapes, elevation, typography\n- **Provider-level defaults**: Set default styles for entire sections\n- **Material3 integration**: Seamless with your app's design system\n\n## 📖 Detailed Usage\n\n### Basic Messages\n\n```kotlin\n// Simple text message\nSnapNotify.show(\"Profile updated successfully!\")\n\n// Different durations\nSnapNotify.show(\"Quick message\", duration = SnackbarDuration.Short)\nSnapNotify.show(\"Important info\", duration = SnackbarDuration.Long)\nSnapNotify.show(\"Persistent message\", duration = SnackbarDuration.Indefinite)\n\n// Custom durations in milliseconds\nSnapNotify.show(\"2 second message\", durationMillis = 2000)\nSnapNotify.show(\"30 second timeout\", durationMillis = 30000)\n```\n\n### Action Buttons\n\n```kotlin\n// With action callback\nSnapNotify.show(\"Message deleted\", \"Undo\") {\n    undoDelete()\n}\n\n// Error handling with retry\nSnapNotify.show(\"Network error\", \"Retry\") {\n    viewModel.retryNetworkCall()\n}\n\n// Clear pending messages when navigating or context changes\nSnapNotify.clearAll()\n```\n\n### ⏱️ Custom Duration Control\n\nPrecise timing control with millisecond accuracy:\n\n```kotlin\n// Quick notifications (1-3 seconds)\nSnapNotify.show(\"Quick toast\", durationMillis = 1500)\nSnapNotify.showSuccess(\"Saved!\", durationMillis = 2000)\n\n// Standard notifications (4-8 seconds)\nSnapNotify.show(\"Processing complete\", durationMillis = 5000)\nSnapNotify.showInfo(\"New feature available\", durationMillis = 6000)\n\n// Long notifications (10+ seconds)\nSnapNotify.showError(\"Network error - retrying in 15s\", \"Retry Now\", {\n    retryNetworkCall()\n}, durationMillis = 15000)\n\n// Custom styled with timing\nval urgentStyle = SnackbarStyle(\n    containerColor = Color.Red,\n    contentColor = Color.White\n)\nSnapNotify.showStyled(\"Critical alert!\", urgentStyle, durationMillis = 12000)\n\n// With action buttons - users can still interact before timeout\nSnapNotify.showWarning(\"Auto-save in 10 seconds\", \"Save Now\", {\n    saveManually()\n}, durationMillis = 10000)\n```\n\n**Key Benefits:**\n- **Precise Control**: Set exact durations from 1ms to several minutes\n- **User Priority**: Action buttons remain functional during custom timeouts\n- **All Methods**: Works with `show()`, `showSuccess()`, `showError()`, `showWarning()`, `showInfo()`, and `showStyled()`\n- **Automatic Cleanup**: Messages auto-dismiss after the specified time\n- **Thread-Safe**: Safe to call from background threads with custom durations\n\n### 🎨 Themed Messages\n\nQuick, semantic styling with pre-built themes:\n\n```kotlin\n// Success messages (green theme)\nSnapNotify.showSuccess(\"Profile updated successfully!\")\nSnapNotify.showSuccess(\"File uploaded!\", \"View\") { openFile() }\n\n// Error messages (red theme)\nSnapNotify.showError(\"Network connection failed!\")\nSnapNotify.showError(\"Save failed\", \"Retry\") { attemptSave() }\n\n// Warning messages (orange theme)\nSnapNotify.showWarning(\"Low storage space!\")\nSnapNotify.showWarning(\"Unsaved changes\", \"Save\") { saveChanges() }\n\n// Info messages (blue theme)\nSnapNotify.showInfo(\"New feature available!\")\nSnapNotify.showInfo(\"Update available\", \"Download\") { startUpdate() }\n```\n\n### 🎨 Custom Styling\n\n**Per-message custom styling:**\n```kotlin\nval customStyle = SnackbarStyle(\n    containerColor = Color(0xFF6A1B9A),\n    contentColor = Color.White,\n    actionColor = Color(0xFFE1BEE7),\n    shape = RoundedCornerShape(16.dp),\n    elevation = 12.dp\n)\n\nSnapNotify.showStyled(\"Custom styled message!\", customStyle)\nSnapNotify.showStyled(\"With action\", customStyle, \"Action\") { doAction() }\n```\n\n### Styling Options\n\n**Pre-built themes:**\n```kotlin\n// Success theme (green)\nSnapNotifyProvider(style = SnackbarStyle.success()) {\n    // Content\n}\n\n// Error theme (red)\nSnapNotifyProvider(style = SnackbarStyle.error()) {\n    // Content\n}\n\n// Warning theme (orange)\nSnapNotifyProvider(style = SnackbarStyle.warning()) {\n    // Content\n}\n\n// Info theme (blue)\nSnapNotifyProvider(style = SnackbarStyle.info()) {\n    // Content\n}\n```\n\n**Custom styling:**\n```kotlin\nSnapNotifyProvider(\n    style = SnackbarStyle(\n        containerColor = Color(0xFF9C27B0),\n        contentColor = Color.White,\n        actionColor = Color(0xFFE1BEE7),\n        shape = RoundedCornerShape(16.dp),\n        elevation = 12.dp,\n        messageTextStyle = MaterialTheme.typography.bodyLarge.copy(\n            fontWeight = FontWeight.Medium\n        ),\n        actionTextStyle = MaterialTheme.typography.labelLarge.copy(\n            fontWeight = FontWeight.Bold\n        )\n    )\n) {\n    // Content with custom-styled snackbars\n}\n```\n\n### Clean Architecture Usage\n\nFollowing best practices, handle UI notifications in the presentation layer:\n\n```kotlin\n// ✅ Recommended: ViewModels handle notifications\nclass ProfileViewModel : ViewModel() {\n    private val repository = UserRepository()\n    \n    fun saveProfile() {\n        viewModelScope.launch {\n            try {\n                repository.saveProfile()\n                SnapNotify.showSuccess(\"Profile saved!\")\n            } catch (e: Exception) {\n                SnapNotify.showError(\"Save failed\", \"Retry\") { saveProfile() }\n            }\n        }\n    }\n}\n\n// ✅ Clean: Repositories focus on data operations\nclass UserRepository {\n    suspend fun saveProfile(): Result\u003cUnit\u003e {\n        return try {\n            api.updateProfile()\n            Result.success(Unit)\n        } catch (e: Exception) {\n            Result.failure(e)\n        }\n    }\n}\n\n// ✅ Alternative: Direct from Composables\n@Composable\nfun SaveButton() {\n    val viewModel: ProfileViewModel = hiltViewModel()\n    \n    Button(\n        onClick = {\n            viewModel.saveProfile()\n            // SnapNotify calls handled in ViewModel\n        }\n    ) {\n        Text(\"Save\")\n    }\n}\n```\n\n## 🏗 Architecture \u0026 Best Practices\n\nSnapNotify follows clean architecture principles with proper separation of concerns:\n\n```\n┌─────────────────┐    ┌──────────────────┐    ┌─────────────────┐\n│   SnapNotify    │───▶│ SnackbarManager  │───▶│ SnapNotifyVM    │\n│   (Public API)  │    │   (Singleton)    │    │   (ViewModel)   │\n└─────────────────┘    └──────────────────┘    └─────────────────┘\n                                │                        │\n                                ▼                        ▼\n                       ┌──────────────────┐    ┌─────────────────┐\n                       │  Message Queue   │    │ SnapNotify UI   │\n                       │   (StateFlow)    │    │  (Composable)   │\n                       └──────────────────┘    └─────────────────┘\n```\n\n### Architectural Layers\n- **Presentation Layer**: ViewModels and Composables handle SnapNotify calls\n- **Domain Layer**: Use Cases return `Result\u003cT\u003e` or throw exceptions\n- **Data Layer**: Repositories focus purely on data operations\n- **UI Layer**: SnapNotifyProvider manages display and styling\n\n### Design Principles\n- **Thread Safety**: Internal mutex synchronization for concurrent access\n- **Clean Architecture**: UI concerns separated from business logic\n- **Reactive Streams**: StateFlow-based message queue management  \n- **Optional DI**: Works with or without dependency injection frameworks\n- **Single Responsibility**: Each component has a focused purpose\n\n## 🔧 Configuration\n\n### Queue Configuration\n\nControl the internal message queue behavior:\n\n```kotlin\n// Configure globally\nSnapNotify.configure(\n    SnapNotifyConfig(\n        maxQueueSize = 100,  // Default is 50\n        onMessageDropped = { droppedMessage -\u003e\n            // Log or monitor dropped messages\n            Log.w(\"SnapNotify\", \"Message dropped: $droppedMessage\")\n        }\n    )\n)\n\n// Or configure per-provider (feature-scoped)\nSnapNotifyProvider(\n    config = SnapNotifyConfig(\n        maxQueueSize = 20,\n        onMessageDropped = { msg -\u003e /* handle drop */ }\n    )\n) {\n    FeatureContent()\n}\n```\n\n**Queue Management:**\n- When the queue is full, the oldest message is dropped\n- The `onMessageDropped` callback is invoked with the dropped message text\n- Useful for monitoring queue saturation in production\n- Prevents memory issues from unbounded message accumulation\n\n### Enhanced Provider Options\n\n```kotlin\nSnapNotifyProvider(\n    modifier = Modifier.padding(16.dp),\n    style = SnackbarStyle.success(),  // Default style for all messages\n    config = SnapNotifyConfig(maxQueueSize = 50),  // Queue configuration\n    hostAlignment = Alignment.TopCenter,  // Position snackbars at top\n    hostInsets = WindowInsets.statusBars,  // Custom insets\n    hostContent = { hostState, style -\u003e  // Complete customization\n        // Custom snackbar host rendering\n        SnackbarHost(hostState) { data -\u003e\n            // Your custom snackbar UI\n        }\n    }\n) {\n    MyAppContent()\n}\n```\n\n**Key Features:**\n- **hostAlignment**: Position snackbars anywhere (Top, Bottom, Center)\n- **hostInsets**: Control padding for system bars and keyboard (IME)\n- **hostContent**: Complete control over snackbar rendering\n- **config**: Feature-scoped queue configuration\n\n### Hilt Integration (Optional)\n\nSnapNotify works without any DI framework. If you use Hilt in your project, it integrates seamlessly - no additional setup needed!\n\n## 📋 API Reference\n\n### SnapNotify Object\n\n```kotlin\nobject SnapNotify {\n    // Configuration\n    fun configure(config: SnapNotifyConfig)\n\n    // Basic messages\n    fun show(message: String, duration: SnackbarDuration = SnackbarDuration.Short)\n    fun show(message: String, duration: SnackbarDuration = SnackbarDuration.Short, durationMillis: Long? = null)\n    fun show(\n        message: String,\n        actionLabel: String,\n        onAction: () -\u003e Unit,\n        duration: SnackbarDuration = SnackbarDuration.Short\n    )\n    fun show(\n        message: String,\n        actionLabel: String,\n        onAction: () -\u003e Unit,\n        duration: SnackbarDuration = SnackbarDuration.Short,\n        durationMillis: Long? = null\n    )\n\n    // Custom styled messages\n    fun showStyled(\n        message: String,\n        style: SnackbarStyle,\n        duration: SnackbarDuration = SnackbarDuration.Short\n    )\n    fun showStyled(\n        message: String,\n        style: SnackbarStyle,\n        duration: SnackbarDuration = SnackbarDuration.Short,\n        durationMillis: Long? = null\n    )\n    fun showStyled(\n        message: String,\n        style: SnackbarStyle,\n        actionLabel: String,\n        onAction: () -\u003e Unit,\n        duration: SnackbarDuration = SnackbarDuration.Short\n    )\n    fun showStyled(\n        message: String,\n        style: SnackbarStyle,\n        actionLabel: String,\n        onAction: () -\u003e Unit,\n        duration: SnackbarDuration = SnackbarDuration.Short,\n        durationMillis: Long? = null\n    )\n\n    // Themed messages (all support durationMillis parameter)\n    fun showSuccess(message: String, duration: SnackbarDuration = SnackbarDuration.Short, durationMillis: Long? = null)\n    fun showSuccess(\n        message: String,\n        actionLabel: String,\n        onAction: () -\u003e Unit,\n        duration: SnackbarDuration = SnackbarDuration.Short,\n        durationMillis: Long? = null\n    )\n\n    fun showError(message: String, duration: SnackbarDuration = SnackbarDuration.Short, durationMillis: Long? = null)\n    fun showError(\n        message: String,\n        actionLabel: String,\n        onAction: () -\u003e Unit,\n        duration: SnackbarDuration = SnackbarDuration.Short,\n        durationMillis: Long? = null\n    )\n\n    fun showWarning(message: String, duration: SnackbarDuration = SnackbarDuration.Short, durationMillis: Long? = null)\n    fun showWarning(\n        message: String,\n        actionLabel: String,\n        onAction: () -\u003e Unit,\n        duration: SnackbarDuration = SnackbarDuration.Short,\n        durationMillis: Long? = null\n    )\n\n    fun showInfo(message: String, duration: SnackbarDuration = SnackbarDuration.Short, durationMillis: Long? = null)\n    fun showInfo(\n        message: String,\n        actionLabel: String,\n        onAction: () -\u003e Unit,\n        duration: SnackbarDuration = SnackbarDuration.Short,\n        durationMillis: Long? = null\n    )\n\n    // Management\n    fun clearAll()\n}\n```\n\n### SnapNotifyProvider Composable\n\n```kotlin\n@Composable\nfun SnapNotifyProvider(\n    modifier: Modifier = Modifier,\n    style: SnackbarStyle? = null,\n    config: SnapNotifyConfig? = null,\n    hostAlignment: Alignment = Alignment.BottomCenter,\n    hostInsets: WindowInsets = WindowInsets.navigationBars.union(WindowInsets.ime),\n    hostContent: (@Composable BoxScope.(SnackbarHostState, SnackbarStyle) -\u003e Unit)? = null,\n    content: @Composable () -\u003e Unit\n)\n```\n\n### SnapNotifyConfig Data Class\n\n```kotlin\ndata class SnapNotifyConfig(\n    val maxQueueSize: Int = 50,  // Maximum pending messages\n    val onMessageDropped: ((String) -\u003e Unit)? = null  // Callback when message is dropped\n)\n```\n\n### SnackbarStyle Data Class\n\n```kotlin\ndata class SnackbarStyle(\n    val containerColor: Color = Color.Unspecified,\n    val contentColor: Color = Color.Unspecified,\n    val actionColor: Color = Color.Unspecified,\n    val shape: Shape? = null,\n    val elevation: Dp? = null,\n    val messageTextStyle: TextStyle? = null,\n    val actionTextStyle: TextStyle? = null\n) {\n    companion object {\n        @Composable fun default(): SnackbarStyle\n        @Composable fun success(): SnackbarStyle\n        @Composable fun error(): SnackbarStyle\n        @Composable fun warning(): SnackbarStyle\n        @Composable fun info(): SnackbarStyle\n    }\n}\n```\n\n## 🧪 Testing\n\nSnapNotify includes comprehensive test coverage with **74+ test cases** covering **100% of the public API**:\n\n### Test Coverage\n- **✅ Public API Methods**: All SnapNotify methods tested\n- **✅ Queue Management**: Backpressure, message dropping, and configuration\n- **✅ Custom Duration System**: Complete SnackbarDurationWrapper functionality\n- **✅ Styling System**: Complete SnackbarStyle functionality\n- **✅ Thread Safety**: Concurrent access and race condition handling\n- **✅ Action Callbacks**: User interaction handling\n- **✅ Timeout Handling**: Custom duration timeout behavior\n- **✅ Edge Cases**: Boundary value testing and error scenarios\n- **✅ Integration Tests**: End-to-end behavior verification\n\n### Running Tests\n```bash\n# Run unit tests\n./gradlew :snapnotify:test\n\n# Run all tests including UI tests\n./gradlew :snapnotify:check\n\n# View test report\nopen snapnotify/build/reports/tests/testDebugUnitTest/index.html\n```\n\nThe comprehensive test suite ensures reliability and catches regressions during development.\n\n## 📱 Example App\n\nCheck out the `SnapNotifyDemo` module for a complete demo:\n\n```bash\n# Build and install demo app\n./gradlew :SnapNotifyDemo:installDebug\n```\n\n## 📄 License\n\n```\nCopyright 2025 Vamsi Vaddavalli\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fivamsi%2Fsnapnotify","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fivamsi%2Fsnapnotify","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fivamsi%2Fsnapnotify/lists"}