Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
*.iml
.gradle
/local.properties
/.idea/caches
/.idea/libraries
/.idea/modules.xml
/.idea/workspace.xml
/.idea/navEditor.xml
/.idea/assetWizardSettings.xml
.DS_Store
/build
/app/build
/captures
.externalNativeBuild
.cxx
local.properties
91 changes: 91 additions & 0 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
plugins {
id("com.android.application")
id("org.jetbrains.kotlin.android")
id("com.google.dagger.hilt.android")
id("com.google.devtools.ksp")
id("org.jetbrains.kotlin.plugin.serialization")
}

android {
namespace = "com.soulstice.app"
compileSdk = 34

defaultConfig {
applicationId = "com.soulstice.app"
minSdk = 24
targetSdk = 34
versionCode = 1
versionName = "1.0"

testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables {
useSupportLibrary = true
}
}

buildTypes {
release {
isMinifyEnabled = false
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_21
targetCompatibility = JavaVersion.VERSION_21
}
kotlinOptions {
jvmTarget = "21"
}
buildFeatures {
compose = true
}
composeOptions {
kotlinCompilerExtensionVersion = "1.5.10"
}
packaging {
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:2024.02.00"))
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")
implementation("com.google.android.material:material:1.11.0")

// Hilt
implementation("com.google.dagger:hilt-android:2.50")
ksp("com.google.dagger:hilt-compiler:2.50")
implementation("androidx.hilt:hilt-navigation-compose:1.1.0")

// Room
val roomVersion = "2.6.1"
implementation("androidx.room:room-runtime:$roomVersion")
implementation("androidx.room:room-ktx:$roomVersion")
ksp("androidx.room:room-compiler:$roomVersion")

// Navigation
implementation("androidx.navigation:navigation-compose:2.7.7")

// DataStore
implementation("androidx.datastore:datastore-preferences:1.0.0")

// Serialization
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.3")

// Testing
testImplementation("junit:junit:4.13.2")
androidTestImplementation("androidx.test.ext:junit:1.1.5")
androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
}
18 changes: 18 additions & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application
android:name=".SoulsticeApp"
android:allowBackup="true"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/Theme.Soulstice">
<activity
android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
211 changes: 211 additions & 0 deletions app/src/main/kotlin/com/soulstice/app/MainActivity.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,211 @@
package com.soulstice.app

import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.isSystemInDarkTheme
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.*
import kotlinx.coroutines.launch
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.navigation.NavDestination.Companion.hierarchy
import androidx.navigation.NavGraph.Companion.findStartDestination
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.currentBackStackEntryAsState
import androidx.navigation.compose.rememberNavController
import androidx.hilt.navigation.compose.hiltViewModel
import com.soulstice.app.ui.screens.*
import com.soulstice.app.ui.theme.*
import com.soulstice.app.ui.viewmodel.InboxViewModel
import com.soulstice.app.ui.viewmodel.MainViewModel
import com.soulstice.app.ui.viewmodel.TaskViewModel
import dagger.hilt.android.AndroidEntryPoint

@AndroidEntryPoint
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
val viewModel: MainViewModel = hiltViewModel()
val theme by viewModel.theme.collectAsState(initial = "Light")
val isDark = when (theme) {
"Dark" -> true
"Light" -> false
else -> isSystemInDarkTheme()
}
SoulsticeTheme(darkTheme = isDark) {
MainScreen()
}
}
}
}

