KmpLibrary

KMPTestApp Documentation

KMPTestApp is the test application that demonstrates the use of all modules of the SweetMeSoft KMP library. It serves as an implementation example and best practices guide.

Table of Contents

General Description

KMPTestApp is a complete application that demonstrates:

Project Structure

kmptestapp/
├── src/
│   ├── androidMain/
│   │   └── kotlin/
│   │       └── com/sweetmesoft/kmptestapp/
│   │           └── MainActivity.kt
│   ├── commonMain/
│   │   └── kotlin/
│   │       └── com/sweetmesoft/kmptestapp/
│   │           ├── App.kt                    # Main entry point
│   │           ├── PhotoProfileRequest.kt    # Data model
│   │           ├── screens/
│   │           │   ├── about/
│   │           │   │   └── AboutScreen.kt    # About screen
│   │           │   ├── main/
│   │           │   │   ├── MainScreen.kt     # Main screen
│   │           │   │   └── MainViewModel.kt  # Main ViewModel
│   │           │   └── splash/
│   │           │       └── SplashScreen.kt   # Splash screen
│   │           └── theme/
│   │               ├── Color.kt              # Theme colors
│   │               └── Theme.kt              # Theme configuration
│   └── iosMain/
│       └── kotlin/
│           └── com/sweetmesoft/kmptestapp/
│               └── MainViewController.kt
└── build.gradle.kts

Configuration

Main Dependencies

commonMain.dependencies {
    implementation(projects.library)
    implementation(projects.kmpcontrols)
    implementation(projects.kmpmaps)
    
    // Navigation
    implementation(libs.voyager.navigator)
    implementation(libs.voyager.bottom.sheet.navigator)
    implementation(libs.voyager.tab.navigator)
    implementation(libs.voyager.transitions)
    
    // UI and Compose
    implementation(compose.runtime)
    implementation(compose.foundation)
    implementation(compose.material3)
    implementation(compose.ui)
    implementation(compose.components.resources)
    implementation(compose.components.uiToolingPreview)
    
    // Lifecycle
    implementation(libs.androidx.lifecycle.viewmodel)
    implementation(libs.androidx.lifecycle.runtime.compose)
    
    // Utilities
    implementation(libs.kotlinx.datetime)
    implementation(libs.kotlinx.serialization.json)
}

Android Configuration

android {
    namespace = "com.sweetmesoft.kmptestapp"
    compileSdk = libs.versions.android.compileSdk.get().toInt()
    
    defaultConfig {
        applicationId = "com.sweetmesoft.kmptestapp"
        minSdk = libs.versions.android.minSdk.get().toInt()
        targetSdk = libs.versions.android.targetSdk.get().toInt()
        versionCode = 1
        versionName = "1.0"
    }
}

Example Screens

App.kt - Entry Point

@Composable
fun App() {
    AppTheme {
        Navigator(screen = SplashScreen()) { navigator ->
            // Configure global navigator
            BaseViewModel.navigator = navigator
            
            // Apply transitions
            SlideTransition(navigator)
        }
    }
}

Features:

SplashScreen.kt - Welcome Screen

class SplashScreen : BaseScreen() {
    @Composable
    override fun ScreenContent() {
        var isLoading by remember { mutableStateOf(true) }
        
        LaunchedEffect(Unit) {
            delay(2000) // Simulate loading
            isLoading = false
        }
        
        Box(
            modifier = Modifier
                .fillMaxSize()
                .background(
                    brush = Brush.verticalGradient(
                        colors = listOf(
                            MaterialTheme.colorScheme.primary,
                            MaterialTheme.colorScheme.secondary
                        )
                    )
                ),
            contentAlignment = Alignment.Center
        ) {
            Column(
                horizontalAlignment = Alignment.CenterHorizontally,
                verticalArrangement = Arrangement.spacedBy(24.dp)
            ) {
                // Application logo
                Icon(
                    imageVector = Icons.Default.Star,
                    contentDescription = null,
                    modifier = Modifier.size(120.dp),
                    tint = Color.White
                )
                
                Text(
                    text = "KMP Test App",
                    style = MaterialTheme.typography.headlineLarge,
                    color = Color.White,
                    fontWeight = FontWeight.Bold
                )
                
                Text(
                    text = "Demonstrating SweetMeSoft KMP Library",
                    style = MaterialTheme.typography.bodyLarge,
                    color = Color.White.copy(alpha = 0.8f),
                    textAlign = TextAlign.Center
                )
                
                if (isLoading) {
                    Spacer(modifier = Modifier.height(32.dp))
                    CircularProgressIndicator(
                        color = Color.White,
                        modifier = Modifier.size(32.dp)
                    )
                } else {
                    LaunchedEffect(Unit) {
                        navigator?.replace(MainScreen())
                    }
                }
            }
        }
    }
}

