Wear OS Android UI Application in Kotlin

 

Wear OS Android UI Application in Kotlin

Here's a comprehensive Wear OS application built with Kotlin that demonstrates key UI components and patterns for smartwatches like Zeblaze.

1. Project Setup

First, ensure your build.gradle (Module) includes these dependencies:

gradle
Copy
dependencies {
    // Wear OS specific
    implementation "androidx.wear:wear:1.3.0"
    implementation "androidx.wear.compose:compose-material:1.2.0"
    implementation "androidx.wear.compose:compose-foundation:1.2.0"
    implementation "androidx.wear.compose:compose-navigation:1.2.0"
    
    // General Compose
    implementation "androidx.activity:activity-compose:1.8.0"
    implementation "androidx.compose.ui:ui:1.5.4"
    implementation "androidx.compose.material:material-icons-core:1.5.4"
    implementation "androidx.compose.material:material-icons-extended:1.5.4"
    
    // Health Services
    implementation "androidx.health:health-services-client:1.0.0-beta02"
    
    // ViewModel
    implementation "androidx.lifecycle:lifecycle-viewmodel-compose:2.6.2"
}

2. Main Activity and Navigation

MainActivity.kt:

kotlin
Copy
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.runtime.Composable
import androidx.navigation.compose.rememberNavController
import androidx.wear.compose.navigation.SwipeDismissableNavHost
import androidx.wear.compose.navigation.composable
import androidx.wear.compose.navigation.rememberSwipeDismissableNavController

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        
        setContent {
            WearApp()
        }
    }
}

@Composable
fun WearApp() {
    val navController = rememberSwipeDismissableNavController()
    
    SwipeDismissableNavHost(
        navController = navController,
        startDestination = "home"
    ) {
        composable("home") { HomeScreen(navController) }
        composable("steps") { StepsScreen(navController) }
        composable("heart") { HeartRateScreen(navController) }
        composable("workout") { WorkoutScreen(navController) }
        composable("settings") { SettingsScreen(navController) }
    }
}

3. Home Screen

HomeScreen.kt:

kotlin
Copy
import androidx.compose.foundation.layout.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.navigation.NavController
import androidx.wear.compose.material.*
import java.text.SimpleDateFormat
import java.util.*

@Composable
fun HomeScreen(navController: NavController) {
    var time by remember { mutableStateOf(getCurrentTime()) }
    
    LaunchedEffect(Unit) {
        while (true) {
            kotlinx.coroutines.delay(1000)
            time = getCurrentTime()
        }
    }
    
    Scaffold(
        timeText = { TimeText() },
        vignette = { Vignette(vignettePosition = VignettePosition.TopAndBottom) }
    ) {
        Column(
            modifier = Modifier.fillMaxSize(),
            verticalArrangement = Arrangement.Center,
            horizontalAlignment = Alignment.CenterHorizontally
        ) {
            // Time display
            Text(
                text = time,
                style = MaterialTheme.typography.display1
            )
            
            Spacer(modifier = Modifier.height(8.dp))
            
            // Quick stats
            Row(
                modifier = Modifier.fillMaxWidth(),
                horizontalArrangement = Arrangement.SpaceEvenly
            ) {
                MetricButton(
                    value = "7,650",
                    unit = "steps",
                    onClick = { navController.navigate("steps") }
                )
                MetricButton(
                    value = "78",
                    unit = "BPM",
                    onClick = { navController.navigate("heart") }
                )
            }
            
            Spacer(modifier = Modifier.height(16.dp))
            
            // Main actions
            Button(
                onClick = { navController.navigate("workout") },
                modifier = Modifier.size(ButtonDefaults.LargeButtonSize)
            ) {
                Icon(
                    imageVector = Icons.Default.DirectionsRun,
                    contentDescription = "Workout"
                )
            }
        }
    }
}

@Composable
fun MetricButton(value: String, unit: String, onClick: () -> Unit) {
    Column(
        horizontalAlignment = Alignment.CenterHorizontally,
        modifier = Modifier.clickable(onClick = onClick)
    ) {
        Text(
            text = value,
            style = MaterialTheme.typography.title2
        )
        Text(
            text = unit,
            style = MaterialTheme.typography.caption2
        )
    }
}

private fun getCurrentTime(): String {
    val sdf = SimpleDateFormat("HH:mm", Locale.getDefault())
    return sdf.format(Date())
}

4. Steps Screen with Health Data

StepsScreen.kt:

kotlin
Copy
import androidx.compose.foundation.layout.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import androidx.navigation.NavController
import androidx.wear.compose.material.*
import kotlinx.coroutines.delay

