Initial commit: Kotlin Multiplatform project setup

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Trey t
2025-11-04 09:15:49 -06:00
commit 78c62cfc52
80 changed files with 3073 additions and 0 deletions

View File

@@ -0,0 +1,100 @@
package com.mycrib.android.ui.screens
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.*
import androidx.compose.material3.*
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun HomeScreen(
onNavigateToResidences: () -> Unit,
onNavigateToTasks: () -> Unit,
onLogout: () -> Unit
) {
Scaffold(
topBar = {
TopAppBar(
title = { Text("myCrib") },
actions = {
IconButton(onClick = onLogout) {
Icon(Icons.Default.ExitToApp, contentDescription = "Logout")
}
}
)
}
) { paddingValues ->
Column(
modifier = Modifier
.fillMaxSize()
.padding(paddingValues)
.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(16.dp)
) {
Card(
modifier = Modifier
.fillMaxWidth()
.clickable { onNavigateToResidences() }
) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(24.dp),
verticalAlignment = Alignment.CenterVertically
) {
Icon(
Icons.Default.Home,
contentDescription = null,
modifier = Modifier.size(48.dp)
)
Spacer(modifier = Modifier.width(16.dp))
Column {
Text(
text = "Residences",
style = MaterialTheme.typography.titleLarge
)
Text(
text = "Manage your properties",
style = MaterialTheme.typography.bodyMedium
)
}
}
}
Card(
modifier = Modifier
.fillMaxWidth()
.clickable { onNavigateToTasks() }
) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(24.dp),
verticalAlignment = Alignment.CenterVertically
) {
Icon(
Icons.Default.CheckCircle,
contentDescription = null,
modifier = Modifier.size(48.dp)
)
Spacer(modifier = Modifier.width(16.dp))
Column {
Text(
text = "Tasks",
style = MaterialTheme.typography.titleLarge
)
Text(
text = "View and manage tasks",
style = MaterialTheme.typography.bodyMedium
)
}
}
}
}
}
}

View File

@@ -0,0 +1,106 @@
package com.mycrib.android.ui.screens
import androidx.compose.foundation.layout.*
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.input.PasswordVisualTransformation
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import com.mycrib.android.viewmodel.AuthViewModel
import com.mycrib.shared.network.ApiResult
@Composable
fun LoginScreen(
onLoginSuccess: () -> Unit,
onNavigateToRegister: () -> Unit,
viewModel: AuthViewModel = viewModel { AuthViewModel() }
) {
var username by remember { mutableStateOf("") }
var password by remember { mutableStateOf("") }
val loginState by viewModel.loginState.collectAsState()
// Handle login state changes
LaunchedEffect(loginState) {
when (loginState) {
is ApiResult.Success -> {
onLoginSuccess()
}
else -> {}
}
}
val errorMessage = when (loginState) {
is ApiResult.Error -> (loginState as ApiResult.Error).message
else -> ""
}
val isLoading = loginState is ApiResult.Loading
Column(
modifier = Modifier
.fillMaxSize()
.padding(16.dp),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
Text(
text = "myCrib",
style = MaterialTheme.typography.headlineLarge,
modifier = Modifier.padding(bottom = 32.dp)
)
OutlinedTextField(
value = username,
onValueChange = { username = it },
label = { Text("Username") },
modifier = Modifier.fillMaxWidth(),
singleLine = true
)
Spacer(modifier = Modifier.height(16.dp))
OutlinedTextField(
value = password,
onValueChange = { password = it },
label = { Text("Password") },
modifier = Modifier.fillMaxWidth(),
singleLine = true,
visualTransformation = PasswordVisualTransformation()
)
if (errorMessage.isNotEmpty()) {
Text(
text = errorMessage,
color = MaterialTheme.colorScheme.error,
modifier = Modifier.padding(top = 8.dp)
)
}
Spacer(modifier = Modifier.height(24.dp))
Button(
onClick = {
viewModel.login(username, password)
},
modifier = Modifier.fillMaxWidth(),
enabled = username.isNotEmpty() && password.isNotEmpty()
) {
if (isLoading) {
CircularProgressIndicator(
modifier = Modifier.size(24.dp),
color = MaterialTheme.colorScheme.onPrimary
)
} else {
Text("Login")
}
}
Spacer(modifier = Modifier.height(16.dp))
TextButton(onClick = onNavigateToRegister) {
Text("Don't have an account? Register")
}
}
}

View File