Features:

MainScreen.kt - Main Screen

The main screen uses LocalGridList to provide navigation to different feature modules.

class MainScreen : BaseBottomBarScreen() {
    @Composable
    override fun ScreenContent() {
        val navigator = LocalNavigator.currentOrThrow
        
        val menuItems = listOf(
            MainMenuItem("Controls", "Input controls", TablerIcons.Forms) { 
                navigator.push(ControlsScreen()) 
            },
            MainMenuItem("Maps", "Map integration", TablerIcons.Map) { 
                navigator.push(MapScreen()) 
            },
            MainMenuItem("Dialogs", "Alerts & Dialogs", TablerIcons.Message) { 
                navigator.push(DialogsScreen()) 
            },
            // ... other items
        )
        
        LocalGridList(
            list = menuItems,
            columns = 2,
            contentPadding = PaddingValues(16.dp)
        ) { _, item ->
            MenuCard(item)
        }
        
        // Alert and popup handling
        PopupHandler.currentPopup?.let { popup ->
            // ...
        }
    }
}

Features:

MainViewModel.kt - Business Logic

class MainViewModel : BaseViewModel() {
    private val _uiState = MutableStateFlow(MainUiState())
    val uiState: StateFlow<MainUiState> = _uiState.asStateFlow()
    
    fun showDatePicker() {
        // Date picker implementation
    }
    
    fun showTimePicker() {
        // Time picker implementation
    }
    
    fun showMapExample() {
        // Navigation to maps example
    }
    
    fun testNetworkCall() {
        viewModelScope.launch {
            setLoading(true)
            try {
                val result = NetworkUtils.get("https://api.example.com/test")
                PopupHandler.showAlert(
                    title = "Success",
                    message = "Response: $result"
                )
            } catch (e: Exception) {
                setError(e.message)
            } finally {
                setLoading(false)
            }
        }
    }
    
    fun showImagePicker() {
        // Image picker implementation
    }
}

data class MainUiState(
    val selectedDate: LocalDate? = null,
    val selectedTime: LocalTime? = null,
    val profileImage: String? = null,
    val searchQuery: String = "",
    val selectedCountry: String? = null
)

Features:

AboutScreen.kt - App Information

class AboutScreen : BaseScreen() {
    @Composable
    override fun ScreenContent() {
        Column(
            modifier = Modifier
                .fillMaxSize()
                .padding(16.dp),
            verticalArrangement = Arrangement.spacedBy(24.dp)
        ) {
            // Header with back button
            Row(
                modifier = Modifier.fillMaxWidth(),
                verticalAlignment = Alignment.CenterVertically
            ) {
                IconButton(
                    onClick = { navigator?.pop() }
                ) {
                    Icon(Icons.Default.ArrowBack, contentDescription = "Back")
                }
                Text(
                    text = "About",
                    style = MaterialTheme.typography.headlineMedium,
                    modifier = Modifier.weight(1f)
                )
            }
            
            // Application information
            AboutContent(
                appName = "KMP Test App",
                version = "1.0.0",
                description = "Demonstration application for SweetMeSoft KMP Library. " +
                        "Shows the use of all components and functionalities " +
                        "available in the multiplatform library.",
                developers = listOf(
                    Developer(
                        name = "SweetMeSoft Team",
                        role = "Development",
                        email = "team@sweetmesoft.com"
                    )
                ),
                links = listOf(
                    AppLink(
                        title = "GitHub Repository",
                        url = "https://github.com/erickvelasco11/KmpLibrary",
                        icon = Icons.Default.Code
                    ),
                    AppLink(
                        title = "Documentation",
                        url = "https://erickvelasco11.github.io/KmpLibrary",
                        icon = Icons.Default.MenuBook
                    )
                )
            )
        }
    }
}

