// MainActivity.kt
package com.paynesland.fitness
import android.Manifest
import android.content.pm.PackageManager
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.result.contract.ActivityResultContracts
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.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.core.content.ContextCompat
import com.paynesland.fitness.ui.theme.PaynesLandFitnessTheme
class MainActivity : ComponentActivity() {
private val requestPermissionLauncher = registerForActivityResult(
ActivityResultContracts.RequestMultiplePermissions()
) { permissions ->
// Handle permission results
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Request necessary permissions
requestPermissions()
setContent {
PaynesLandFitnessTheme {
FitnessApp()
}
}
}
private fun requestPermissions() {
val permissions = arrayOf(
Manifest.permission.CAMERA,
Manifest.permission.BLUETOOTH,
Manifest.permission.BLUETOOTH_ADMIN,
Manifest.permission.RECORD_AUDIO
)
val permissionsToRequest = permissions.filter {
ContextCompat.checkSelfPermission(this, it) != PackageManager.PERMISSION_GRANTED
}
if (permissionsToRequest.isNotEmpty()) {
requestPermissionLauncher.launch(permissionsToRequest.toTypedArray())
}
}
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun FitnessApp() {
var selectedDay by remember { mutableStateOf(DayOfWeek.MONDAY) }
var userProfile by remember { mutableStateOf(UserProfile()) }
var showProfile by remember { mutableStateOf(false) }
Column(
modifier = Modifier
.fillMaxSize()
.padding(16.dp)
) {
// Header
Card(
modifier = Modifier.fillMaxWidth(),
colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.primary)
) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
Column {
Text(
text = "PaynesLand Fitness",
fontSize = 24.sp,
fontWeight = FontWeight.Bold,
color = Color.White
)
Text(
text = "Your Personal Trainer",
fontSize = 14.sp,
color = Color.White.copy(alpha = 0.8f)
)
}
IconButton(onClick = { showProfile = true }) {
Icon(
imageVector = Icons.Default.Person,
contentDescription = "Profile",
tint = Color.White
)
}
}
}
Spacer(modifier = Modifier.height(16.dp))
// Day Selection
DaySelector(
selectedDay = selectedDay,
onDaySelected = { selectedDay = it }
)
Spacer(modifier = Modifier.height(16.dp))
// Main Content
DayContent(day = selectedDay, userProfile = userProfile)
// Profile Dialog
if (showProfile) {
ProfileDialog(
userProfile = userProfile,
onProfileUpdate = { userProfile = it },
onDismiss = { showProfile = false }
)
}
}
}
@Composable
fun DaySelector(selectedDay: DayOfWeek, onDaySelected: (DayOfWeek) -> Unit) {
LazyColumn {
items(DayOfWeek.values()) { day ->
Card(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 4.dp),
colors = CardDefaults.cardColors(
containerColor = if (selectedDay == day)
MaterialTheme.colorScheme.primaryContainer
else MaterialTheme.colorScheme.surface
),
onClick = { onDaySelected(day) }
) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp),
verticalAlignment = Alignment.CenterVertically
) {
Icon(
imageVector = day.icon,
contentDescription = day.displayName,
tint = if (selectedDay == day)
MaterialTheme.colorScheme.primary
else MaterialTheme.colorScheme.onSurface
)
Spacer(modifier = Modifier.width(12.dp))
Text(
text = day.displayName,
fontWeight = if (selectedDay == day) FontWeight.Bold else FontWeight.Normal
)
}
}
}
}
}
@Composable
fun DayContent(day: DayOfWeek, userProfile: UserProfile) {
var currentExercise by remember { mutableStateOf<Exercise?>(null) }
var currentMeal by remember { mutableStateOf<Meal?>(null) }
Column {
Text(
text = "${day.displayName} Workout & Nutrition",
fontSize = 20.sp,
fontWeight = FontWeight.Bold,
modifier = Modifier.padding(bottom = 16.dp)
)
// Exercise Section
Card(
modifier = Modifier.fillMaxWidth(),
colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.secondaryContainer)
) {
Column(modifier = Modifier.padding(16.dp)) {
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
Text(
text = "Today's Exercise",
fontSize = 18.sp,
fontWeight = FontWeight.SemiBold
)
Button(
onClick = { currentExercise = ExerciseGenerator.getRandomExercise(userProfile.fitnessLevel) }
) {
Icon(Icons.Default.Refresh, contentDescription = "Generate")
Spacer(modifier = Modifier.width(8.dp))
Text("Generate")
}
}
Spacer(modifier = Modifier.height(12.dp))
currentExercise?.let { exercise ->
ExerciseCard(exercise = exercise)
} ?: Text(
text = "Tap 'Generate' to get your workout!",
color = MaterialTheme.colorScheme.onSecondaryContainer.copy(alpha = 0.7f)
)
}
}
Spacer(modifier = Modifier.height(16.dp))
// Meal Section
Card(
modifier = Modifier.fillMaxWidth(),
colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.tertiaryContainer)
) {
Column(modifier = Modifier.padding(16.dp)) {
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
Text(
text = "Meal Suggestion",
fontSize = 18.sp,
fontWeight = FontWeight.SemiBold
)
Button(
onClick = { currentMeal = MealGenerator.getRandomMeal(userProfile.dietaryPreference) }
) {
Icon(Icons.Default.Restaurant, contentDescription = "Generate")
Spacer(modifier = Modifier.width(8.dp))
Text("Generate")
}
}
Spacer(modifier = Modifier.height(12.dp))
currentMeal?.let { meal ->
MealCard(meal = meal)
} ?: Text(
text = "Tap 'Generate' to get meal ideas!",
color = MaterialTheme.colorScheme.onTertiaryContainer.copy(alpha = 0.7f)
)
}
}
}
}
@Composable
fun ExerciseCard(exercise: Exercise) {
Card(
modifier = Modifier.fillMaxWidth(),
colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surface)
) {
Column(modifier = Modifier.padding(16.dp)) {
Row(verticalAlignment = Alignment.CenterVertically) {
Icon(
imageVector = Icons.Default.FitnessCenter,
contentDescription = "Exercise",
tint = MaterialTheme.colorScheme.primary
)
Spacer(modifier = Modifier.width(8.dp))
Text(
text = exercise.name,
fontSize = 16.sp,
fontWeight = FontWeight.Bold
)
}
Spacer(modifier = Modifier.height(8.dp))
Text(
text = "Duration: ${exercise.duration}",
color = MaterialTheme.colorScheme.primary
)
Spacer(modifier = Modifier.height(8.dp))
Text(
text = exercise.instructions,
fontSize = 14.sp,
lineHeight = 20.sp
)
}
}
}
@Composable
fun MealCard(meal: Meal) {
Card(
modifier = Modifier.fillMaxWidth(),
colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surface)
) {
Column(modifier = Modifier.padding(16.dp)) {
Row(verticalAlignment = Alignment.CenterVertically) {
Icon(
imageVector = Icons.Default.Restaurant,
contentDescription = "Meal",
tint = MaterialTheme.colorScheme.primary
)
Spacer(modifier = Modifier.width(8.dp))
Text(
text = meal.name,
fontSize = 16.sp,
fontWeight = FontWeight.Bold
)
}
Spacer(modifier = Modifier.height(8.dp))
Text(
text = "Calories: ${meal.calories} | Prep: ${meal.prepTime}",
color = MaterialTheme.colorScheme.primary
)
Spacer(modifier = Modifier.height(8.dp))
Text(
text = "Ingredients:",
fontWeight = FontWeight.SemiBold
)
Text(
text = meal.ingredients.joinToString(", "),
fontSize = 14.sp
)
Spacer(modifier = Modifier.height(8.dp))
Text(
text = "Instructions:",
fontWeight = FontWeight.SemiBold
)
Text(
text = meal.instructions,
fontSize = 14.sp,
lineHeight = 20.sp
)
}
}
}
@Composable
fun ProfileDialog(
userProfile: UserProfile,
onProfileUpdate: (UserProfile) -> Unit,
onDismiss: () -> Unit
) {
var name by remember { mutableStateOf(userProfile.name) }
var age by remember { mutableStateOf(userProfile.age.toString()) }
var weight by remember { mutableStateOf(userProfile.weight.toString()) }
var height by remember { mutableStateOf(userProfile.height.toString()) }
var fitnessLevel by remember { mutableStateOf(userProfile.fitnessLevel) }
var dietaryPreference by remember { mutableStateOf(userProfile.dietaryPreference) }
AlertDialog(
onDismissRequest = onDismiss,
title = { Text("Fitness Profile") },
text = {
Column {
OutlinedTextField(
value = name,
onValueChange = { name = it },
label = { Text("Name") },
modifier = Modifier.fillMaxWidth()
)
Spacer(modifier = Modifier.height(8.dp))
OutlinedTextField(
value = age,
onValueChange = { age = it },
label = { Text("Age") },
modifier = Modifier.fillMaxWidth()
)
Spacer(modifier = Modifier.height(8.dp))
OutlinedTextField(
value = weight,
onValueChange = { weight = it },
label = { Text("Weight (lbs)") },
modifier = Modifier.fillMaxWidth()
)
Spacer(modifier = Modifier.height(8.dp))
OutlinedTextField(
value = height,
onValueChange = { height = it },
label = { Text("Height (inches)") },
modifier = Modifier.fillMaxWidth()
)
}
},
confirmButton = {
TextButton(
onClick = {
onProfileUpdate(
UserProfile(
name = name,
age = age.toIntOrNull() ?: 25,
weight = weight.toFloatOrNull() ?: 150f,
height = height.toFloatOrNull() ?: 68f,
fitnessLevel = fitnessLevel,
dietaryPreference = dietaryPreference
)
)
onDismiss()
}
) {
Text("Save")
}
},
dismissButton = {
TextButton(onClick = onDismiss) {
Text("Cancel")
}
}
)
}
// Data Classes
data class UserProfile(
val name: String = "Fitness Enthusiast",
val age: Int = 25,
val weight: Float = 150f,
val height: Float = 68f,
val fitnessLevel: FitnessLevel = FitnessLevel.INTERMEDIATE,
val dietaryPreference: DietaryPreference = DietaryPreference.BALANCED
)
enum class FitnessLevel {
BEGINNER, INTERMEDIATE, ADVANCED
}
enum class DietaryPreference {
BALANCED, VEGETARIAN, VEGAN, KETO, PALEO
}
enum class DayOfWeek(val displayName: String, val icon: ImageVector) {
MONDAY("Monday", Icons.Default.DirectionsRun),
TUESDAY("Tuesday", Icons.Default.FitnessCenter),
WEDNESDAY("Wednesday", Icons.Default.Pool),
THURSDAY("Thursday", Icons.Default.SportsGymnastics),
FRIDAY("Friday", Icons.Default.Sports),
SATURDAY("Saturday", Icons.Default.Hiking),
SUNDAY("Sunday", Icons.Default.SelfImprovement)
}
data class Exercise(
val name: String,
val duration: String,
val instructions: String,
val difficulty: FitnessLevel
)
data class Meal(
val name: String,
val calories: Int,
val prepTime: String,
val ingredients: List<String>,
val instructions: String,
val dietaryType: DietaryPreference
)
// Exercise Generator
object ExerciseGenerator {
private val exercises = listOf(
Exercise(
"Push-ups",
"3 sets of 10-15 reps",
"Start in plank position. Lower your body until chest nearly touches the floor. Push back up to starting position. Keep your core tight throughout the movement.",
FitnessLevel.BEGINNER
),
Exercise(
"Burpees",
"3 sets of 8-12 reps",
"Start standing. Drop into squat, place hands on floor, jump feet back into plank. Do a push-up, jump feet forward, then jump up with arms overhead.",
FitnessLevel.INTERMEDIATE
),
Exercise(
"Mountain Climbers",
"3 sets of 30 seconds",
"Start in plank position. Alternate bringing knees to chest rapidly while maintaining plank form. Keep hips level and core engaged.",
FitnessLevel.INTERMEDIATE
),
Exercise(
"Squats",
"3 sets of 15-20 reps",
"Stand with feet shoulder-width apart. Lower your body as if sitting back into a chair. Keep chest up, knees behind toes. Return to starting position.",
FitnessLevel.BEGINNER
),
Exercise(
"Plank",
"3 sets of 30-60 seconds",
"Hold a push-up position with forearms on ground. Keep body straight from head to heels. Engage core and breathe steadily.",
FitnessLevel.BEGINNER
),
Exercise(
"High-Intensity Interval Training",
"20 minutes",
"Alternate between 30 seconds high-intensity exercise and 30 seconds rest. Include jumping jacks, burpees, mountain climbers, and squat jumps.",
FitnessLevel.ADVANCED
),
Exercise(
"Lunges",
"3 sets of 12 per leg",
"Step forward with one leg, lowering hips until both knees are bent at 90 degrees. Push back to starting position. Alternate legs.",
FitnessLevel.INTERMEDIATE
),
Exercise(
"Jumping Jacks",
"3 sets of 20-30 reps",
"Start with feet together, arms at sides. Jump while spreading legs shoulder-width apart and raising arms overhead. Jump back to starting position.",
FitnessLevel.BEGINNER
)
)
fun getRandomExercise(fitnessLevel: FitnessLevel): Exercise {
val filteredExercises = exercises.filter { it.difficulty <= fitnessLevel }
return filteredExercises.random()
}
}
// Meal Generator
object MealGenerator {
private val meals = listOf(
Meal(
"Grilled Chicken Salad",
450,
"15 minutes",
listOf("Chicken breast", "Mixed greens", "Cherry tomatoes", "Cucumber", "Olive oil", "Lemon"),
"Grill chicken breast and slice. Combine greens, tomatoes, cucumber in bowl. Top with chicken and dress with olive oil and lemon.",
DietaryPreference.BALANCED
),
Meal(
"Protein Smoothie Bowl",
320,
"10 minutes",
listOf("Protein powder", "Banana", "Berries", "Almond milk", "Granola", "Chia seeds"),
"Blend protein powder, half banana, and almond milk. Pour into bowl, top with berries, granola, chia seeds, and remaining banana slices.",
DietaryPreference.BALANCED
),
Meal(
"Quinoa Buddha Bowl",
380,
"25 minutes",
listOf("Quinoa", "Sweet potato", "Chickpeas", "Avocado", "Spinach", "Tahini"),
"Cook quinoa. Roast diced sweet potato and chickpeas. Combine with spinach and avocado. Drizzle with tahini dressing.",
DietaryPreference.VEGETARIAN
),
Meal(
"Salmon with Vegetables",
520,
"20 minutes",
listOf("Salmon fillet", "Broccoli", "Bell peppers", "Brown rice", "Olive oil", "Garlic"),
"Bake salmon with olive oil and garlic. Steam broccoli and sauté bell peppers. Serve over brown rice.",
DietaryPreference.BALANCED
),
Meal(
"Vegan Lentil Curry",
340,
"30 minutes",
listOf("Red lentils", "Coconut milk", "Curry powder", "Onion", "Garlic", "Spinach"),
"Sauté onion and garlic. Add lentils, coconut milk, and curry powder. Simmer 20 minutes. Stir in spinach until wilted.",
DietaryPreference.VEGAN
),
Meal(
"Keto Avocado Egg Salad",
420,
"10 minutes",
listOf("Hard-boiled eggs", "Avocado", "Bacon", "Mayo", "Lettuce", "Cheese"),
"Mash avocado and eggs together. Add crumbled bacon and mayo. Serve over lettuce with cheese.",
DietaryPreference.KETO
),
Meal(
"Paleo Beef Stir-fry",
480,
"15 minutes",
listOf("Beef strips", "Mixed vegetables", "Coconut oil", "Ginger", "Garlic", "Coconut aminos"),
"Heat coconut oil in pan. Stir-fry beef until browned. Add vegetables, ginger, garlic. Cook until tender. Season with coconut aminos.",
DietaryPreference.PALEO
)
)
fun getRandomMeal(dietaryPreference: DietaryPreference): Meal {
val filteredMeals = meals.filter {
it.dietaryType == dietaryPreference || it.dietaryType == DietaryPreference.BALANCED
}
return filteredMeals.random()
}
}
// Theme
// ui/theme/Theme.kt
package com.paynesland.fitness.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),
background = Color(0xFF121212),
surface = Color(0xFF1E1E1E),
onPrimary = Color.White,
onSecondary = Color.Black,
onTertiary = Color.White,
onBackground = Color.White,
onSurface = Color.White,
)
private val LightColorScheme = lightColorScheme(
primary = Color(0xFF6200EE),
secondary = Color(0xFF03DAC6),
tertiary = Color(0xFF3700B3),
background = Color(0xFFFFFBFE),
surface = Color(0xFFFFFBFE),
onPrimary = Color.White,
onSecondary = Color.Black,
onTertiary = Color.White,
onBackground = Color(0xFF1C1B1F),
onSurface = Color(0xFF1C1B1F),
)
@Composable
fun PaynesLandFitnessTheme(
darkTheme: Boolean = isSystemInDarkTheme(),
content: @Composable () -> Unit
) {
val colorScheme = when {
darkTheme -> DarkColorScheme
else -> LightColorScheme
}
MaterialTheme(
colorScheme = colorScheme,
typography = Typography(),
content = content
)
}
// AndroidManifest.xml
/*
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
<application
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.PaynesLandFitness"
tools:targetApi="31">
<activity
android:name=".MainActivity"
android:exported="true"
android:theme="@style/Theme.PaynesLandFitness">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
*/
// build.gradle (Module: app)
/*
plugins {
id 'com.android.application'
id 'org.jetbrains.kotlin.android'
}
android {
namespace 'com.paynesland.fitness'
compileSdk 34
defaultConfig {
applicationId "com.paynesland.fitness"
minSdk 24
targetSdk 34
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables {
useSupportLibrary true
}
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
buildFeatures {
compose true
}
composeOptions {
kotlinCompilerExtensionVersion '1.5.4'
}
packagingOptions {
resources {
excludes += '/META-INF/{AL2.0,LGPL2.1}'
}
}
}
dependencies {
implementation 'androidx.core:core-ktx:1.12.0'
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.7.0'
implementation 'androidx.activity:activity-compose:1.8.2'
implementation platform('androidx.compose:compose-bom:2023.10.01')
implementation 'androidx.compose.ui:ui'
implementation 'androidx.compose.ui:ui-graphics'
implementation 'androidx.compose.ui:ui-tooling-preview'
implementation 'androidx.compose.material3:material3'
implementation 'androidx.compose.material:material-icons-extended'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
androidTestImplementation platform('androidx.compose:compose-bom:2023.10.01')
androidTestImplementation 'androidx.compose.ui:ui-test-junit4'
debugImplementation 'androidx.compose.ui:ui-tooling'
debugImplementation 'androidx.compose.ui:ui-test-manifest'
}
*/