sealed class Screen(val route: String, val label: String, val icon: ImageVector) {
object Dashboard : Screen("dashboard", "Cockpit", Icons.Default.Home)
object Inbox : Screen("inbox", "Inbox", Icons.Default.Inbox)
object Projects : Screen("projects", "Projects", Icons.Default.GridView)
object CreativeStudio : Screen("creative_studio", "Creative Studio", Icons.Default.Brush)
object BusinessHub : Screen("business_hub", "Business Hub", Icons.Default.BusinessCenter)
object KnowledgeBase : Screen("knowledge_base", "Knowledge Base", Icons.Default.MenuBook)
object Journal : Screen("journal", "Journal", Icons.Default.Edit)
object Habits : Screen("habits", "Habits", Icons.Default.AutoGraph)
object UserGuide : Screen("user_guide", "User Guide", Icons.Default.HelpCenter)
object Focus : Screen("focus", "Focus Timer", Icons.Default.Timer)
object Settings : Screen("settings", "Settings", Icons.Default.Settings)
object Review : Screen("review", "Daily Review", Icons.Default.AutoAwesome)
object ProjectDetail : Screen("project_detail/{projectId}", "Project Detail", Icons.Default.GridView)
}

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun MainScreen(
taskViewModel: TaskViewModel = hiltViewModel(),
inboxViewModel: InboxViewModel = hiltViewModel()
) {
val navController = rememberNavController()
val drawerState = rememberDrawerState(initialValue = DrawerValue.Closed)
val scope = rememberCoroutineScope()
var showQuickCapture by remember { mutableStateOf(false) }

val items = listOf(
Screen.Dashboard,
Screen.Inbox,
Screen.Projects,
Screen.CreativeStudio,
Screen.BusinessHub,
Screen.KnowledgeBase,
Screen.Journal,
Screen.Habits,
Screen.UserGuide,
Screen.Focus,
Screen.Settings,
Screen.Review
)

val navBackStackEntry by navController.currentBackStackEntryAsState()
val currentDestination = navBackStackEntry?.destination

if (showQuickCapture) {
QuickCaptureDialog(
onDismiss = { showQuickCapture = false },
onCapture = { text ->
inboxViewModel.addItem(text)
showQuickCapture = false
}
)
}

ModalNavigationDrawer(
drawerState = drawerState,
drawerContent = {
ModalDrawerSheet {
Spacer(modifier = Modifier.height(16.dp))
Text(
"Soulstice",
modifier = Modifier.padding(horizontal = 28.dp, vertical = 16.dp),
style = MaterialTheme.typography.headlineMedium,
color = Taupe900
)
items.forEach { screen ->
NavigationDrawerItem(
icon = { Icon(screen.icon, contentDescription = null) },
label = { Text(screen.label) },
selected = currentDestination?.hierarchy?.any { it.route == screen.route } == true,
onClick = {
navController.navigate(screen.route) {
popUpTo(navController.graph.findStartDestination().id) {
saveState = true
}
launchSingleTop = true
restoreState = true
}
scope.launch { drawerState.close() }
},
modifier = Modifier.padding(NavigationDrawerItemDefaults.ItemPadding)
)
}
}
}
) {
Scaffold(
floatingActionButton = {
FloatingActionButton(
onClick = { showQuickCapture = true },
containerColor = Sage600,
contentColor = androidx.compose.ui.graphics.Color.White
) {
Icon(Icons.Default.Add, contentDescription = "Quick Capture")
}
},
topBar = {
CenterAlignedTopAppBar(
title = { Text("Soulstice") },
navigationIcon = {
IconButton(onClick = {
scope.launch { drawerState.open() }
}) {
Icon(Icons.Default.Menu, contentDescription = "Menu")
}
}
)
}
) { innerPadding ->
NavHost(navController, startDestination = Screen.Dashboard.route, Modifier.padding(innerPadding)) {
composable(Screen.Dashboard.route) { DashboardScreen() }
composable(Screen.Inbox.route) { InboxScreen() }
composable(Screen.Projects.route) {
ProjectsScreen(onProjectClick = { projectId ->
navController.navigate("project_detail/$projectId")
})
}
composable(Screen.ProjectDetail.route) { backStackEntry ->
val projectId = backStackEntry.arguments?.getString("projectId") ?: ""
ProjectDetailScreen(projectId = projectId)
}
composable(Screen.CreativeStudio.route) { CreativeStudioScreen() }
composable(Screen.BusinessHub.route) { BusinessHubScreen() }
composable(Screen.KnowledgeBase.route) { KnowledgeBaseScreen() }
composable(Screen.Journal.route) { JournalScreen() }
composable(Screen.Habits.route) { HabitsScreen() }
composable(Screen.UserGuide.route) { UserGuideScreen() }
composable(Screen.Focus.route) { FocusScreen() }
composable(Screen.Settings.route) { SettingsScreen() }
composable(Screen.Review.route) { ReviewScreen() }
}
}
}
}

@Composable
fun QuickCaptureDialog(onDismiss: () -> Unit, onCapture: (String) -> Unit) {
var text by remember { mutableStateOf("") }

AlertDialog(
onDismissRequest = onDismiss,
title = { Text("Quick Capture") },
text = {
TextField(
value = text,
onValueChange = { text = it },
placeholder = { Text("What's on your mind?") },
modifier = Modifier.fillMaxWidth()
)
},
confirmButton = {
Button(onClick = { if (text.isNotBlank()) onCapture(text) }) {
Text("Capture")
}
},
dismissButton = {
TextButton(onClick = onDismiss) {
Text("Cancel")
}
}
)
}
7 changes: 7 additions & 0 deletions app/src/main/kotlin/com/soulstice/app/SoulsticeApp.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.soulstice.app

import android.app.Application
import dagger.hilt.android.HiltAndroidApp

@HiltAndroidApp
class SoulsticeApp : Application()
25 changes: 25 additions & 0 deletions app/src/main/kotlin/com/soulstice/app/data/local/AppDatabase.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package com.soulstice.app.data.local

import androidx.room.Database
import androidx.room.RoomDatabase
import com.soulstice.app.data.local.dao.SoulsticeDao
import com.soulstice.app.data.local.entities.*

@Database(
entities = [
Task::class,
Project::class,
Habit::class,
HabitCompletion::class,
Resource::class,
Client::class,
JournalEntry::class,
InboxItem::class,
FocusSession::class
],
version = 4,
exportSchema = false
)
abstract class AppDatabase : RoomDatabase() {
abstract fun soulsticeDao(): SoulsticeDao
}
Loading