Features:

Themes and Styles

Color.kt - Color Palette

val md_theme_light_primary = Color(0xFF6750A4)
val md_theme_light_onPrimary = Color(0xFFFFFFFF)
val md_theme_light_primaryContainer = Color(0xFFEADDFF)
val md_theme_light_onPrimaryContainer = Color(0xFF21005D)

val md_theme_dark_primary = Color(0xFFD0BCFF)
val md_theme_dark_onPrimary = Color(0xFF381E72)
val md_theme_dark_primaryContainer = Color(0xFF4F378B)
val md_theme_dark_onPrimaryContainer = Color(0xFFEADDFF)

// Custom colors
val SweetMeSoftBlue = Color(0xFF2196F3)
val SweetMeSoftGreen = Color(0xFF4CAF50)
val SweetMeSoftOrange = Color(0xFFFF9800)

Theme.kt - Theme Configuration

private val LightColorScheme = lightColorScheme(
    primary = md_theme_light_primary,
    onPrimary = md_theme_light_onPrimary,
    primaryContainer = md_theme_light_primaryContainer,
    onPrimaryContainer = md_theme_light_onPrimaryContainer,
    // ... more colors
)

private val DarkColorScheme = darkColorScheme(
    primary = md_theme_dark_primary,
    onPrimary = md_theme_dark_onPrimary,
    primaryContainer = md_theme_dark_primaryContainer,
    onPrimaryContainer = md_theme_dark_onPrimaryContainer,
    // ... more colors
)

@Composable
fun AppTheme(
    darkTheme: Boolean = isSystemInDarkTheme(),
    dynamicColor: Boolean = true,
    content: @Composable () -> Unit
) {
    val colorScheme = when {
        dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
            val context = LocalContext.current
            if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
        }
        darkTheme -> DarkColorScheme
        else -> LightColorScheme
    }
    
    MaterialTheme(
        colorScheme = colorScheme,
        typography = Typography(),
        content = content
    )
}

Features:

Usage Examples

Example 1: Components Section

@Composable
fun ComponentsSection(viewModel: MainViewModel) {
    Card(
        modifier = Modifier.fillMaxWidth(),
        elevation = CardDefaults.cardElevation(defaultElevation = 4.dp)
    ) {
        Column(
            modifier = Modifier.padding(16.dp),
            verticalArrangement = Arrangement.spacedBy(12.dp)
        ) {
            Text(
                text = "KMPControls Components",
                style = MaterialTheme.typography.titleLarge,
                fontWeight = FontWeight.Bold
            )
            
            // Date selector
            ClickableOutlinedTextField(
                value = viewModel.uiState.value.selectedDate?.toString() ?: "",
                label = "Select Date",
                onClick = { viewModel.showDatePicker() },
                modifier = Modifier.fillMaxWidth()
            )
            
            // Time selector
            ClickableOutlinedTextField(
                value = viewModel.uiState.value.selectedTime?.toString() ?: "",
                label = "Select Time",
                onClick = { viewModel.showTimePicker() },
                modifier = Modifier.fillMaxWidth()
            )
            
            // Password control
            var password by remember { mutableStateOf("") }
            PasswordControl(
                value = password,
                onValueChange = { password = it },
                label = "Example Password",
                modifier = Modifier.fillMaxWidth()
            )
        }
    }
}

