commit 8d03328ec248d3b3528f7f8579e7db20981209d2 Author: Ahmar Rafly <202310715320@mhs.ubharajaya.ac.id> Date: Wed Nov 12 15:49:53 2025 +0700 Push Awal diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..aa724b7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,15 @@ +*.iml +.gradle +/local.properties +/.idea/caches +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +/.idea/navEditor.xml +/.idea/assetWizardSettings.xml +.DS_Store +/build +/captures +.externalNativeBuild +.cxx +local.properties diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..26d3352 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,3 @@ +# Default ignored files +/shelf/ +/workspace.xml diff --git a/.idea/AndroidProjectSystem.xml b/.idea/AndroidProjectSystem.xml new file mode 100644 index 0000000..4a53bee --- /dev/null +++ b/.idea/AndroidProjectSystem.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/.idea/compiler.xml b/.idea/compiler.xml new file mode 100644 index 0000000..b86273d --- /dev/null +++ b/.idea/compiler.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/deploymentTargetSelector.xml b/.idea/deploymentTargetSelector.xml new file mode 100644 index 0000000..85ee642 --- /dev/null +++ b/.idea/deploymentTargetSelector.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/deviceManager.xml b/.idea/deviceManager.xml new file mode 100644 index 0000000..91f9558 --- /dev/null +++ b/.idea/deviceManager.xml @@ -0,0 +1,13 @@ + + + + + + \ No newline at end of file diff --git a/.idea/gradle.xml b/.idea/gradle.xml new file mode 100644 index 0000000..639c779 --- /dev/null +++ b/.idea/gradle.xml @@ -0,0 +1,19 @@ + + + + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 0000000..7061a0d --- /dev/null +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,61 @@ + + + + \ No newline at end of file diff --git a/.idea/migrations.xml b/.idea/migrations.xml new file mode 100644 index 0000000..f8051a6 --- /dev/null +++ b/.idea/migrations.xml @@ -0,0 +1,10 @@ + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..b2c751a --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,9 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/runConfigurations.xml b/.idea/runConfigurations.xml new file mode 100644 index 0000000..16660f1 --- /dev/null +++ b/.idea/runConfigurations.xml @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/app/build.gradle.kts b/app/build.gradle.kts new file mode 100644 index 0000000..8b2c35e --- /dev/null +++ b/app/build.gradle.kts @@ -0,0 +1,69 @@ +plugins { + alias(libs.plugins.android.application) + alias(libs.plugins.jetbrains.kotlin.android) +} + +android { + namespace = "com.example.notebook" + compileSdk = 34 + + defaultConfig { + applicationId = "com.example.notebook" + 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_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + } + kotlinOptions { + jvmTarget = "1.8" + } + buildFeatures { + compose = true + } + composeOptions { + kotlinCompilerExtensionVersion = "1.5.8" + } + packaging { + resources { + excludes += "/META-INF/{AL2.0,LGPL2.1}" + } + } +} + +dependencies { + implementation(libs.core.ktx) + implementation(libs.lifecycle.runtime.ktx) + implementation(libs.activity.compose) + implementation(platform(libs.compose.bom)) + implementation(libs.ui) + implementation(libs.ui.graphics) + implementation(libs.ui.tooling.preview) + implementation(libs.material3) + implementation(libs.material.icons.extended) + testImplementation(libs.junit) + androidTestImplementation(libs.ext.junit) + androidTestImplementation(libs.espresso.core) + androidTestImplementation(platform(libs.compose.bom)) + androidTestImplementation(libs.ui.test.junit4) + debugImplementation(libs.ui.tooling) + debugImplementation(libs.ui.test.manifest) +} \ No newline at end of file diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/app/src/androidTest/java/com/example/notebook/ExampleInstrumentedTest.java b/app/src/androidTest/java/com/example/notebook/ExampleInstrumentedTest.java new file mode 100644 index 0000000..f6b9bdc --- /dev/null +++ b/app/src/androidTest/java/com/example/notebook/ExampleInstrumentedTest.java @@ -0,0 +1,26 @@ +package com.example.notebook; + +import android.content.Context; + +import androidx.test.platform.app.InstrumentationRegistry; +import androidx.test.ext.junit.runners.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.junit.Assert.*; + +/** + * Instrumented test, which will execute on an Android device. + * + * @see Testing documentation + */ +@RunWith(AndroidJUnit4.class) +public class ExampleInstrumentedTest { + @Test + public void useAppContext() { + // Context of the app under test. + Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); + assertEquals("com.example.notebook", appContext.getPackageName()); + } +} \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..a2adb8f --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/com/example/notebook/MainActivity.kt b/app/src/main/java/com/example/notebook/MainActivity.kt new file mode 100644 index 0000000..6298171 --- /dev/null +++ b/app/src/main/java/com/example/notebook/MainActivity.kt @@ -0,0 +1,743 @@ +package com.example.notebook + +import android.os.Bundle +import androidx.activity.ComponentActivity +import androidx.activity.compose.rememberLauncherForActivityResult +import androidx.activity.compose.setContent +import androidx.activity.result.PickVisualMediaRequest +import androidx.activity.result.contract.ActivityResultContracts +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.lazy.grid.GridCells +import androidx.compose.foundation.lazy.grid.LazyVerticalGrid +import androidx.compose.foundation.lazy.grid.items +import androidx.compose.foundation.selection.selectable +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.filled.Send +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.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.compose.ui.window.Dialog +import androidx.compose.ui.window.DialogProperties +import com.example.notebook.ui.theme.NotebookTheme + +class MainActivity : ComponentActivity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContent { + NotebookTheme { + Surface( + modifier = Modifier.fillMaxSize(), + color = MaterialTheme.colorScheme.background + ) { + NotebookApp() + } + } + } + } +} + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun NotebookApp() { + var selectedTabIndex by remember { mutableStateOf(0) } + val tabs = listOf("Sources", "Chat", "Studio") + var showGoogleAppsMenu by remember { mutableStateOf(false) } + var showSettingsMenu by remember { mutableStateOf(false) } + var showAccountScreen by remember { mutableStateOf(false) } + + if (showAccountScreen) { + AccountScreen(onDismiss = { showAccountScreen = false }) + } + + Scaffold( + topBar = { + TopAppBar( + title = { Text("Apis Notepad") }, + actions = { + IconButton(onClick = { /* do something */ }) { + Icon(Icons.Filled.Share, contentDescription = "Share") + } + Box { + IconButton(onClick = { showSettingsMenu = true }) { + Icon(Icons.Filled.Settings, contentDescription = "Settings") + } + SettingsMenu(expanded = showSettingsMenu, onDismiss = { showSettingsMenu = false }) + } + Box { + IconButton(onClick = { showGoogleAppsMenu = true }) { + Icon(Icons.Filled.Apps, contentDescription = "Google Apps") + } + GoogleAppsMenu(expanded = showGoogleAppsMenu, onDismiss = { showGoogleAppsMenu = false }) + } + Box( + modifier = Modifier + .size(32.dp) + .clip(CircleShape) + .background(Color.Magenta) + .clickable { showAccountScreen = true }, + contentAlignment = Alignment.Center + ) { + Text("M", color = Color.White, fontWeight = FontWeight.Bold) + } + Spacer(modifier = Modifier.width(8.dp)) + } + ) + } + ) { innerPadding -> + Column(modifier = Modifier.padding(innerPadding)) { + TabRow(selectedTabIndex = selectedTabIndex) { + tabs.forEachIndexed { index, title -> + Tab(selected = selectedTabIndex == index, + onClick = { selectedTabIndex = index }, + text = { Text(title) }) + } + } + when (selectedTabIndex) { + 0 -> SourcesScreen() + 1 -> ChatScreen() + 2 -> StudioScreen() + } + } + } +} + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun AccountScreen(onDismiss: () -> Unit) { + Dialog(onDismissRequest = onDismiss, properties = DialogProperties(usePlatformDefaultWidth = false)) { + Scaffold( + topBar = { + TopAppBar( + title = { + Column { + Text("202310715190@mhs.ubharajaya.ac.id", fontSize = 14.sp) + Text("Managed by mhs.ubharajay.ac.id", fontSize = 12.sp, color = Color.Gray) + } + }, + actions = { + IconButton(onClick = onDismiss) { + Icon(Icons.Default.Close, contentDescription = "Close") + } + } + ) + } + ) { paddingValues -> + Column( + modifier = Modifier + .fillMaxSize() + .padding(paddingValues) + .padding(horizontal = 16.dp), + horizontalAlignment = Alignment.CenterHorizontally + ) { + Spacer(modifier = Modifier.height(48.dp)) + + Box { + Box( + modifier = Modifier + .size(80.dp) + .clip(CircleShape) + .background(Color.Magenta), + contentAlignment = Alignment.Center + ) { + Text("M", color = Color.White, fontWeight = FontWeight.Bold, fontSize = 40.sp) + } + Icon( + imageVector = Icons.Default.PhotoCamera, + contentDescription = "Change profile picture", + tint = Color.Black, + modifier = Modifier + .align(Alignment.BottomEnd) + .size(24.dp) + .background(Color.LightGray, CircleShape) + .padding(4.dp) + ) + } + Spacer(modifier = Modifier.height(16.dp)) + + Text("Hi, 202310715190!", fontSize = 20.sp) + Spacer(modifier = Modifier.height(16.dp)) + + OutlinedButton(onClick = { /* TODO */ }, modifier = Modifier.fillMaxWidth()) { + Text("Manage your Google Account") + } + Spacer(modifier = Modifier.height(32.dp)) + + Card( + modifier = Modifier.fillMaxWidth(), + shape = RoundedCornerShape(16.dp) + ) { + Column { + Row( + modifier = Modifier + .fillMaxWidth() + .clickable { /* TODO */ } + .padding(16.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Icon(Icons.Default.PersonAdd, contentDescription = null) + Spacer(modifier = Modifier.width(16.dp)) + Text("Add account") + } + Divider(modifier = Modifier.padding(horizontal = 16.dp)) + Row( + modifier = Modifier + .fillMaxWidth() + .clickable { /* TODO */ } + .padding(16.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Icon(Icons.Default.ManageAccounts, contentDescription = null) + Spacer(modifier = Modifier.width(16.dp)) + Text("Manage accounts") + } + } + } + + Spacer(modifier = Modifier.weight(1f)) + + Row( + modifier = Modifier.padding(bottom = 16.dp), + horizontalArrangement = Arrangement.spacedBy(8.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Text("Privacy Policy", fontSize = 12.sp) + Box(modifier = Modifier.size(2.dp).background(Color.Gray, CircleShape)) + Text("Terms of Service", fontSize = 12.sp) + } + } + } + } +} + + +@Composable +fun SettingsMenu(expanded: Boolean, onDismiss: () -> Unit) { + DropdownMenu( + expanded = expanded, + onDismissRequest = onDismiss + ) { + DropdownMenuItem( + text = { Text("NotebookLM Help") }, + onClick = { /* TODO */ }, + leadingIcon = { Icon(Icons.Default.HelpOutline, contentDescription = null) } + ) + DropdownMenuItem( + text = { Text("Send feedback") }, + onClick = { /* TODO */ }, + leadingIcon = { Icon(Icons.Default.Feedback, contentDescription = null) } + ) + DropdownMenuItem( + text = { Text("Output Language") }, + onClick = { /* TODO */ }, + leadingIcon = { Icon(Icons.Default.Language, contentDescription = null) } + ) + Divider() + DropdownMenuItem( + text = { Text("Light mode") }, + onClick = { /* TODO */ }, + leadingIcon = { Icon(Icons.Default.LightMode, contentDescription = null) } + ) + DropdownMenuItem( + text = { Text("Dark mode") }, + onClick = { /* TODO */ }, + leadingIcon = { Icon(Icons.Default.DarkMode, contentDescription = null) } + ) + DropdownMenuItem( + text = { Text("Device") }, + onClick = { /* TODO */ }, + leadingIcon = { Icon(Icons.Default.Tonality, contentDescription = null) }, + trailingIcon = { Icon(Icons.Default.Check, contentDescription = "Selected") } + ) + Divider() + DropdownMenuItem( + text = { Text("Upgrade to Plus") }, + onClick = { /* TODO */ }, + leadingIcon = { Icon(Icons.Default.AllInclusive, contentDescription = null) } + ) + } +} + +@Composable +fun GoogleAppsMenu(expanded: Boolean, onDismiss: () -> Unit) { + val apps = listOf( + "Account" to Icons.Default.AccountCircle, "Gmail" to Icons.Default.Mail, "Drive" to Icons.Default.Cloud, + "Classroom" to Icons.Default.School, "Docs" to Icons.Default.Article, "Gemini" to Icons.Default.AutoAwesome, + "Sheets" to Icons.Default.GridOn, "Slides" to Icons.Default.Slideshow, "Calendar" to Icons.Default.CalendarToday, + "Chat" to Icons.Default.Chat, "Meet" to Icons.Default.Videocam, "Forms" to Icons.Default.Assignment + ) + val appChunks = apps.chunked(3) + + DropdownMenu( + expanded = expanded, + onDismissRequest = onDismiss, + modifier = Modifier.width(300.dp) + ) { + Column( + modifier = Modifier.padding(vertical = 16.dp), + verticalArrangement = Arrangement.spacedBy(16.dp) + ) { + appChunks.forEach { rowApps -> + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceAround + ) { + rowApps.forEach { (name, icon) -> + Column( + horizontalAlignment = Alignment.CenterHorizontally, + modifier = Modifier + .clickable { /*TODO*/ } + .padding(horizontal = 4.dp) + ) { + Icon(icon, contentDescription = name, modifier = Modifier.size(40.dp)) + Spacer(modifier = Modifier.height(4.dp)) + Text(name, fontSize = 12.sp, textAlign = TextAlign.Center) + } + } + } + } + } + } +} + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun SourcesScreen() { + var showAddSourcesSheet by remember { mutableStateOf(false) } + var showDiscoverDialog by remember { mutableStateOf(false) } + + if (showAddSourcesSheet) { + AddSourcesSheet(onDismiss = { showAddSourcesSheet = false }) + } + if (showDiscoverDialog) { + DiscoverSourcesDialog(onDismiss = { showDiscoverDialog = false }) + } + + Column( + modifier = Modifier + .fillMaxSize() + .padding(16.dp), + horizontalAlignment = Alignment.CenterHorizontally + ) { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(8.dp) + ) { + Button(onClick = { showAddSourcesSheet = true }, modifier = Modifier.weight(1f)) { + Icon(Icons.Filled.Add, contentDescription = "Add") + Spacer(modifier = Modifier.width(4.dp)) + Text("Add") + } + Button(onClick = { showDiscoverDialog = true }, modifier = Modifier.weight(1f)) { + Icon(Icons.Filled.Search, contentDescription = "Discover") + Spacer(modifier = Modifier.width(4.dp)) + Text("Discover") + } + } + Column( + modifier = Modifier.fillMaxSize(), + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.CenterHorizontally + ) { + Icon(Icons.Filled.Description, contentDescription = "", modifier = Modifier.size(48.dp), tint = Color.Gray) + Spacer(modifier = Modifier.height(16.dp)) + Text("Saved sources will appear here", style = MaterialTheme.typography.titleMedium) + Spacer(modifier = Modifier.height(8.dp)) + Text( + text = "Click Add source above to add PDFs, websites, text, videos, or audio files. Or import a file directly from Google Drive.", + style = MaterialTheme.typography.bodyMedium, + textAlign = TextAlign.Center, + color = Color.Gray + ) + } + } +} + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun DiscoverSourcesDialog(onDismiss: () -> Unit) { + var description by remember { mutableStateOf("") } + val findFromOptions = listOf("Web", "Google Drive") + var selectedOption by remember { mutableStateOf(findFromOptions[0]) } + + Dialog(onDismissRequest = onDismiss, properties = DialogProperties(usePlatformDefaultWidth = false)) { + Scaffold( + topBar = { + TopAppBar( + title = { Text("Discover sources") }, + actions = { + IconButton(onClick = onDismiss) { + Icon(Icons.Default.Close, contentDescription = "Close") + } + } + ) + }, + bottomBar = { + Row( + modifier = Modifier.fillMaxWidth().padding(16.dp), + horizontalArrangement = Arrangement.spacedBy(8.dp) + ) { + Button( + onClick = { /* TODO */ }, + modifier = Modifier.weight(1f) + ) { + Icon(Icons.Default.Celebration, contentDescription = null) + Spacer(modifier = Modifier.width(4.dp)) + Text("I'm feeling curious") + } + Button( + onClick = { /* TODO */ }, + modifier = Modifier.weight(1f), + enabled = description.isNotBlank() + ) { + Text("Submit") + } + } + } + ) { paddingValues -> + Column( + modifier = Modifier + .fillMaxSize() + .padding(paddingValues) + .padding(16.dp), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.spacedBy(16.dp) + ) { + Icon( + imageVector = Icons.Default.TravelExplore, + contentDescription = null, + modifier = Modifier.size(48.dp) + ) + Text("What are you interested in?", style = MaterialTheme.typography.titleLarge) + OutlinedTextField( + value = description, + onValueChange = { description = it }, + placeholder = { Text("Describe something you'd like to learn about or click \"I'm feeling curious\" to explore a new topic.") }, + modifier = Modifier.fillMaxWidth().height(150.dp) + ) + Column(modifier = Modifier.fillMaxWidth()) { + Text("Find sources from:", style = MaterialTheme.typography.titleMedium) + findFromOptions.forEach { text -> + Row( + Modifier + .fillMaxWidth() + .selectable( + selected = (text == selectedOption), + onClick = { selectedOption = text } + ) + .padding(vertical = 8.dp), + verticalAlignment = Alignment.CenterVertically + ) { + RadioButton( + selected = (text == selectedOption), + onClick = { selectedOption = text } + ) + Text( + text = text, + style = MaterialTheme.typography.bodyLarge, + modifier = Modifier.padding(start = 8.dp) + ) + } + } + } + } + } + } +} + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun AddSourcesSheet(onDismiss: () -> Unit) { + val sheetState = rememberModalBottomSheetState() + var showUploadMenu by remember { mutableStateOf(false) } + + val photoPickerLauncher = rememberLauncherForActivityResult( + contract = ActivityResultContracts.PickVisualMedia(), + onResult = { uri -> /* Handle URI */ } + ) + + val filePickerLauncher = rememberLauncherForActivityResult( + contract = ActivityResultContracts.GetContent(), + onResult = { uri -> /* Handle URI */ } + ) + + ModalBottomSheet( + onDismissRequest = onDismiss, + sheetState = sheetState + ) { + Column( + modifier = Modifier + .fillMaxWidth() + .padding(16.dp), + verticalArrangement = Arrangement.spacedBy(16.dp) + ) { + Row(verticalAlignment = Alignment.CenterVertically) { + Icon(Icons.Default.AllInclusive, contentDescription = null) + Spacer(modifier = Modifier.width(8.dp)) + Text("NotebookLM", fontWeight = FontWeight.Bold, fontSize = 20.sp) + Spacer(modifier = Modifier.weight(1f)) + IconButton(onClick = onDismiss) { + Icon(Icons.Default.Close, contentDescription = "Close") + } + } + Row(verticalAlignment = Alignment.CenterVertically) { + Text("Add sources", style = MaterialTheme.typography.titleLarge) + Spacer(modifier = Modifier.weight(1f)) + Button(onClick = { /*TODO*/ }) { + Icon(Icons.Default.Search, contentDescription = null) + Spacer(modifier = Modifier.width(4.dp)) + Text("Discover sources") + } + } + Text("Get started by selecting sources", style = MaterialTheme.typography.bodyMedium, color = Color.Gray) + + // Upload sources card + Box { + Card( + modifier = Modifier + .fillMaxWidth() + .clickable { showUploadMenu = true } + .border(1.dp, Color.Gray, RoundedCornerShape(8.dp)) + ) { + Column( + modifier = Modifier.padding(16.dp).fillMaxWidth(), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.spacedBy(8.dp) + ) { + Box( + modifier = Modifier.size(40.dp).clip(CircleShape).background(MaterialTheme.colorScheme.primaryContainer), + contentAlignment = Alignment.Center + ) { + Icon(Icons.Default.CloudUpload, contentDescription = null, tint = MaterialTheme.colorScheme.onPrimaryContainer) + } + Text("Upload sources", fontWeight = FontWeight.Bold) + Text("Supported file types: PDF, .txt, Markdown, Audio (e.g. mp3)", fontSize = 12.sp, color = Color.Gray, textAlign = TextAlign.Center) + } + } + DropdownMenu( + expanded = showUploadMenu, + onDismissRequest = { showUploadMenu = false } + ) { + DropdownMenuItem( + text = { Text("Perpustakaan Foto") }, + onClick = { + photoPickerLauncher.launch(PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.ImageAndVideo)) + showUploadMenu = false + }, + leadingIcon = { Icon(Icons.Default.PhotoLibrary, contentDescription = null) } + ) + DropdownMenuItem( + text = { Text("Ambil Video") }, + onClick = { /* TODO: Implement Camera Launcher */ showUploadMenu = false }, + leadingIcon = { Icon(Icons.Default.PhotoCamera, contentDescription = null) } + ) + DropdownMenuItem( + text = { Text("Pilih File") }, + onClick = { + filePickerLauncher.launch("*/*") + showUploadMenu = false + }, + leadingIcon = { Icon(Icons.Default.Folder, contentDescription = null) } + ) + DropdownMenuItem( + text = { Text("Google Drive") }, + onClick = { + filePickerLauncher.launch("*/*") + showUploadMenu = false + }, + leadingIcon = { Icon(Icons.Default.Cloud, contentDescription = null) } + ) + } + } + + // Google Workspace card + Card(modifier = Modifier.fillMaxWidth()) { + Column(modifier = Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) { + Row(verticalAlignment = Alignment.CenterVertically) { + Icon(Icons.Default.CorporateFare, contentDescription = null) + Spacer(modifier = Modifier.width(8.dp)) + Text("Google Workspace", fontWeight = FontWeight.Bold) + } + Button(onClick = { /*TODO*/ }) { + Icon(Icons.Default.Cloud, contentDescription = null) + Spacer(modifier = Modifier.width(4.dp)) + Text("Google Drive") + } + } + } + + // Link card + Card(modifier = Modifier.fillMaxWidth()) { + Column(modifier = Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) { + Row(verticalAlignment = Alignment.CenterVertically) { + Icon(Icons.Default.Link, contentDescription = null) + Spacer(modifier = Modifier.width(8.dp)) + Text("Link", fontWeight = FontWeight.Bold) + } + Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) { + Button(onClick = { /*TODO*/ }) { + Icon(Icons.Default.Language, contentDescription = null) + Spacer(modifier = Modifier.width(4.dp)) + Text("Website") + } + Button(onClick = { /*TODO*/ }) { + Icon(Icons.Default.PlayCircle, contentDescription = null) + Spacer(modifier = Modifier.width(4.dp)) + Text("YouTube") + } + } + } + } + } + } +} + + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun ChatScreen() { + var showAddSourcesSheet by remember { mutableStateOf(false) } + + if (showAddSourcesSheet) { + AddSourcesSheet(onDismiss = { showAddSourcesSheet = false }) + } + + Scaffold( + bottomBar = { + BottomAppBar { + Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.padding(horizontal = 8.dp)) { + OutlinedTextField( + value = "", + onValueChange = {}, + placeholder = { Text("Upload a source to get started") }, + modifier = Modifier.weight(1f) + ) + IconButton(onClick = { /* do something */ }) { + Icon(Icons.AutoMirrored.Filled.Send, contentDescription = "Send") + } + } + } + } + ) { innerPadding -> + Column( + modifier = Modifier + .padding(innerPadding) + .fillMaxSize(), + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.CenterHorizontally + ) { + Icon( + Icons.Filled.Upload, + contentDescription = "Upload", + modifier = Modifier.size(48.dp) + ) + Text( + text = "Add a source to get started", + style = MaterialTheme.typography.headlineSmall, + textAlign = TextAlign.Center + ) + Spacer(modifier = Modifier.height(16.dp)) + Button(onClick = { showAddSourcesSheet = true }) { + Text("Upload a source") + } + } + } +} + +@Composable +fun StudioScreen() { + val studioActions = listOf( + "Audio Overview" to Icons.Default.GraphicEq, + "Video Overview" to Icons.Default.OndemandVideo, + "Mind Map" to Icons.Default.AccountTree, + "Reports" to Icons.Default.Assessment, + "Flashcards" to Icons.Default.Style, + "Quiz" to Icons.Default.Quiz + ) + + Column( + modifier = Modifier + .fillMaxSize() + .padding(16.dp), + horizontalAlignment = Alignment.CenterHorizontally + ) { + LazyVerticalGrid( + columns = GridCells.Fixed(2), + verticalArrangement = Arrangement.spacedBy(8.dp), + horizontalArrangement = Arrangement.spacedBy(8.dp) + ) { + items(studioActions) { (title, icon) -> + StudioActionCard(title = title, icon = icon) + } + } + + Column( + modifier = Modifier.fillMaxSize(), + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.CenterHorizontally + ) { + Icon(Icons.Default.AutoAwesome, contentDescription = null, tint = Color.Gray) + Spacer(modifier = Modifier.height(8.dp)) + Text("Studio output will be saved here.", style = MaterialTheme.typography.titleMedium) + Spacer(modifier = Modifier.height(8.dp)) + Text( + text = "After adding sources, click to add Audio Overview, Study Guide, Mind Map, and more!", + style = MaterialTheme.typography.bodyMedium, + textAlign = TextAlign.Center, + color = Color.Gray + ) + Spacer(modifier = Modifier.height(16.dp)) + Button(onClick = { /*TODO*/ }) { + Icon(Icons.Default.NoteAdd, contentDescription = null) + Spacer(modifier = Modifier.width(4.dp)) + Text("Add note") + } + } + } +} + +@Composable +fun StudioActionCard(title: String, icon: ImageVector) { + Card( + modifier = Modifier.fillMaxWidth(), + shape = MaterialTheme.shapes.medium + ) { + Box(modifier = Modifier.padding(12.dp)) { + Column(horizontalAlignment = Alignment.Start) { + Icon(icon, contentDescription = null, modifier = Modifier.size(24.dp)) + Spacer(modifier = Modifier.height(8.dp)) + Text(title, style = MaterialTheme.typography.bodyLarge) + } + Icon( + Icons.Default.Edit, + contentDescription = "Edit", + modifier = Modifier + .align(Alignment.TopEnd) + .size(18.dp), + tint = Color.Gray + ) + } + } +} + +@Preview(showBackground = true) +@Composable +fun DefaultPreview() { + NotebookTheme { + NotebookApp() + } +} diff --git a/app/src/main/java/com/example/notebook/ui/theme/Color.kt b/app/src/main/java/com/example/notebook/ui/theme/Color.kt new file mode 100644 index 0000000..09807e3 --- /dev/null +++ b/app/src/main/java/com/example/notebook/ui/theme/Color.kt @@ -0,0 +1,11 @@ +package com.example.notebook.ui.theme + +import androidx.compose.ui.graphics.Color + +val Purple80 = Color(0xFFD0BCFF) +val PurpleGrey80 = Color(0xFFCCC2DC) +val Pink80 = Color(0xFFEFB8C8) + +val Purple40 = Color(0xFF6650a4) +val PurpleGrey40 = Color(0xFF625b71) +val Pink40 = Color(0xFF7D5260) diff --git a/app/src/main/java/com/example/notebook/ui/theme/Theme.kt b/app/src/main/java/com/example/notebook/ui/theme/Theme.kt new file mode 100644 index 0000000..2904fbd --- /dev/null +++ b/app/src/main/java/com/example/notebook/ui/theme/Theme.kt @@ -0,0 +1,70 @@ +package com.example.notebook.ui.theme + +import android.app.Activity +import android.os.Build +import androidx.compose.foundation.isSystemInDarkTheme +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.darkColorScheme +import androidx.compose.material3.dynamicDarkColorScheme +import androidx.compose.material3.dynamicLightColorScheme +import androidx.compose.material3.lightColorScheme +import androidx.compose.runtime.Composable +import androidx.compose.runtime.SideEffect +import androidx.compose.ui.graphics.toArgb +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalView +import androidx.core.view.WindowCompat + +private val DarkColorScheme = darkColorScheme( + primary = Purple80, + secondary = PurpleGrey80, + tertiary = Pink80 +) + +private val LightColorScheme = lightColorScheme( + primary = Purple40, + secondary = PurpleGrey40, + tertiary = Pink40 + + /* Other default colors to override + background = Color(0xFFFFFBFE), + surface = Color(0xFFFFFBFE), + onPrimary = Color.White, + onSecondary = Color.White, + onTertiary = Color.White, + onBackground = Color(0xFF1C1B1F), + onSurface = Color(0xFF1C1B1F), + */ +) + +@Composable +fun NotebookTheme( + darkTheme: Boolean = isSystemInDarkTheme(), + // Dynamic color is available on Android 12+ + 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 + } + val view = LocalView.current + if (!view.isInEditMode) { + SideEffect { + val window = (view.context as Activity).window + window.statusBarColor = colorScheme.primary.toArgb() + WindowCompat.getInsetsController(window, view).isAppearanceLightStatusBars = darkTheme + } + } + + MaterialTheme( + colorScheme = colorScheme, + typography = Typography, + content = content + ) +} diff --git a/app/src/main/java/com/example/notebook/ui/theme/Type.kt b/app/src/main/java/com/example/notebook/ui/theme/Type.kt new file mode 100644 index 0000000..a0d01f4 --- /dev/null +++ b/app/src/main/java/com/example/notebook/ui/theme/Type.kt @@ -0,0 +1,34 @@ +package com.example.notebook.ui.theme + +import androidx.compose.material3.Typography +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.FontFamily +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.sp + +// Set of Material typography styles to start with +val Typography = Typography( + bodyLarge = TextStyle( + fontFamily = FontFamily.Default, + fontWeight = FontWeight.Normal, + fontSize = 16.sp, + lineHeight = 24.sp, + letterSpacing = 0.5.sp + ) + /* Other default text styles to override + titleLarge = TextStyle( + fontFamily = FontFamily.Default, + fontWeight = FontWeight.Normal, + fontSize = 22.sp, + lineHeight = 28.sp, + letterSpacing = 0.sp + ), + labelSmall = TextStyle( + fontFamily = FontFamily.Default, + fontWeight = FontWeight.Medium, + fontSize = 11.sp, + lineHeight = 16.sp, + letterSpacing = 0.5.sp + ) + */ +) diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 0000000..07d5da9 --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_launcher_foreground.xml b/app/src/main/res/drawable/ic_launcher_foreground.xml new file mode 100644 index 0000000..2b068d1 --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_foreground.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 0000000..6f3b755 --- /dev/null +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml new file mode 100644 index 0000000..6f3b755 --- /dev/null +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.webp b/app/src/main/res/mipmap-hdpi/ic_launcher.webp new file mode 100644 index 0000000..c209e78 Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher.webp differ diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp new file mode 100644 index 0000000..b2dfe3d Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.webp b/app/src/main/res/mipmap-mdpi/ic_launcher.webp new file mode 100644 index 0000000..4f0f1d6 Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher.webp differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp new file mode 100644 index 0000000..62b611d Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xhdpi/ic_launcher.webp new file mode 100644 index 0000000..948a307 Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher.webp differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp new file mode 100644 index 0000000..1b9a695 Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp new file mode 100644 index 0000000..28d4b77 Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp new file mode 100644 index 0000000..9287f50 Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp new file mode 100644 index 0000000..aa7d642 Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp new file mode 100644 index 0000000..9126ae3 Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp differ diff --git a/app/src/main/res/values-night/themes.xml b/app/src/main/res/values-night/themes.xml new file mode 100644 index 0000000..7b596ca --- /dev/null +++ b/app/src/main/res/values-night/themes.xml @@ -0,0 +1,3 @@ + +