@Composable
fun StepsScreen(navController: NavController) {
    var steps by remember { mutableStateOf(0) }
    var goal by remember { mutableStateOf(10000) }
    var isRefreshing by remember { mutableStateOf(false) }
    
    // Simulate data refresh
    LaunchedEffect(isRefreshing) {
        if (isRefreshing) {
            delay(1500)
            steps = (7500..8500).random()
            isRefreshing = false
        }
    }
    
    Scaffold(
        timeText = { TimeText() },
        positionIndicator = { PositionIndicator() }
    ) {
        Column(
            modifier = Modifier.fillMaxSize(),
            verticalArrangement = Arrangement.Center,
            horizontalAlignment = Alignment.CenterHorizontally
        ) {
            CircularProgressIndicator(
                progress = (steps.toFloat() / goal.toFloat()).coerceAtMost(1f),
                modifier = Modifier.size(100.dp),
                startAngle = 270f,
                strokeWidth = 8.dp,
                indicatorColor = Color.Green
            )
            
            Spacer(modifier = Modifier.height(16.dp))
            
            Text(
                text = "$steps / $goal",
                style = MaterialTheme.typography.title2
            )
            
            Text(
                text = "steps today",
                style = MaterialTheme.typography.caption2
            )
            
            Spacer(modifier = Modifier.height(16.dp))
            
            Button(
                onClick = { isRefreshing = true },
                enabled = !isRefreshing,
                colors = ButtonDefaults.buttonColors(
                    backgroundColor = Color.Green.copy(alpha = 0.2f),
                    contentColor = Color.Green
                )
            ) {
                Text(text = if (isRefreshing) "Updating..." else "Refresh")
            }
            
            Spacer(modifier = Modifier.height(8.dp))
            
            TextButton(onClick = { navController.popBackStack() }) {
                Text(text = "Back")
            }
        }
    }
}

5. Heart Rate Screen with Animation

HeartRateScreen.kt:

kotlin
Copy
import androidx.compose.animation.core.*
import androidx.compose.foundation.layout.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.scale
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import androidx.navigation.NavController
import androidx.wear.compose.material.*
import kotlinx.coroutines.delay

@Composable
fun HeartRateScreen(navController: NavController) {
    var heartRate by remember { mutableStateOf<Int?>(null) }
    var isMeasuring by remember { mutableStateOf(false) }
    
    val pulseAnimation by animateFloatAsState(
        targetValue = if (isMeasuring) 1.2f else 1f,
        animationSpec = repeatable(
            iterations = AnimationConstants.Infinite,
            animation = tween(500),
            repeatMode = RepeatMode.Reverse
        )
    )
    
    LaunchedEffect(isMeasuring) {
        if (isMeasuring) {
            delay(3000) // Simulate measurement time
            heartRate = (65..85).random()
            isMeasuring = false
        }
    }
    
    Scaffold(
        timeText = { TimeText() },
        positionIndicator = { PositionIndicator() }
    ) {
        Column(
            modifier = Modifier.fillMaxSize(),
            verticalArrangement = Arrangement.Center,
            horizontalAlignment = Alignment.CenterHorizontally
        ) {
            Icon(
                imageVector = Icons.Default.Favorite,
                contentDescription = "Heart",
                tint = Color.Red,
                modifier = Modifier
                    .size(48.dp)
                    .scale(pulseAnimation)
            )
            
            Spacer(modifier = Modifier.height(16.dp))
            
            Text(
                text = when {
                    isMeasuring -> "Measuring..."
                    heartRate == null -> "--"
                    else -> heartRate.toString()
                },
                style = MaterialTheme.typography.display2
            )
            
            Text(
                text = "BPM",
                style = MaterialTheme.typography.caption1
            )
            
            Spacer(modifier = Modifier.height(16.dp))
            
            Button(
                onClick = { isMeasuring = true },
                enabled = !isMeasuring,
                colors = ButtonDefaults.buttonColors(
                    backgroundColor = Color.Red.copy(alpha = 0.2f),
                    contentColor = Color.Red
                )
            ) {
                Text(text = if (isMeasuring) "..." else "Measure")
            }
            
            Spacer(modifier = Modifier.height(8.dp))
            
            TextButton(onClick = { navController.popBackStack() }) {
                Text(text = "Back")
            }
        }
    }
}

6. Workout Tracking Screen

WorkoutScreen.kt:

kotlin
Copy
import androidx.compose.foundation.layout.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.navigation.NavController
import androidx.wear.compose.material.*
import kotlinx.coroutines.delay
import java.text.SimpleDateFormat
import java.util.*

