CalmDirectory is an Android directory application built with Jetpack Compose, optimized for E-ink displays using the Mudita Mindful Design (MMD) library. The app provides a minimalist, distraction-free interface for searching points of interest (POIs) such as restaurants, gas stations, hotels, and more.
| Landing Screen | Search Results | POI Details Screen | Settings Screen |
|---|---|---|---|
![]() |
![]() |
![]() |
![]() |
- Overview
- Mudita Mindful Design (MMD) Integration
- E-ink Display Considerations
- Technical Stack
- Setup and Installation
- Architecture
- Usage
- API Configuration
- Contributing
CalmDirectory is designed with mindfulness and digital wellness at its core. By leveraging the Mudita Mindful Design library, the app provides an optimal experience for E-ink devices, minimizing visual noise, eliminating unnecessary animations, and maximizing readability.
Key Features:
- E-ink optimized UI with monochromatic color scheme
- Minimal animations to reduce E-ink ghosting
- High-contrast typography for better readability
- Location-based POI search using Google Places API
- Manual and device-based location selection
- Category-based browsing (restaurants, gas stations, hotels, etc.)
The Mudita Mindful Design (MMD) library is a custom Material Design component library specifically optimized for E-ink displays and mindful user experiences. It extends Jetpack Compose with components that prioritize:
- Readability: High contrast, optimized typography
- Minimalism: Clean interfaces without visual clutter
- Performance: Reduced animations to prevent E-ink ghosting
- Mindfulness: Distraction-free design patterns
Dependency:
implementation 'com.mudita:MMD:1.0.0'CalmDirectory implements several E-ink optimization strategies through MMD:
E-ink displays excel with black and white content. The app uses eInkColorScheme which provides:
- Pure black text on white backgrounds
- High contrast ratios for readability
- No color gradients that can cause artifacts
ThemeMMD(
colorScheme = eInkColorScheme,
content = content
)MMD's eInkTypography provides font styles specifically tuned for E-ink readability:
- Increased font weights for better contrast
- Larger default sizes to reduce eye strain
- Optimized line heights and letter spacing
typography = eInkTypography.copy(
headlineLarge = TextStyle(
fontSize = 28.sp,
fontWeight = FontWeight.SemiBold,
color = Color.Black
),
bodyLarge = TextStyle(
fontSize = 18.sp,
fontWeight = FontWeight.Normal,
color = Color.Black
)
)Ripple animations cause ghosting on E-ink displays. MMD automatically disables ripple effects by default, replacing them with instant feedback mechanisms.
E-ink displays have slow refresh rates (typically 100-200ms). MMD components minimize or eliminate animations to prevent:
- Ghosting artifacts
- Visual latency
- User confusion
MMD emphasizes clear visual separation using dividers and borders rather than shadows or elevation, which render poorly on E-ink:
HorizontalDividerMMD(
thickness = 2.dp,
color = MaterialTheme.colorScheme.outlineVariant
)CalmDirectory utilizes the following MMD components throughout the application:
Optimized lazy list rendering with:
- Instant scrolling (no momentum scrolling)
- Clear item boundaries
- Efficient recomposition for E-ink displays
Usage in Landing Screen:
LazyColumnMMD(modifier = modifier) {
items(poiCategories) { category ->
// Category items
HorizontalDividerMMD(thickness = 1.dp)
}
}E-ink optimized top app bar with:
- High contrast title and icon rendering
- No elevation shadows
- Clear visual separation from content
Usage in Main Activity:
TopAppBarMMD(
title = { Text("Directory") },
navigationIcon = { /* Back button */ },
actions = { /* Action icons */ }
)Button component designed for E-ink with:
- High contrast borders
- No gradient backgrounds
- Instant press feedback (no ripple)
- Clear visual states (pressed/unpressed)
Usage in Settings:
ButtonMMD(onClick = {
userPreferencesRepository.saveApiKey(newApiKey)
}) {
Text("Save")
}Input field optimized for E-ink displays:
- High contrast borders
- Clear focus indicators
- No animated label transitions
- Instant cursor rendering
Usage in Settings Screen:
TextFieldMMD(
value = newApiKey,
onValueChange = { newApiKey = it },
label = { Text("API Key") }
)Toggle switch with E-ink optimizations:
- Clear on/off states
- No transition animations
- High contrast thumb and track
Usage for Location Toggle:
SwitchMMD(
checked = useDeviceLocation,
onCheckedChange = { viewModel.setUseDeviceLocation(it) }
)Search input component with:
- Optimized keyboard interactions
- Clear text rendering
- Instant query updates
Usage in Top App Bar:
SearchBarDefaultsMMD.InputField(
query = searchQuery,
onQueryChange = { searchViewModel.onSearchQueryChange(it) },
expanded = true,
placeholder = { Text("Search for a place") }
)Loading indicator designed for E-ink:
- Simplified animation or static frames
- High contrast rendering
- Minimal refresh cycles
Usage in Search Screen Host:
CircularProgressIndicatorMMD()Visual separator optimized for E-ink:
- Configurable thickness
- Sharp rendering
- No anti-aliasing artifacts
Usage Throughout:
HorizontalDividerMMD(
modifier = Modifier.padding(horizontal = 16.dp),
thickness = 1.dp
)Notification system with E-ink considerations:
- Simple appearing/disappearing (no slide animations)
- High contrast messages
- Clear dismissal
Usage in Settings:
val snackbarHostState = remember { SnackbarHostStateMMD() }
snackbarHostState.showSnackbar("API Key saved successfully")- List-based navigation: Vertical scrolling is more natural on E-ink than complex grid layouts
- Full-width elements: Minimizes partial refreshes
- Clear hierarchies: Strong visual structure with dividers and whitespace
- Larger fonts: 18sp for body text, 28sp for headlines
- Increased font weights: SemiBold for emphasis
- No subpixel rendering: Sharp, whole-pixel boundaries
- Single-tap actions: No long-press or swipe gestures that require visual feedback
- Instant state changes: Toggle switches and buttons show immediate state
- Clear affordances: Icons and text clearly indicate interactivity
- Text-first: Information conveyed through text rather than icons alone
- Monochrome icons: Material Icons work well on E-ink
- Concise information: Dense information display to minimize scrolling
CalmDirectory has been optimized for:
- E-ink Android tablets (e.g., Onyx Boox, reMarkable, Mudita devices)
- Devices with monochrome displays
- Low refresh rate screens (10-15 Hz)
Testing Recommendations:
- Enable "Show layout bounds" in Developer Options to verify alignment
- Test with device-specific E-ink modes (A2, X mode, etc.)
- Verify text remains readable at minimum brightness
- Confirm no ghosting occurs during normal navigation
- Language: Kotlin 1.9.22
- UI Framework: Jetpack Compose 1.7.3
- Design Library: Mudita Material Design (MMD) 1.0.0
- Build System: Gradle 8.3.0
- Architecture: MVVM with StateFlow
- Navigation: Jetpack Compose Navigation
- Data Persistence: DataStore Preferences
- Networking:
- Google Places SDK for Android 3.5.0
- Ktor Client 2.3.2
- Retrofit 2.9.0
- Min SDK: 28 (Android 9.0 Pie)
- Target SDK: 35 (Android 15)
- Android Studio (latest stable version)
- JDK 17
- Gradle 8.3.0 (system installation)
- Google Places API Key (Get one here)
-
Clone the repository:
git clone https://github.com/yourusername/CalmDirectory.git cd CalmDirectory -
Configure the MMD library:
The MMD library (
com.mudita:MMD:1.0.0) must be available in your local Maven repository or configured via a custom repository. Add tosettings.gradle:dependencyResolutionManagement { repositories { google() mavenCentral() maven { url 'https://mudita.jfrog.io/artifactory/mmd-release' } // Example } }
-
Build the project:
gradle build
-
Run on an emulator or device:
gradle installDebug adb shell am start -n com.example.helloworld/.MainActivity
On first launch, the app will prompt you to:
- Enter your Google Places API key
- Configure location preferences (device location or manual entry)
CalmDirectory follows MVVM architecture optimized for E-ink rendering:
┌─────────────────────────────────────┐
│ UI Layer (Compose) │
│ ┌──────────────────────────┐ │
│ │ MMD Components │ │
│ │ - LazyColumnMMD │ │
│ │ - ButtonMMD │ │
│ │ - TextFieldMMD │ │
│ │ - TopAppBarMMD │ │
│ └──────────────────────────┘ │
│ ┌──────────────────────────┐ │
│ │ Theme (CalmDirectory) │ │
│ │ - eInkColorScheme │ │
│ │ - eInkTypography │ │
│ └──────────────────────────┘ │
└─────────────────────────────────────┘
↕
┌─────────────────────────────────────┐
│ ViewModel Layer │
│ - MainViewModel │
│ - SearchViewModel │
│ - SettingsViewModel │
│ (StateFlow for reactive state) │
└─────────────────────────────────────┘
↕
┌─────────────────────────────────────┐
│ Data Layer │
│ - UserPreferencesRepository │
│ - LocationRepository │
│ - GooglePlacesApiService │
│ - LocationService │
└─────────────────────────────────────┘
Theme.kt: MMD theme configuration with E-ink optimizationsLandingScreen.kt: Category grid usingLazyColumnMMDSearchScreen.kt: POI search results with MMD componentsSettingsScreen.kt: Configuration screen withTextFieldMMD,SwitchMMD,ButtonMMDMainActivity.kt: Navigation host withTopAppBarMMD
- Launch the app
- Select a category (Gas Stations, Restaurants, etc.)
- View results based on your current or default location
- Tap the search icon in the top bar
- Enter any search query (e.g., "pizza near me")
- Browse results and tap to view details
- Navigate to Settings
- Toggle "Use device location"
- ON: Uses GPS/network location
- OFF: Enter a default location manually (e.g., "New York, NY")
- Location autocomplete provides suggestions
- View full information: address, phone, hours, description
- Tap icons to:
- Open website
- View on map
- Call phone number
-
Create a Google Cloud Project:
- Go to Google Cloud Console
- Create a new project or select existing
-
Enable APIs:
- Enable "Places API (New)"
- Enable "Places SDK for Android"
- Enable "Geocoding API"
-
Create API Key:
- Navigate to Credentials
- Create API Key
- Restrict key to Android apps (add your package name:
com.example.helloworld)
-
Configure in App:
- On first launch, enter key in the settings screen
- Key is stored locally in DataStore
API keys are stored securely using DataStore Preferences:
class UserPreferencesRepository(context: Context) {
private val dataStore = context.dataStore
val apiKey: Flow<String?> = dataStore.data
.map { preferences -> preferences[API_KEY] }
suspend fun saveApiKey(apiKey: String) {
dataStore.edit { preferences ->
preferences[API_KEY] = apiKey
}
}
}Contributions are welcome! When contributing to CalmDirectory, please keep the following principles in mind:
- Maintain monochromatic design: No colors, gradients, or low-contrast elements
- Use MMD components: Prefer MMD components over standard Material3 components
- Minimize animations: Avoid any animated transitions
- Test on E-ink: If possible, test changes on actual E-ink devices
- Sharp rendering: Ensure all UI elements have clear, pixel-aligned boundaries
- Follow existing MVVM patterns
- Use StateFlow for reactive state
- Keep ViewM- odels free of Android framework dependencies
- Document E-ink optimizations in comments
- Fork the repository
- Create a feature branch (
feature/your-feature-name) - Implement changes following E-ink guidelines
- Test thoroughly on both regular and E-ink displays if available
- Submit PR with description of changes and screenshots
This project is licensed under the GNU General Public License v3.0 (GPLv3). See the LICENSE file for details.
CalmDirectory is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
CalmDirectory is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with this program. If not, see https://www.gnu.org/licenses/.
- Mudita for the Mindful Design library
- Google for Places API
- E-ink Android community for optimization insights
For issues, questions, or feature requests:
- Open an issue on GitHub
- Include device type (especially if E-ink)
- Attach screenshots demonstrating the issue
Built with mindfulness for a calmer digital experience.
CalmDirectory is an Android directory application built with Jetpack Compose, optimized for E-ink displays using the Mudita Mindful Design (MMD) library. The app provides a minimalist, distraction-free interface for searching points of interest (POIs) such as restaurants, gas stations, hotels, and more.
| Landing Screen | Search Results | POI Details Screen | Settings Screen |
|---|---|---|---|
![]() |
![]() |
![]() |
![]() |
- Overview
- Mudita Mindful Design (MMD) Integration
- E-ink Display Considerations
- Technical Stack
- Setup and Installation
- Architecture
- Usage
- API Configuration
- Contributing
CalmDirectory is designed with mindfulness and digital wellness at its core. By leveraging the Mudita Mindful Design library, the app provides an optimal experience for E-ink devices, minimizing visual noise, eliminating unnecessary animations, and maximizing readability.
Key Features:
- E-ink optimized UI with monochromatic color scheme
- Minimal animations to reduce E-ink ghosting
- High-contrast typography for better readability
- Location-based POI search using Google Places API
- Manual and device-based location selection
- Category-based browsing (restaurants, gas stations, hotels, etc.)
The Mudita Mindful Design (MMD) library is a custom Material Design component library specifically optimized for E-ink displays and mindful user experiences. It extends Jetpack Compose with components that prioritize:
- Readability: High contrast, optimized typography
- Minimalism: Clean interfaces without visual clutter
- Performance: Reduced animations to prevent E-ink ghosting
- Mindfulness: Distraction-free design patterns
Dependency:
implementation 'com.mudita:MMD:1.0.0'CalmDirectory implements several E-ink optimization strategies through MMD:
E-ink displays excel with black and white content. The app uses eInkColorScheme which provides:
- Pure black text on white backgrounds
- High contrast ratios for readability
- No color gradients that can cause artifacts
ThemeMMD(
colorScheme = eInkColorScheme,
content = content
)MMD's eInkTypography provides font styles specifically tuned for E-ink readability:
- Increased font weights for better contrast
- Larger default sizes to reduce eye strain
- Optimized line heights and letter spacing
typography = eInkTypography.copy(
headlineLarge = TextStyle(
fontSize = 28.sp,
fontWeight = FontWeight.SemiBold,
color = Color.Black
),
bodyLarge = TextStyle(
fontSize = 18.sp,
fontWeight = FontWeight.Normal,
color = Color.Black
)
)Ripple animations cause ghosting on E-ink displays. MMD automatically disables ripple effects by default, replacing them with instant feedback mechanisms.
E-ink displays have slow refresh rates (typically 100-200ms). MMD components minimize or eliminate animations to prevent:
- Ghosting artifacts
- Visual latency
- User confusion
MMD emphasizes clear visual separation using dividers and borders rather than shadows or elevation, which render poorly on E-ink:
HorizontalDividerMMD(
thickness = 2.dp,
color = MaterialTheme.colorScheme.outlineVariant
)CalmDirectory utilizes the following MMD components throughout the application:
Optimized lazy list rendering with:
- Instant scrolling (no momentum scrolling)
- Clear item boundaries
- Efficient recomposition for E-ink displays
Usage in Landing Screen:
LazyColumnMMD(modifier = modifier) {
items(poiCategories) { category ->
// Category items
HorizontalDividerMMD(thickness = 1.dp)
}
}E-ink optimized top app bar with:
- High contrast title and icon rendering
- No elevation shadows
- Clear visual separation from content
Usage in Main Activity:
TopAppBarMMD(
title = { Text("Directory") },
navigationIcon = { /* Back button */ },
actions = { /* Action icons */ }
)Button component designed for E-ink with:
- High contrast borders
- No gradient backgrounds
- Instant press feedback (no ripple)
- Clear visual states (pressed/unpressed)
Usage in Settings:
ButtonMMD(onClick = {
userPreferencesRepository.saveApiKey(newApiKey)
}) {
Text("Save")
}Input field optimized for E-ink displays:
- High contrast borders
- Clear focus indicators
- No animated label transitions
- Instant cursor rendering
Usage in Settings Screen:
TextFieldMMD(
value = newApiKey,
onValueChange = { newApiKey = it },
label = { Text("API Key") }
)Toggle switch with E-ink optimizations:
- Clear on/off states
- No transition animations
- High contrast thumb and track
Usage for Location Toggle:
SwitchMMD(
checked = useDeviceLocation,
onCheckedChange = { viewModel.setUseDeviceLocation(it) }
)Search input component with:
- Optimized keyboard interactions
- Clear text rendering
- Instant query updates
Usage in Top App Bar:
SearchBarDefaultsMMD.InputField(
query = searchQuery,
onQueryChange = { searchViewModel.onSearchQueryChange(it) },
expanded = true,
placeholder = { Text("Search for a place") }
)Loading indicator designed for E-ink:
- Simplified animation or static frames
- High contrast rendering
- Minimal refresh cycles
Usage in Search Screen Host:
CircularProgressIndicatorMMD()Visual separator optimized for E-ink:
- Configurable thickness
- Sharp rendering
- No anti-aliasing artifacts
Usage Throughout:
HorizontalDividerMMD(
modifier = Modifier.padding(horizontal = 16.dp),
thickness = 1.dp
)Notification system with E-ink considerations:
- Simple appearing/disappearing (no slide animations)
- High contrast messages
- Clear dismissal
Usage in Settings:
val snackbarHostState = remember { SnackbarHostStateMMD() }
snackbarHostState.showSnackbar("API Key saved successfully")- List-based navigation: Vertical scrolling is more natural on E-ink than complex grid layouts
- Full-width elements: Minimizes partial refreshes
- Clear hierarchies: Strong visual structure with dividers and whitespace
- Larger fonts: 18sp for body text, 28sp for headlines
- Increased font weights: SemiBold for emphasis
- No subpixel rendering: Sharp, whole-pixel boundaries
- Single-tap actions: No long-press or swipe gestures that require visual feedback
- Instant state changes: Toggle switches and buttons show immediate state
- Clear affordances: Icons and text clearly indicate interactivity
- Text-first: Information conveyed through text rather than icons alone
- Monochrome icons: Material Icons work well on E-ink
- Concise information: Dense information display to minimize scrolling
CalmDirectory has been optimized for:
- E-ink Android tablets (e.g., Onyx Boox, reMarkable, Mudita devices)
- Devices with monochrome displays
- Low refresh rate screens (10-15 Hz)
Testing Recommendations:
- Enable "Show layout bounds" in Developer Options to verify alignment
- Test with device-specific E-ink modes (A2, X mode, etc.)
- Verify text remains readable at minimum brightness
- Confirm no ghosting occurs during normal navigation
- Language: Kotlin 1.9.22
- UI Framework: Jetpack Compose 1.7.3
- Design Library: Mudita Material Design (MMD) 1.0.0
- Build System: Gradle 8.3.0
- Architecture: MVVM with StateFlow
- Navigation: Jetpack Compose Navigation
- Data Persistence: DataStore Preferences
- Networking:
- Google Places SDK for Android 3.5.0
- Ktor Client 2.3.2
- Retrofit 2.9.0
- Min SDK: 28 (Android 9.0 Pie)
- Target SDK: 35 (Android 15)
- Android Studio (latest stable version)
- JDK 17
- Gradle 8.3.0 (system installation)
- Google Places API Key (Get one here)
-
Clone the repository:
git clone https://github.com/yourusername/CalmDirectory.git cd CalmDirectory -
Configure the MMD library:
The MMD library (
com.mudita:MMD:1.0.0) must be available in your local Maven repository or configured via a custom repository. Add tosettings.gradle:dependencyResolutionManagement { repositories { google() mavenCentral() maven { url 'https://mudita.jfrog.io/artifactory/mmd-release' } // Example } }
-
Build the project:
gradle build
-
Run on an emulator or device:
gradle installDebug adb shell am start -n com.example.helloworld/.MainActivity
On first launch, the app will prompt you to:
- Enter your Google Places API key
- Configure location preferences (device location or manual entry)
CalmDirectory follows MVVM architecture optimized for E-ink rendering:
┌─────────────────────────────────────┐
│ UI Layer (Compose) │
│ ┌──────────────────────────┐ │
│ │ MMD Components │ │
│ │ - LazyColumnMMD │ │
│ │ - ButtonMMD │ │
│ │ - TextFieldMMD │ │
│ │ - TopAppBarMMD │ │
│ └──────────────────────────┘ │
│ ┌──────────────────────────┐ │
│ │ Theme (CalmDirectory) │ │
│ │ - eInkColorScheme │ │
│ │ - eInkTypography │ │
│ └──────────────────────────┘ │
└─────────────────────────────────────┘
↕
┌─────────────────────────────────────┐
│ ViewModel Layer │
│ - MainViewModel │
│ - SearchViewModel │
│ - SettingsViewModel │
│ (StateFlow for reactive state) │
└─────────────────────────────────────┘
↕
┌─────────────────────────────────────┐
│ Data Layer │
│ - UserPreferencesRepository │
│ - LocationRepository │
│ - GooglePlacesApiService │
│ - LocationService │
└─────────────────────────────────────┘
Theme.kt: MMD theme configuration with E-ink optimizationsLandingScreen.kt: Category grid usingLazyColumnMMDSearchScreen.kt: POI search results with MMD componentsSettingsScreen.kt: Configuration screen withTextFieldMMD,SwitchMMD,ButtonMMDMainActivity.kt: Navigation host withTopAppBarMMD
- Launch the app
- Select a category (Gas Stations, Restaurants, etc.)
- View results based on your current or default location
- Tap the search icon in the top bar
- Enter any search query (e.g., "pizza near me")
- Browse results and tap to view details
- Navigate to Settings
- Toggle "Use device location"
- ON: Uses GPS/network location
- OFF: Enter a default location manually (e.g., "New York, NY")
- Location autocomplete provides suggestions
- View full information: address, phone, hours, description
- Tap icons to:
- Open website
- View on map
- Call phone number
-
Create a Google Cloud Project:
- Go to Google Cloud Console
- Create a new project or select existing
-
Enable APIs:
- Enable "Places API (New)"
- Enable "Places SDK for Android"
- Enable "Geocoding API"
-
Create API Key:
- Navigate to Credentials
- Create API Key
- Restrict key to Android apps (add your package name:
com.example.helloworld)
-
Configure in App:
- On first launch, enter key in the settings screen
- Key is stored locally in DataStore
API keys are stored securely using DataStore Preferences:
class UserPreferencesRepository(context: Context) {
private val dataStore = context.dataStore
val apiKey: Flow<String?> = dataStore.data
.map { preferences -> preferences[API_KEY] }
suspend fun saveApiKey(apiKey: String) {
dataStore.edit { preferences ->
preferences[API_KEY] = apiKey
}
}
}Contributions are welcome! When contributing to CalmDirectory, please keep the following principles in mind:
- Maintain monochromatic design: No colors, gradients, or low-contrast elements
- Use MMD components: Prefer MMD components over standard Material3 components
- Minimize animations: Avoid any animated transitions
- Test on E-ink: If possible, test changes on actual E-ink devices
- Sharp rendering: Ensure all UI elements have clear, pixel-aligned boundaries
- Follow existing MVVM patterns
- Use StateFlow for reactive state
- Keep ViewM- odels free of Android framework dependencies
- Document E-ink optimizations in comments
- Fork the repository
- Create a feature branch (
feature/your-feature-name) - Implement changes following E-ink guidelines
- Test thoroughly on both regular and E-ink displays if available
- Submit PR with description of changes and screenshots
This project is licensed under the GNU General Public License v3.0 (GPLv3). See the LICENSE file for details.
CalmDirectory is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
CalmDirectory is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with this program. If not, see https://www.gnu.org/licenses/.
- Mudita for the Mindful Design library
- Google for Places API
- E-ink Android community for optimization insights
For issues, questions, or feature requests:
- Open an issue on GitHub
- Include device type (especially if E-ink)
- Attach screenshots demonstrating the issue
Built with mindfulness for a calmer digital experience.