@@ -0,0 +1,125 @@
package com.mycrib.android.ui.screens
import androidx.compose.foundation.layout.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.input.PasswordVisualTransformation
import androidx.compose.ui.unit.dp
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun RegisterScreen(
onRegisterSuccess: () -> Unit,
onNavigateBack: () -> Unit
) {
var username by remember { mutableStateOf("") }
var email by remember { mutableStateOf("") }
var password by remember { mutableStateOf("") }
var confirmPassword by remember { mutableStateOf("") }
var errorMessage by remember { mutableStateOf("") }
var isLoading by remember { mutableStateOf(false) }
Scaffold(
topBar = {
TopAppBar(
title = { Text("Register") },
navigationIcon = {
IconButton(onClick = onNavigateBack) {
Icon(Icons.Default.ArrowBack, contentDescription = "Back")
}
}
)
}
) { paddingValues ->
Column(
modifier = Modifier
.fillMaxSize()
.padding(paddingValues)
.padding(16.dp),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
OutlinedTextField(
value = username,
onValueChange = { username = it },
label = { Text("Username") },
modifier = Modifier.fillMaxWidth(),
singleLine = true
)
Spacer(modifier = Modifier.height(16.dp))
OutlinedTextField(
value = email,
onValueChange = { email = it },
label = { Text("Email") },
modifier = Modifier.fillMaxWidth(),
singleLine = true
)
Spacer(modifier = Modifier.height(16.dp))
OutlinedTextField(
value = password,
onValueChange = { password = it },
label = { Text("Password") },
modifier = Modifier.fillMaxWidth(),
singleLine = true,
visualTransformation = PasswordVisualTransformation()
)
Spacer(modifier = Modifier.height(16.dp))
OutlinedTextField(
value = confirmPassword,
onValueChange = { confirmPassword = it },
label = { Text("Confirm Password") },
modifier = Modifier.fillMaxWidth(),
singleLine = true,
visualTransformation = PasswordVisualTransformation()
)
if (errorMessage.isNotEmpty()) {
Text(
text = errorMessage,
color = MaterialTheme.colorScheme.error,
modifier = Modifier.padding(top = 8.dp)
)
}
Spacer(modifier = Modifier.height(24.dp))
Button(
onClick = {
when {
password != confirmPassword -> {
errorMessage = "Passwords do not match"
}
else -> {
isLoading = true
errorMessage = ""
// TODO: Call API
onRegisterSuccess()
}
}
},
modifier = Modifier.fillMaxWidth(),
enabled = username.isNotEmpty() && email.isNotEmpty() &&
password.isNotEmpty() && !isLoading
) {
if (isLoading) {
CircularProgressIndicator(
modifier = Modifier.size(24.dp),
color = MaterialTheme.colorScheme.onPrimary
)
} else {
Text("Register")
}
}
}
}
}

View File

@@ -0,0 +1,39 @@
package com.mycrib.android.ui.screens
import androidx.compose.foundation.layout.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.material3.*
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun ResidenceDetailScreen(
residenceId: Int,
onNavigateBack: () -> Unit
) {
Scaffold(
topBar = {
TopAppBar(
title = { Text("Residence Details") },
navigationIcon = {
IconButton(onClick = onNavigateBack) {
Icon(Icons.Default.ArrowBack, contentDescription = "Back")
}
}
)
}
) { paddingValues ->
Column(
modifier = Modifier
.fillMaxSize()
.padding(paddingValues)
.padding(16.dp)
) {
Text("Residence ID: $residenceId")
Text("Details coming soon!")
}
}
}

View File

@@ -0,0 +1,82 @@
package com.mycrib.android.ui.screens
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.*
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun ResidencesScreen(
onNavigateBack: () -> Unit,
onResidenceClick: (Int) -> Unit
) {
// TODO: Load residences from API
val residences = remember { emptyList<String>() }
Scaffold(
topBar = {
TopAppBar(
title = { Text("Residences") },
navigationIcon = {
IconButton(onClick = onNavigateBack) {
Icon(Icons.Default.ArrowBack, contentDescription = "Back")
}
},
actions = {
IconButton(onClick = { /* TODO: Add residence */ }) {
Icon(Icons.Default.Add, contentDescription = "Add")
}
}
)
}
) { paddingValues ->
if (residences.isEmpty()) {
Box(
modifier = Modifier
.fillMaxSize()
.padding(paddingValues),
contentAlignment = androidx.compose.ui.Alignment.Center
) {
Text("No residences yet. Add one to get started!")
}
} else {
LazyColumn(
modifier = Modifier
.fillMaxSize()
.padding(paddingValues),
contentPadding = PaddingValues(16.dp),
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
items(residences) { residence ->
Card(
modifier = Modifier
.fillMaxWidth()
.clickable { onResidenceClick(0) }
) {
Column(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp)
) {
Text(
text = "Residence Name",
style = MaterialTheme.typography.titleMedium
)
Text(
text = "Address",
style = MaterialTheme.typography.bodyMedium
)
}
}
}
}
}
}
}

View File

@@ -0,0 +1,37 @@
package com.mycrib.android.ui.screens
import androidx.compose.foundation.layout.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.material3.*
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun TasksScreen(
onNavigateBack: () -> Unit
) {
Scaffold(
topBar = {
TopAppBar(
title = { Text("Tasks") },
navigationIcon = {
IconButton(onClick = onNavigateBack) {
Icon(Icons.Default.ArrowBack, contentDescription = "Back")
}
}
)
}
) { paddingValues ->
Box(
modifier = Modifier
.fillMaxSize()
.padding(paddingValues),
contentAlignment = androidx.compose.ui.Alignment.Center
) {
Text("Tasks coming soon!")
}
}
}

View File

@@ -0,0 +1,31 @@
package com.mycrib.android.ui.theme
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.material3.*
import androidx.compose.runtime.Composable
import androidx.compose.ui.graphics.Color
private val DarkColorScheme = darkColorScheme(
primary = Color(0xFF6200EE),
secondary = Color(0xFF03DAC6),
tertiary = Color(0xFF3700B3)
)
private val LightColorScheme = lightColorScheme(
primary = Color(0xFF6200EE),
secondary = Color(0xFF03DAC6),
tertiary = Color(0xFF3700B3)
)
@Composable
fun MyCribTheme(
darkTheme: Boolean = isSystemInDarkTheme(),
content: @Composable () -> Unit
) {
val colorScheme = if (darkTheme) DarkColorScheme else LightColorScheme
MaterialTheme(
colorScheme = colorScheme,
content = content
)
}