@Composable
fun WorkoutScreen(navController: NavController) {
    var isRunning by remember { mutableStateOf(false) }
    var startTime by remember { mutableStateOf(0L) }
    var currentTime by remember { mutableStateOf("00:00:00") }
    
    LaunchedEffect(isRunning) {
        if (isRunning) {
            startTime = System.currentTimeMillis()
            while (isRunning) {
                val elapsed = System.currentTimeMillis() - startTime
                currentTime = SimpleDateFormat("HH:mm:ss", Locale.getDefault())
                    .apply { timeZone = TimeZone.getTimeZone("UTC") }
                    .format(Date(elapsed))
                delay(1000)
            }
        }
    }
    
    Scaffold(
        timeText = { TimeText() },
        positionIndicator = { PositionIndicator() }
    ) {
        Column(
            modifier = Modifier.fillMaxSize(),
            verticalArrangement = Arrangement.Center,
            horizontalAlignment = Alignment.CenterHorizontally
        ) {
            Text(
                text = currentTime,
                style = MaterialTheme.typography.display2,
                textAlign = TextAlign.Center
            )
            
            Spacer(modifier = Modifier.height(16.dp))
            
            Row(
                modifier = Modifier.fillMaxWidth(),
                horizontalArrangement = Arrangement.SpaceEvenly
            ) {
                Button(
                    onClick = {
                        isRunning = !isRunning
                        if (!isRunning) startTime = 0
                    },
                    colors = ButtonDefaults.buttonColors(
                        backgroundColor = if (isRunning) Color.Red else Color.Green
                    ),
                    modifier = Modifier.size(ButtonDefaults.LargeButtonSize)
                ) {
                    Icon(
                        imageVector = if (isRunning) Icons.Default.Stop else Icons.Default.PlayArrow,
                        contentDescription = if (isRunning) "Stop" else "Start"
                    )
                }
            }
            
            Spacer(modifier = Modifier.height(8.dp))
            
            if (!isRunning && startTime > 0) {
                Text(
                    text = "Workout saved",
                    style = MaterialTheme.typography.caption1,
                    color = Color.Green
                )
            }
            
            Spacer(modifier = Modifier.height(8.dp))
            
            TextButton(onClick = { navController.popBackStack() }) {
                Text(text = "Back")
            }
        }
    }
}

7. Settings Screen with Toggle

SettingsScreen.kt:

kotlin
Copy
import androidx.compose.foundation.layout.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.navigation.NavController
import androidx.wear.compose.material.*

@Composable
fun SettingsScreen(navController: NavController) {
    var notificationsEnabled by remember { mutableStateOf(true) }
    var hapticFeedback by remember { mutableStateOf(false) }
    
    ScalingLazyColumn(
        modifier = Modifier.fillMaxSize(),
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        item {
            ListHeader {
                Text("Settings")
            }
        }
        
        item {
            ToggleChip(
                checked = notificationsEnabled,
                onCheckedChange = { notificationsEnabled = it },
                label = { Text("Notifications") },
                appIcon = {
                    Icon(
                        Icons.Default.Notifications,
                        contentDescription = "Notifications"
                    )
                }
            )
        }
        
        item {
            ToggleChip(
                checked = hapticFeedback,
                onCheckedChange = { hapticFeedback = it },
                label = { Text("Haptic Feedback") },
                appIcon = {
                    Icon(
                        Icons.Default.Vibration,
                        contentDescription = "Haptic Feedback"
                    )
                }
            )
        }
        
        item {
            Button(
                onClick = { navController.popBackStack() },
                modifier = Modifier.padding(top = 16.dp)
            ) {
                Text("Back")
            }
        }
    }
}

Key Features Implemented:

  1. Navigation: Swipe-to-dismiss navigation between screens

  2. Health Metrics: Steps counter and heart rate monitor

  3. Workout Tracking: Stopwatch functionality for workouts

  4. Settings: Configurable options with toggle chips

  5. Animations: Pulse animation for heart rate measurement

  6. Progress Indicators: Circular progress for step goals

  7. Wear OS Components: Proper use of Wear-specific Material components

This implementation provides a solid foundation for  any Wear OS smartwatch application. You can extend it with actual sensor data integration using Health Services API and customize the UI to match  specific design language.

Comments

Popular posts from this blog

Kotlin Math Operations and Functions Overview

Kotlin Strings: Features and Operations Guide

Kotlin Android Program (QCR) Application Codes That Read Text in Photos