Example 2: Maps Section

@Composable
fun MapsSection(viewModel: MainViewModel) {
    Card(
        modifier = Modifier.fillMaxWidth(),
        elevation = CardDefaults.cardElevation(defaultElevation = 4.dp)
    ) {
        Column(
            modifier = Modifier.padding(16.dp),
            verticalArrangement = Arrangement.spacedBy(12.dp)
        ) {
            Text(
                text = "KMPMaps Components",
                style = MaterialTheme.typography.titleLarge,
                fontWeight = FontWeight.Bold
            )
            
            Text(
                text = "Multiplatform maps with Google Maps (Android) and MapKit (iOS)",
                style = MaterialTheme.typography.bodyMedium,
                color = MaterialTheme.colorScheme.onSurfaceVariant
            )
            
            Button(
                onClick = { viewModel.showMapExample() },
                modifier = Modifier.fillMaxWidth()
            ) {
                Icon(Icons.Default.Map, contentDescription = null)
                Spacer(modifier = Modifier.width(8.dp))
                Text("View Map Example")
            }
        }
    }
}

Example 3: Utilities Section

@Composable
fun UtilitiesSection(viewModel: MainViewModel) {
    Card(
        modifier = Modifier.fillMaxWidth(),
        elevation = CardDefaults.cardElevation(defaultElevation = 4.dp)
    ) {
        Column(
            modifier = Modifier.padding(16.dp),
            verticalArrangement = Arrangement.spacedBy(12.dp)
        ) {
            Text(
                text = "Library Utilities",
                style = MaterialTheme.typography.titleLarge,
                fontWeight = FontWeight.Bold
            )
            
            // Network test
            Button(
                onClick = { viewModel.testNetworkCall() },
                modifier = Modifier.fillMaxWidth()
            ) {
                Icon(Icons.Default.CloudDownload, contentDescription = null)
                Spacer(modifier = Modifier.width(8.dp))
                Text("Test Network Call")
            }
            
            // Image selector
            Button(
                onClick = { viewModel.showImagePicker() },
                modifier = Modifier.fillMaxWidth()
            ) {
                Icon(Icons.Default.Image, contentDescription = null)
                Spacer(modifier = Modifier.width(8.dp))
                Text("Select Image")
            }
            
            // Validation example
            var email by remember { mutableStateOf("") }
            OutlinedTextField(
                value = email,
                onValueChange = { email = it },
                label = { Text("Test Email") },
                isError = email.isNotBlank() && !StringUtils.isValidEmail(email),
                supportingText = {
                    if (email.isNotBlank() && !StringUtils.isValidEmail(email)) {
                        Text("Invalid email")
                    }
                },
                modifier = Modifier.fillMaxWidth()
            )
        }
    }
}

How to Run

Prerequisites

  1. Android Studio (latest stable version)
  2. Xcode (for iOS development)
  3. JDK 17 or higher
  4. Kotlin Multiplatform Mobile plugin

Steps to Run

Android

# Clone the repository
git clone https://github.com/erickvelasco11/KmpLibrary.git
cd KmpLibrary

# Run on Android
./gradlew :kmptestapp:installDebug

# Or from Android Studio
# 1. Open the project
# 2. Select 'kmptestapp' as module
# 3. Run on Android device/emulator

iOS

# Generate Xcode project
./gradlew :kmptestapp:embedAndSignAppleFrameworkForXcode

# Open in Xcode
open iosApp/iosApp.xcodeproj

# Or from Android Studio with KMP plugin
# 1. Select iOS configuration
# 2. Run on iOS simulator/device

API Keys Configuration

To use maps functionalities, configure the API keys:

Android

<!-- android/src/main/res/values/strings.xml -->
<resources>
    <string name="google_maps_key">YOUR_API_KEY_HERE</string>
</resources>

iOS

<!-- iosApp/iosApp/Info.plist -->
<key>NSLocationWhenInUseUsageDescription</key>
<string>This app needs location access to show maps</string>