Merge pull request 'nextNoteVault' (#23) from nextNoteVault into master

Reviewed-on: #23
This commit is contained in:
sebastian 2025-05-10 06:23:23 +00:00
commit 58d2658dc4
172 changed files with 1517 additions and 4756 deletions

6
.gitignore vendored
View File

@ -13,3 +13,9 @@
.externalNativeBuild
.cxx
local.properties
/music_database
/music_database-shm
/music_database-wal
/note_database
/note_database-shm
/note_database-wal

1
.idea/.gitignore vendored
View File

@ -6,3 +6,4 @@
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml
/AndroidProjectSystem.xml

1
.idea/.name Normal file
View File

@ -0,0 +1 @@
NoteVault

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="AndroidProjectSystem">
<option name="providerId" value="com.android.tools.idea.GradleProjectSystem" />
</component>
</project>

View File

@ -0,0 +1,10 @@
<component name="ProjectCodeStyleConfiguration">
<code_scheme name="Project" version="173">
<JetCodeStyleSettings>
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
</JetCodeStyleSettings>
<codeStyleSettings language="kotlin">
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
</codeStyleSettings>
</code_scheme>
</component>

View File

@ -1,5 +1,5 @@
<component name="ProjectCodeStyleConfiguration">
<state>
<option name="PREFERRED_PROJECT_CODE_STYLE" value="Default" />
<option name="USE_PER_PROJECT_SETTINGS" value="true" />
</state>
</component>

View File

@ -1,31 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="DataSourceManagerImpl" format="xml" multifile-model="true">
<data-source source="LOCAL" name="music_database" uuid="7fd8322d-6535-44e0-a663-86c7b54f4fbd">
<data-source source="LOCAL" name="music_database" uuid="95a3c2ec-2c29-4336-900a-3993de90ae66">
<driver-ref>sqlite.xerial</driver-ref>
<synchronize>true</synchronize>
<jdbc-driver>org.sqlite.JDBC</jdbc-driver>
<jdbc-url>jdbc:sqlite:$PROJECT_DIR$/music_database</jdbc-url>
<jdbc-additional-properties>
<property name="com.intellij.clouds.kubernetes.db.enabled" value="false" />
</jdbc-additional-properties>
<working-dir>$ProjectFileDir$</working-dir>
</data-source>
<data-source source="LOCAL" name="music_database [2]" uuid="6f801a54-4854-4db5-910c-ae63a8207d3e">
<driver-ref>sqlite.xerial</driver-ref>
<synchronize>true</synchronize>
<jdbc-driver>org.sqlite.JDBC</jdbc-driver>
<jdbc-url>jdbc:sqlite:$PROJECT_DIR$/music_database</jdbc-url>
<jdbc-additional-properties>
<property name="com.intellij.clouds.kubernetes.db.enabled" value="false" />
</jdbc-additional-properties>
<working-dir>$ProjectFileDir$</working-dir>
</data-source>
<data-source source="LOCAL" name="music_database [3]" uuid="3acbe0c9-6415-4726-8c75-c07350984fcc">
<driver-ref>sqlite.xerial</driver-ref>
<synchronize>true</synchronize>
<jdbc-driver>org.sqlite.JDBC</jdbc-driver>
<jdbc-url>jdbc:sqlite:$USER_HOME$/.cache/JetBrains/IntelliJIdea2024.1/device-explorer/Pixel Tablet API 30/_/data/data/core.notevault/databases/music_database</jdbc-url>
<jdbc-url>jdbc:sqlite:$USER_HOME$/.cache/JetBrains/IntelliJIdea2025.1/device-explorer/samsung SM-P610/_/data/data/com.stormtales.notevault/databases/music_database</jdbc-url>
<jdbc-additional-properties>
<property name="com.intellij.clouds.kubernetes.db.enabled" value="false" />
</jdbc-additional-properties>
@ -39,5 +19,53 @@
</library>
</libraries>
</data-source>
<data-source source="LOCAL" name="note_database" uuid="b3770d7c-0a73-40c6-aab8-010effaa19b6">
<driver-ref>sqlite.xerial</driver-ref>
<synchronize>true</synchronize>
<jdbc-driver>org.sqlite.JDBC</jdbc-driver>
<jdbc-url>jdbc:sqlite:$USER_HOME$/.cache/JetBrains/IntelliJIdea2025.1/device-explorer/samsung SM-P610/_/data/data/come.stormborntales.notevault/databases/note_database</jdbc-url>
<jdbc-additional-properties>
<property name="com.intellij.clouds.kubernetes.db.enabled" value="false" />
</jdbc-additional-properties>
<working-dir>$ProjectFileDir$</working-dir>
<libraries>
<library>
<url>file://$APPLICATION_CONFIG_DIR$/jdbc-drivers/Xerial SQLiteJDBC/3.45.1/org/xerial/sqlite-jdbc/3.45.1.0/sqlite-jdbc-3.45.1.0.jar</url>
</library>
<library>
<url>file://$APPLICATION_CONFIG_DIR$/jdbc-drivers/Xerial SQLiteJDBC/3.45.1/org/slf4j/slf4j-api/1.7.36/slf4j-api-1.7.36.jar</url>
</library>
<library>
<url>file://$APPLICATION_CONFIG_DIR$/jdbc-drivers/Xerial SQLiteJDBC/3.45.1/org/xerial/sqlite-jdbc/3.45.1.0/sqlite-jdbc-3.45.1.0.jar</url>
</library>
<library>
<url>file://$APPLICATION_CONFIG_DIR$/jdbc-drivers/Xerial SQLiteJDBC/3.45.1/org/slf4j/slf4j-api/1.7.36/slf4j-api-1.7.36.jar</url>
</library>
</libraries>
</data-source>
<data-source source="LOCAL" name="note_database [2]" uuid="ad94cfd9-e485-4151-8bf4-a080c51fa27c">
<driver-ref>sqlite.xerial</driver-ref>
<synchronize>true</synchronize>
<jdbc-driver>org.sqlite.JDBC</jdbc-driver>
<jdbc-url>jdbc:sqlite:$PROJECT_DIR$/note_database</jdbc-url>
<jdbc-additional-properties>
<property name="com.intellij.clouds.kubernetes.db.enabled" value="false" />
</jdbc-additional-properties>
<working-dir>$ProjectFileDir$</working-dir>
<libraries>
<library>
<url>file://$APPLICATION_CONFIG_DIR$/jdbc-drivers/Xerial SQLiteJDBC/3.45.1/org/xerial/sqlite-jdbc/3.45.1.0/sqlite-jdbc-3.45.1.0.jar</url>
</library>
<library>
<url>file://$APPLICATION_CONFIG_DIR$/jdbc-drivers/Xerial SQLiteJDBC/3.45.1/org/slf4j/slf4j-api/1.7.36/slf4j-api-1.7.36.jar</url>
</library>
<library>
<url>file://$APPLICATION_CONFIG_DIR$/jdbc-drivers/Xerial SQLiteJDBC/3.45.1/org/xerial/sqlite-jdbc/3.45.1.0/sqlite-jdbc-3.45.1.0.jar</url>
</library>
<library>
<url>file://$APPLICATION_CONFIG_DIR$/jdbc-drivers/Xerial SQLiteJDBC/3.45.1/org/slf4j/slf4j-api/1.7.36/slf4j-api-1.7.36.jar</url>
</library>
</libraries>
</data-source>
</component>
</project>

View File

@ -1,13 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="deploymentTargetDropDown">
<value>
<entry key="NoteVault">
<State />
</entry>
<entry key="app">
<State />
</entry>
</value>
</component>
</project>

View File

@ -4,6 +4,14 @@
<selectionStates>
<SelectionState runConfigName="app">
<option name="selectionMode" value="DROPDOWN" />
<DropdownSelection timestamp="2025-05-03T08:34:29.354537334Z">
<Target type="DEFAULT_BOOT">
<handle>
<DeviceId pluginId="PhysicalDevice" identifier="serial=R52N50NLGRT" />
</handle>
</Target>
</DropdownSelection>
<DialogSelection />
</SelectionState>
</selectionStates>
</component>

View File

@ -5,7 +5,6 @@
<option name="linkedExternalProjectsSettings">
<GradleProjectSettings>
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="gradleJvm" value="21" />
<option name="modules">
<set>
<option value="$PROJECT_DIR$" />

View File

@ -0,0 +1,50 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="ComposePreviewDimensionRespectsLimit" enabled="true" level="WARNING" enabled_by_default="true">
<option name="composableFile" value="true" />
</inspection_tool>
<inspection_tool class="ComposePreviewMustBeTopLevelFunction" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
</inspection_tool>
<inspection_tool class="ComposePreviewNeedsComposableAnnotation" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
</inspection_tool>
<inspection_tool class="ComposePreviewNotSupportedInUnitTestFiles" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
</inspection_tool>
<inspection_tool class="GlancePreviewDimensionRespectsLimit" enabled="true" level="WARNING" enabled_by_default="true">
<option name="composableFile" value="true" />
</inspection_tool>
<inspection_tool class="GlancePreviewMustBeTopLevelFunction" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
</inspection_tool>
<inspection_tool class="GlancePreviewNeedsComposableAnnotation" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
</inspection_tool>
<inspection_tool class="GlancePreviewNotSupportedInUnitTestFiles" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
</inspection_tool>
<inspection_tool class="PreviewAnnotationInFunctionWithParameters" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
</inspection_tool>
<inspection_tool class="PreviewApiLevelMustBeValid" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
</inspection_tool>
<inspection_tool class="PreviewDeviceShouldUseNewSpec" enabled="true" level="WEAK WARNING" enabled_by_default="true">
<option name="composableFile" value="true" />
</inspection_tool>
<inspection_tool class="PreviewFontScaleMustBeGreaterThanZero" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
</inspection_tool>
<inspection_tool class="PreviewMultipleParameterProviders" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
</inspection_tool>
<inspection_tool class="PreviewParameterProviderOnFirstParameter" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
</inspection_tool>
<inspection_tool class="PreviewPickerAnnotation" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
</inspection_tool>
</profile>
</component>

6
.idea/kotlinc.xml Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="KotlinJpsPluginSettings">
<option name="version" value="2.0.0" />
</component>
</project>

View File

@ -3,10 +3,5 @@
<component name="FrameworkDetectionExcludesConfiguration">
<file type="web" url="file://$PROJECT_DIR$" />
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="21" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" />
</component>
<component name="ProjectType">
<option name="id" value="Android" />
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="21" project-jdk-type="JavaSDK" />
</project>

View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="RunConfigurationProducerService">
<option name="ignoredProducers">
<set>
<option value="com.intellij.execution.junit.AbstractAllInDirectoryConfigurationProducer" />
<option value="com.intellij.execution.junit.AllInPackageConfigurationProducer" />
<option value="com.intellij.execution.junit.PatternConfigurationProducer" />
<option value="com.intellij.execution.junit.TestInClassConfigurationProducer" />
<option value="com.intellij.execution.junit.UniqueIdConfigurationProducer" />
<option value="com.intellij.execution.junit.testDiscovery.JUnitTestDiscoveryConfigurationProducer" />
<option value="org.jetbrains.kotlin.idea.junit.KotlinJUnitRunConfigurationProducer" />
<option value="org.jetbrains.kotlin.idea.junit.KotlinPatternConfigurationProducer" />
</set>
</option>
</component>
</project>

View File

@ -1,124 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Palette2">
<group name="Swing">
<item class="com.intellij.uiDesigner.HSpacer" tooltip-text="Horizontal Spacer" icon="/com/intellij/uiDesigner/icons/hspacer.svg" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="1" hsize-policy="6" anchor="0" fill="1" />
</item>
<item class="com.intellij.uiDesigner.VSpacer" tooltip-text="Vertical Spacer" icon="/com/intellij/uiDesigner/icons/vspacer.svg" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="1" anchor="0" fill="2" />
</item>
<item class="javax.swing.JPanel" icon="/com/intellij/uiDesigner/icons/panel.svg" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3" />
</item>
<item class="javax.swing.JScrollPane" icon="/com/intellij/uiDesigner/icons/scrollPane.svg" removable="false" auto-create-binding="false" can-attach-label="true">
<default-constraints vsize-policy="7" hsize-policy="7" anchor="0" fill="3" />
</item>
<item class="javax.swing.JButton" icon="/com/intellij/uiDesigner/icons/button.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="3" anchor="0" fill="1" />
<initial-values>
<property name="text" value="Button" />
</initial-values>
</item>
<item class="javax.swing.JRadioButton" icon="/com/intellij/uiDesigner/icons/radioButton.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="3" anchor="8" fill="0" />
<initial-values>
<property name="text" value="RadioButton" />
</initial-values>
</item>
<item class="javax.swing.JCheckBox" icon="/com/intellij/uiDesigner/icons/checkBox.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="3" anchor="8" fill="0" />
<initial-values>
<property name="text" value="CheckBox" />
</initial-values>
</item>
<item class="javax.swing.JLabel" icon="/com/intellij/uiDesigner/icons/label.svg" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="0" anchor="8" fill="0" />
<initial-values>
<property name="text" value="Label" />
</initial-values>
</item>
<item class="javax.swing.JTextField" icon="/com/intellij/uiDesigner/icons/textField.svg" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
<preferred-size width="150" height="-1" />
</default-constraints>
</item>
<item class="javax.swing.JPasswordField" icon="/com/intellij/uiDesigner/icons/passwordField.svg" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
<preferred-size width="150" height="-1" />
</default-constraints>
</item>
<item class="javax.swing.JFormattedTextField" icon="/com/intellij/uiDesigner/icons/formattedTextField.svg" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
<preferred-size width="150" height="-1" />
</default-constraints>
</item>
<item class="javax.swing.JTextArea" icon="/com/intellij/uiDesigner/icons/textArea.svg" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JTextPane" icon="/com/intellij/uiDesigner/icons/textPane.svg" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JEditorPane" icon="/com/intellij/uiDesigner/icons/editorPane.svg" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JComboBox" icon="/com/intellij/uiDesigner/icons/comboBox.svg" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="0" hsize-policy="2" anchor="8" fill="1" />
</item>
<item class="javax.swing.JTable" icon="/com/intellij/uiDesigner/icons/table.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JList" icon="/com/intellij/uiDesigner/icons/list.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="2" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JTree" icon="/com/intellij/uiDesigner/icons/tree.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JTabbedPane" icon="/com/intellij/uiDesigner/icons/tabbedPane.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3">
<preferred-size width="200" height="200" />
</default-constraints>
</item>
<item class="javax.swing.JSplitPane" icon="/com/intellij/uiDesigner/icons/splitPane.svg" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3">
<preferred-size width="200" height="200" />
</default-constraints>
</item>
<item class="javax.swing.JSpinner" icon="/com/intellij/uiDesigner/icons/spinner.svg" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1" />
</item>
<item class="javax.swing.JSlider" icon="/com/intellij/uiDesigner/icons/slider.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1" />
</item>
<item class="javax.swing.JSeparator" icon="/com/intellij/uiDesigner/icons/separator.svg" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3" />
</item>
<item class="javax.swing.JProgressBar" icon="/com/intellij/uiDesigner/icons/progressbar.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="0" fill="1" />
</item>
<item class="javax.swing.JToolBar" icon="/com/intellij/uiDesigner/icons/toolbar.svg" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="0" fill="1">
<preferred-size width="-1" height="20" />
</default-constraints>
</item>
<item class="javax.swing.JToolBar$Separator" icon="/com/intellij/uiDesigner/icons/toolbarSeparator.svg" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="0" anchor="0" fill="1" />
</item>
<item class="javax.swing.JScrollBar" icon="/com/intellij/uiDesigner/icons/scrollbar.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="0" anchor="0" fill="2" />
</item>
</group>
</component>
</project>

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
<mapping directory="" vcs="Git" />
</component>
</project>

View File

@ -1,16 +1,18 @@
plugins {
alias(libs.plugins.android.application)
alias(libs.plugins.kotlin.android)
alias(libs.plugins.kotlin.compose)
alias(libs.plugins.ksp) // NEU
}
android {
namespace = "core.notevault"
compileSdk = 35
namespace = "come.stormborntales.notevault"
compileSdk = 34
defaultConfig {
applicationId = "core.notevault"
minSdk = 28
targetSdk = 35
applicationId = "come.stormborntales.notevault"
minSdk = 24
targetSdk = 34
versionCode = 1
versionName = "1.0"
@ -22,48 +24,43 @@ android {
isMinifyEnabled = false
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
}
debug {
// Setzt den Manifest-Platzhalter nur für den Debug-Build auf true
manifestPlaceholders["usesCleartextTraffic"] = "true"
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
kotlinOptions {
jvmTarget = "11"
}
buildFeatures {
viewBinding = true
compose = true
}
}
dependencies {
implementation(libs.appcompat)
implementation(libs.material)
implementation(libs.constraintlayout)
implementation(libs.lifecycle.livedata.ktx)
implementation(libs.lifecycle.viewmodel.ktx)
implementation(libs.navigation.fragment)
implementation(libs.navigation.ui)
implementation(libs.room.common)
implementation("androidx.recyclerview:recyclerview:1.3.2")
implementation("com.github.chrisbanes:PhotoView:2.3.0")
implementation(libs.car.ui.lib)
implementation(libs.androidx.core.ktx)
implementation(libs.androidx.lifecycle.runtime.ktx)
implementation(libs.androidx.activity.compose)
implementation(platform(libs.androidx.compose.bom))
implementation(libs.androidx.ui)
implementation(libs.androidx.ui.graphics)
implementation(libs.androidx.ui.tooling.preview)
implementation(libs.androidx.material3)
implementation(libs.androidx.navigation.compose)
implementation(libs.androidx.lifecycle.viewmodel.compose)
testImplementation(libs.junit)
androidTestImplementation(libs.ext.junit)
androidTestImplementation(libs.espresso.core)
implementation("com.squareup.retrofit2:retrofit:2.9.0")
implementation("com.squareup.retrofit2:converter-gson:2.9.0")
implementation("com.squareup.okhttp3:logging-interceptor:4.9.1")
val room_version = "2.6.1" // Aktuelle Room-Version
implementation("androidx.room:room-runtime:$room_version")
annotationProcessor("androidx.room:room-compiler:$room_version") // Für Annotation Processing
// Optional: Unterstützung für Kotlin Coroutines oder RxJava
implementation("androidx.room:room-ktx:$room_version") // Für Kotlin-Extensions
implementation("androidx.room:room-rxjava3:$room_version") // Für RxJava-Unterstützung
implementation("androidx.work:work-runtime:2.10.0")
androidTestImplementation(libs.androidx.junit)
androidTestImplementation(libs.androidx.espresso.core)
androidTestImplementation(platform(libs.androidx.compose.bom))
androidTestImplementation(libs.androidx.ui.test.junit4)
debugImplementation(libs.androidx.ui.tooling)
debugImplementation(libs.androidx.ui.test.manifest)
implementation(libs.androidx.foundation)
implementation(libs.androidx.room.runtime)
implementation(libs.androidx.room.ktx)
implementation(libs.lifecycle.livedata.ktx)
implementation(libs.compose.runtime.livedata)
implementation(libs.coil.compose)
ksp(libs.androidx.room.compiler)
}

View File

@ -0,0 +1,24 @@
package come.stormborntales.notevault
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.Assert.*
/**
* Instrumented test, which will execute on an Android device.
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
@RunWith(AndroidJUnit4::class)
class ExampleInstrumentedTest {
@Test
fun useAppContext() {
// Context of the app under test.
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
assertEquals("come.stormborntales.notevault", appContext.packageName)
}
}

View File

@ -1,25 +0,0 @@
package core.notevault;
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 <a href="http://d.android.com/tools/testing">Testing documentation</a>
*/
@RunWith(AndroidJUnit4.class)
public class ExampleInstrumentedTest {
@Test
public void useAppContext() {
// Context of the app under test.
Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
assertEquals("core.notevault", appContext.getPackageName());
}
}

View File

@ -1,8 +1,7 @@
<?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.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.INTERNET" />
<application
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
@ -12,24 +11,19 @@
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.NoteVault"
android:usesCleartextTraffic="true"
tools:targetApi="31">
<activity
android:name=".MainActivity"
android:exported="true"
android:label="@string/app_name"
android:theme="@style/Theme.NoteVault.NoActionBar">
android:theme="@style/Theme.NoteVault">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<activity android:name=".ui.home.FullScreenImageActivity"
android:theme="@style/Theme.AppCompat.NoActionBar">
<!-- Intent Filter hinzufügen, wenn Activity vom Launcher oder externen Apps aufgerufen werden soll -->
</activity>
<activity android:name=".FullscreenImageViewerActivity" />
</application>

Binary file not shown.

After

Width:  |  Height:  |  Size: 197 KiB

View File

@ -0,0 +1,26 @@
package come.stormborntales.notevault
import android.net.Uri
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import come.stormborntales.notevault.ui.screens.FullscreenImageViewer
import come.stormborntales.notevault.ui.theme.NoteVaultTheme
// FullscreenImageViewerActivity.kt
class FullscreenImageViewerActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val imageUris = intent.getParcelableArrayListExtra<Uri>("imageUris") ?: emptyList()
setContent {
NoteVaultTheme {
FullscreenImageViewer(
images = imageUris,
onClose = { finish() }
)
}
}
}
}

View File

@ -0,0 +1,253 @@
package come.stormborntales.notevault
import android.net.Uri
import android.os.Bundle
import android.util.Log
import androidx.activity.ComponentActivity
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.compose.setContent
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.AccountCircle
import androidx.compose.material.icons.filled.Menu
import androidx.compose.material3.DrawerValue
import androidx.compose.material3.DropdownMenu
import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.ModalDrawerSheet
import androidx.compose.material3.ModalNavigationDrawer
import androidx.compose.material3.NavigationDrawerItem
import androidx.compose.material3.NavigationDrawerItemDefaults
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TextField
import androidx.compose.material3.TextFieldDefaults
import androidx.compose.material3.TopAppBar
import androidx.compose.material3.rememberDrawerState
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.currentBackStackEntryAsState
import androidx.navigation.compose.rememberNavController
import come.stormborntales.notevault.data.local.AppDatabase
import come.stormborntales.notevault.data.local.entity.NoteEntity
import come.stormborntales.notevault.data.repository.NoteRepository
import come.stormborntales.notevault.ui.screens.AddNoteDialog
import come.stormborntales.notevault.ui.screens.MainScreen
import come.stormborntales.notevault.ui.screens.NotesScreen
import come.stormborntales.notevault.ui.screens.SettingsScreen
import come.stormborntales.notevault.ui.theme.NoteVaultTheme
import come.stormborntales.notevault.ui.viewmodel.NoteViewModel
import come.stormborntales.notevault.ui.viewmodel.NoteViewModelFactory
import kotlinx.coroutines.launch
class MainActivity : ComponentActivity() {
@OptIn(ExperimentalMaterial3Api::class)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val applicationContext = applicationContext
val database = AppDatabase.getDatabase(applicationContext)
val repository = NoteRepository(database.noteDao())
val viewModelFactory = NoteViewModelFactory(repository)
setContent {
NoteVaultTheme {
val navController = rememberNavController()
val viewModel: NoteViewModel = viewModel(factory = viewModelFactory)
var selectedUris by remember { mutableStateOf<List<Uri>>(emptyList()) }
var showDialog by remember { mutableStateOf(false) }
var noteToEdit by remember { mutableStateOf<NoteEntity?>(null) }
val drawerState = rememberDrawerState(DrawerValue.Closed)
val scope = rememberCoroutineScope()
val imagePickerLauncher = rememberLauncherForActivityResult(
contract = ActivityResultContracts.OpenMultipleDocuments(),
onResult = { uris ->
if (uris.isNotEmpty()) {
selectedUris = uris
showDialog = true
}
}
)
val openDialog: (NoteEntity?) -> Unit = { note ->
Log.d("EditNote", "NoteEntity: ${note?.title}")
noteToEdit = note
showDialog = true
}
val navItems = listOf(
"Home" to "main",
"Notes" to "notes",
"Einstellungen" to "settings"
)
ModalNavigationDrawer(
drawerState = drawerState,
drawerContent = {
ModalDrawerSheet {
Text("Navigation", modifier = Modifier.padding(16.dp), style = MaterialTheme.typography.titleMedium)
navItems.forEach { (label, route) ->
NavigationDrawerItem(
label = { Text(label) },
selected = false,
onClick = {
navController.navigate(route) {
popUpTo(navController.graph.startDestinationId) { saveState = true }
launchSingleTop = true
restoreState = true
}
scope.launch { drawerState.close() }
},
modifier = Modifier.padding(NavigationDrawerItemDefaults.ItemPadding)
)
}
}
}
) {
Scaffold(
topBar = {
var searchQuery by remember { mutableStateOf("") }
var isMenuExpanded by remember { mutableStateOf(false) }
val navBackStackEntry by navController.currentBackStackEntryAsState()
val currentRoute = navBackStackEntry?.destination?.route
TopAppBar(
title = {
if(currentRoute == "main") {
TextField(
value = searchQuery,
onValueChange = {
searchQuery = it
viewModel.searchQuery.value = it
},
placeholder = { Text("Suche...") },
singleLine = true,
modifier = Modifier
.fillMaxWidth()
.padding(end = 48.dp), // Platz für Avatar
textStyle = MaterialTheme.typography.bodyLarge,
colors = TextFieldDefaults.textFieldColors(
containerColor = Color.Transparent,
unfocusedIndicatorColor = Color.Transparent,
focusedIndicatorColor = Color.Transparent
)
)
} else {
Text("NoteVault")
}
},
navigationIcon = {
IconButton(onClick = { scope.launch { drawerState.open() } }) {
Icon(Icons.Default.Menu, contentDescription = "Menü öffnen")
}
},
actions = {
Box {
IconButton(onClick = { isMenuExpanded = true }) {
Icon(
imageVector = Icons.Default.AccountCircle, // Avatar-Icon
contentDescription = "Benutzerprofil"
)
}
DropdownMenu(
expanded = isMenuExpanded,
onDismissRequest = { isMenuExpanded = false }
) {
DropdownMenuItem(
text = { Text("Profil") },
onClick = {
isMenuExpanded = false
// TODO: Navigiere ggf. zu Profilscreen
}
)
DropdownMenuItem(
text = { Text("Abmelden") },
onClick = {
isMenuExpanded = false
// TODO: Logout-Logik
}
)
}
}
}
)
}
) { innerPadding ->
NavHost(
navController = navController,
startDestination = "main",
modifier = Modifier.padding(innerPadding)
) {
composable("main") {
MainScreen(
viewModel = viewModel,
onAddNoteClicked = {
imagePickerLauncher.launch(arrayOf("image/*"))
},
onEditNote = openDialog
)
}
composable("settings") {
SettingsScreen()
}
composable("notes") {
NotesScreen()
}
}
if (showDialog) {
val context = LocalContext.current
AddNoteDialog(
onDismiss = { showDialog = false },
onSave = { title, composer, year, genre, description ->
if (noteToEdit == null) {
viewModel.addNote(
context = context,
title = title,
composer = composer,
year = year,
genre = genre,
description = description,
selectedUris = selectedUris,
onDone = { showDialog = false }
)
} else {
viewModel.updateNote(
editedNote = noteToEdit!!,
updatedTitle = title,
updatedComposer = composer,
updatedYear = year,
updatedGenre = genre,
updatedDescription = description,
onDone = { showDialog = false }
)
}
},
initialTitle = noteToEdit?.title ?: "",
initialComposer = noteToEdit?.composer,
initialYear = noteToEdit?.year,
initialGenre = noteToEdit?.genre,
initialDescription = noteToEdit?.description
)
}
}
}
}
}
}
}

View File

@ -0,0 +1,35 @@
package come.stormborntales.notevault.data.local
import android.content.Context
import android.util.Log
import androidx.room.Database
import androidx.room.Room
import androidx.room.RoomDatabase
import androidx.room.TypeConverters
import come.stormborntales.notevault.data.local.dao.NoteDao
import come.stormborntales.notevault.data.local.entity.NoteCollection
import come.stormborntales.notevault.data.local.entity.NoteEntity
@Database(entities = [NoteEntity::class, NoteCollection::class], version = 1)
@TypeConverters(UriListConverter::class)
abstract class AppDatabase : RoomDatabase() {
abstract fun noteDao(): NoteDao
companion object {
@Volatile
private var INSTANCE: AppDatabase? = null
fun getDatabase(context: Context): AppDatabase {
return INSTANCE ?: synchronized(this) {
val instance = Room.databaseBuilder(
context.applicationContext,
AppDatabase::class.java,
"note_database"
).build()
INSTANCE = instance
Log.d("AppDatabase", "Datenbank erstellt: ${instance.openHelper.writableDatabase}")
instance
}
}
}
}

View File

@ -0,0 +1,11 @@
package come.stormborntales.notevault.data.local
import androidx.room.TypeConverter
class UriListConverter {
@TypeConverter
fun fromList(list: List<String>): String = list.joinToString(",")
@TypeConverter
fun toList(data: String): List<String> = if (data.isBlank()) emptyList() else data.split(",")
}

View File

@ -0,0 +1,25 @@
package come.stormborntales.notevault.data.local.dao
import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import androidx.room.Update
import come.stormborntales.notevault.data.local.entity.NoteEntity
import kotlinx.coroutines.flow.Flow
@Dao
interface NoteDao {
@Query("SELECT * FROM notes")
fun getAll(): Flow<List<NoteEntity>>
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insert(note: NoteEntity)
@Delete
suspend fun delete(note: NoteEntity)
@Update
suspend fun update(note: NoteEntity)
}

View File

@ -0,0 +1,11 @@
package come.stormborntales.notevault.data.local.entity
import androidx.room.Entity
import androidx.room.PrimaryKey
@Entity("note_collections")
data class NoteCollection(
@PrimaryKey(autoGenerate = true) var id: Int = 0,
var name: String,
var parentId: Int? = null
)

View File

@ -0,0 +1,24 @@
package come.stormborntales.notevault.data.local.entity
import androidx.room.Entity
import androidx.room.ForeignKey
import androidx.room.PrimaryKey
@Entity(tableName = "notes",
foreignKeys = [ForeignKey(
entity = NoteCollection::class,
parentColumns = ["id"],
childColumns = ["collectionId"],
onDelete = ForeignKey.CASCADE
)])
data class NoteEntity(
@PrimaryKey(autoGenerate = true) val id: Int = 0,
var title: String,
val images: List<String>, // oder String + TypeConverter
var composer: String?,
var year: Int?,
var genre: String?,
var description: String?,
val imagePreview: String,
val collectionId: Int?
)

View File

@ -0,0 +1,10 @@
package come.stormborntales.notevault.data.model
import android.net.Uri
class NoteEntry(
val title: String, val images: List<Uri>, val composer: String?, val year: Int?,
val genre: String?, val description: String?
) {
}

View File

@ -0,0 +1,14 @@
package come.stormborntales.notevault.data.repository
import come.stormborntales.notevault.data.local.dao.NoteDao
import come.stormborntales.notevault.data.local.entity.NoteEntity
import kotlinx.coroutines.flow.Flow
class NoteRepository(private val dao: NoteDao) {
val allNotes: Flow<List<NoteEntity>> = dao.getAll()
suspend fun insert(note: NoteEntity) = dao.insert(note)
suspend fun delete(note: NoteEntity) = dao.delete(note)
suspend fun update(note: NoteEntity) = dao.update(note);
}

View File

@ -0,0 +1,126 @@
package come.stormborntales.notevault.ui.screens
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.*
import androidx.compose.ui.unit.dp
@Composable
fun AddNoteDialog(
onDismiss: () -> Unit,
onSave: (title: String, composer: String?, year: Int?, genre: String?, description: String?) -> Unit,
initialTitle: String = "",
initialComposer: String? = null,
initialYear: Int? = null,
initialGenre: String? = null,
initialDescription: String? = null
) {
var title by remember { mutableStateOf(initialTitle) }
var composer by remember { mutableStateOf(initialComposer ?: "") }
var yearText by remember { mutableStateOf(initialYear?.toString() ?: "") }
var genre by remember { mutableStateOf(initialGenre ?: "") }
var description by remember { mutableStateOf(initialDescription ?: "") }
var showTitleError by remember { mutableStateOf(false) }
AlertDialog(
onDismissRequest = onDismiss,
confirmButton = {
TextButton(onClick = {
if (title.isBlank()) {
showTitleError = true
} else {
val year = yearText.toIntOrNull()
onSave(
title.trim(),
composer.takeIf { it.isNotBlank() },
year,
genre.takeIf { it.isNotBlank() },
description.takeIf { it.isNotBlank() }
)
}
}) {
Text("Speichern")
}
},
dismissButton = {
TextButton(onClick = onDismiss) {
Text("Abbrechen")
}
},
title = {
Text("Notenblatt hinzufügen", style = MaterialTheme.typography.titleLarge)
},
text = {
Column(modifier = Modifier.verticalScroll(rememberScrollState())) {
OutlinedTextField(
value = title,
onValueChange = {
title = it
if (it.isNotBlank()) showTitleError = false
},
label = { Text("Titel*") },
isError = showTitleError,
singleLine = true,
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 4.dp)
)
if (showTitleError) {
Text(
"Titel darf nicht leer sein",
color = MaterialTheme.colorScheme.error,
style = MaterialTheme.typography.bodySmall,
modifier = Modifier.padding(start = 16.dp, bottom = 4.dp)
)
}
OutlinedTextField(
value = composer,
onValueChange = { composer = it },
label = { Text("Komponist (optional)") },
singleLine = true,
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 4.dp)
)
OutlinedTextField(
value = yearText,
onValueChange = { yearText = it.filter { c -> c.isDigit() } },
label = { Text("Erscheinungsjahr (optional)") },
singleLine = true,
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 4.dp)
)
OutlinedTextField(
value = genre,
onValueChange = { genre = it },
label = { Text("Genre (optional)") },
singleLine = true,
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 4.dp)
)
OutlinedTextField(
value = description,
onValueChange = { description = it },
label = { Text("Beschreibung (optional)") },
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 4.dp),
maxLines = 4
)
}
}
)
}

View File

@ -0,0 +1,58 @@
package come.stormborntales.notevault.ui.screens
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.pager.HorizontalPager
import androidx.compose.foundation.pager.rememberPagerState
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.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import android.net.Uri
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.material.icons.filled.Close
import androidx.compose.ui.layout.ContentScale
import kotlinx.coroutines.launch
@OptIn(ExperimentalFoundationApi::class)
@Composable
fun FullscreenImageViewer(
images: List<Uri>,
onClose: () -> Unit
) {
val pagerState = rememberPagerState(pageCount = { images.size })
Box(modifier = Modifier.fillMaxSize()) {
HorizontalPager(state = pagerState) { page ->
val context = LocalContext.current
val imageBitmap = remember(images[page]) {
loadImageBitmap(context, images[page].toString())
}
imageBitmap?.let {
Image(
bitmap = it,
contentDescription = "Notenbild",
modifier = Modifier
.fillMaxSize()
.background(Color.Black),
contentScale = ContentScale.Fit
)
}
}
IconButton(
onClick = onClose,
modifier = Modifier
.align(Alignment.TopStart)
.padding(16.dp)
) {
Icon(Icons.Default.Close, contentDescription = "Schließen", tint = Color.White)
}
}
}

View File

@ -0,0 +1,269 @@
package come.stormborntales.notevault.ui.screens
import android.content.Context
import android.content.Intent
import android.graphics.BitmapFactory
import android.graphics.ImageDecoder
import android.media.Image
import android.net.Uri
import android.util.Log
import androidx.compose.foundation.Image
import androidx.compose.foundation.horizontalScroll
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Add
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import come.stormborntales.notevault.FullscreenImageViewerActivity
import come.stormborntales.notevault.data.local.entity.NoteEntity
import come.stormborntales.notevault.data.model.NoteEntry
import come.stormborntales.notevault.ui.viewmodel.NoteViewModel
import java.io.InputStream
import androidx.core.net.toUri
import androidx.lifecycle.viewmodel.compose.viewModel
import coil.compose.AsyncImage
fun loadImageBitmap(context: Context, uriString: String): ImageBitmap? {
return try {
val uri = uriString.toUri()
val inputStream: InputStream? = context.contentResolver.openInputStream(uri)
val bitmap = BitmapFactory.decodeStream(inputStream)
bitmap?.asImageBitmap()
} catch (e: Exception) {
e.printStackTrace()
null
}
}@Composable
fun NoteCard(note: NoteEntity, onDeleteNote: (NoteEntity) -> Unit, onEditNote: (NoteEntity) -> Unit) {
val context = LocalContext.current
val screenWidth = LocalConfiguration.current.screenWidthDp // Bildschirmbreite in dp
// Dynamische Bildgröße basierend auf der Bildschirmbreite
val imageSize = if (screenWidth < 400) 80.dp else 120.dp
Card(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 8.dp),
shape = RoundedCornerShape(16.dp),
elevation = CardDefaults.cardElevation(defaultElevation = 4.dp),
) {
// Responsive Layout: Überprüfen, ob der Bildschirm schmaler als 360 dp ist
if (screenWidth < 400) {
// Wenn der Bildschirm schmal ist, arrangiere die Elemente vertikal
Row(modifier = Modifier.padding(16.dp)) {
AsyncImage(
model = note.imagePreview,
contentDescription = "Vorschaubild",
modifier = Modifier
.size(imageSize)
.clip(RoundedCornerShape(12.dp))
)
Column(modifier = Modifier.padding(16.dp)) {
Text(
text = note.title,
style = MaterialTheme.typography.titleMedium
)
note.composer?.let {
Text("von $it", style = MaterialTheme.typography.labelMedium)
}
Spacer(modifier = Modifier.height(4.dp))
note.year?.let {
Text("Jahr: $it", style = MaterialTheme.typography.bodySmall)
}
note.genre?.let {
Text("Genre: $it", style = MaterialTheme.typography.bodySmall)
}
note.description?.let {
Text("Beschreibung: $it", style = MaterialTheme.typography.bodySmall, maxLines = 2)
}
Spacer(modifier = Modifier.height(16.dp)) // Abstand zwischen Text und Buttons
}
}
// Buttons unter dem Text
Row(
horizontalArrangement = Arrangement.spacedBy(8.dp),
modifier = Modifier.fillMaxWidth().padding(16.dp),
) {
OutlinedButton(
onClick = {
val uris = ArrayList<Uri>()
note.images.forEach { uris.add(it.toUri()) }
val intent = Intent(context, FullscreenImageViewerActivity::class.java).apply {
putParcelableArrayListExtra("imageUris", ArrayList(uris))
}
context.startActivity(intent)
},
contentPadding = PaddingValues(horizontal = 12.dp, vertical = 4.dp)
) {
Text("Anzeigen", style = MaterialTheme.typography.labelLarge)
}
OutlinedButton(
onClick = {
onEditNote(note)
},
contentPadding = PaddingValues(horizontal = 12.dp, vertical = 4.dp)
) {
Text("Bearbeiten", style = MaterialTheme.typography.labelLarge)
}
OutlinedButton(
onClick = {
onDeleteNote(note)
},
colors = ButtonDefaults.outlinedButtonColors(
contentColor = MaterialTheme.colorScheme.error
),
contentPadding = PaddingValues(horizontal = 12.dp, vertical = 4.dp)
) {
Text("Löschen", style = MaterialTheme.typography.labelLarge)
}
}
} else {
// Wenn der Bildschirm breiter als 360 dp ist, arrangiere die Elemente nebeneinander
Row(modifier = Modifier.padding(16.dp)) {
// Linkes Vorschaubild
AsyncImage(
model = note.imagePreview,
contentDescription = "Vorschaubild",
modifier = Modifier
.size(imageSize)
.clip(RoundedCornerShape(12.dp))
)
// Rechte Info-Spalte
Column(
modifier = Modifier
.weight(1f) // Damit die Spalte den verfügbaren Platz nutzt
.align(Alignment.CenterVertically)
.padding(start = 16.dp)
) {
Text(
text = note.title,
style = MaterialTheme.typography.titleMedium
)
note.composer?.let {
Text("von $it", style = MaterialTheme.typography.labelMedium)
}
Spacer(modifier = Modifier.height(4.dp))
note.year?.let {
Text("Jahr: $it", style = MaterialTheme.typography.bodySmall)
}
note.genre?.let {
Text("Genre: $it", style = MaterialTheme.typography.bodySmall)
}
note.description?.let {
Text("Beschreibung: $it", style = MaterialTheme.typography.bodySmall, maxLines = 2)
}
Spacer(modifier = Modifier.height(8.dp))
// Buttons in einer Row anordnen
Row(
horizontalArrangement = Arrangement.spacedBy(8.dp),
modifier = Modifier.fillMaxWidth() // Stellt sicher, dass die Row den gesamten verfügbaren Platz einnimmt
) {
OutlinedButton(
onClick = {
val uris = ArrayList<Uri>()
note.images.forEach { uris.add(it.toUri()) }
val intent = Intent(context, FullscreenImageViewerActivity::class.java).apply {
putParcelableArrayListExtra("imageUris", ArrayList(uris))
}
context.startActivity(intent)
},
contentPadding = PaddingValues(horizontal = 12.dp, vertical = 4.dp)
) {
Text("Anzeigen", style = MaterialTheme.typography.labelLarge)
}
OutlinedButton(
onClick = {
onEditNote(note)
},
contentPadding = PaddingValues(horizontal = 12.dp, vertical = 4.dp)
) {
Text("Bearbeiten", style = MaterialTheme.typography.labelLarge)
}
OutlinedButton(
onClick = {
onDeleteNote(note)
},
colors = ButtonDefaults.outlinedButtonColors(
contentColor = MaterialTheme.colorScheme.error
),
contentPadding = PaddingValues(horizontal = 12.dp, vertical = 4.dp)
) {
Text("Löschen", style = MaterialTheme.typography.labelLarge)
}
}
}
}
}
}
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun MainScreen(
onAddNoteClicked: () -> Unit, // Übergib hier deine Logik für den Import
viewModel: NoteViewModel,
onEditNote: (NoteEntity) -> Unit
) {
val notes by viewModel.filteredNotes.collectAsState(initial = emptyList())
Scaffold(
floatingActionButton = {
FloatingActionButton(
onClick = onAddNoteClicked
) {
Icon(Icons.Default.Add, contentDescription = "Neue Noten hinzufügen")
}
},
floatingActionButtonPosition = FabPosition.End
) { innerPadding ->
LazyColumn(
contentPadding = innerPadding,
modifier = Modifier
.fillMaxSize()
.padding(16.dp)
) {
items(notes) { note ->
NoteCard(note = note, onDeleteNote = { noteEntity ->
viewModel.deleteNote(noteEntity)
}, onEditNote = onEditNote)
}
}
}
}

View File

@ -0,0 +1,24 @@
package come.stormborntales.notevault.ui.screens
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Divider
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.Switch
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
@Composable
fun NotesScreen() {
}

View File

@ -0,0 +1,79 @@
package come.stormborntales.notevault.ui.screens
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Divider
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.Switch
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
@Composable
fun SettingsScreen() {
Column(
modifier = Modifier
.fillMaxSize()
.padding(16.dp)
) {
Text(
text = "Einstellungen",
style = MaterialTheme.typography.headlineMedium,
modifier = Modifier.padding(bottom = 16.dp)
)
Divider()
Spacer(modifier = Modifier.height(16.dp))
// Beispielhafte Einstellung
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.fillMaxWidth()
) {
Text(
text = "Dark Mode",
modifier = Modifier.weight(1f),
style = MaterialTheme.typography.bodyLarge
)
Switch(
checked = false,
onCheckedChange = { /* TODO: Dark Mode aktivieren */ }
)
}
Spacer(modifier = Modifier.height(16.dp))
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.fillMaxWidth()
) {
Text(
text = "Benachrichtigungen",
modifier = Modifier.weight(1f),
style = MaterialTheme.typography.bodyLarge
)
Switch(
checked = true,
onCheckedChange = { /* TODO: Benachrichtigungseinstellungen */ }
)
}
Spacer(modifier = Modifier.height(32.dp))
OutlinedButton(
onClick = { /* TODO: Impressum anzeigen */ },
modifier = Modifier.fillMaxWidth()
) {
Text("Impressum")
}
}
}

View File

@ -0,0 +1,11 @@
package come.stormborntales.notevault.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)

View File

@ -0,0 +1,58 @@
package come.stormborntales.notevault.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.ui.platform.LocalContext
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 NoteVaultTheme(
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
}
MaterialTheme(
colorScheme = colorScheme,
typography = Typography,
content = content
)
}

View File

@ -0,0 +1,34 @@
package come.stormborntales.notevault.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
)
*/
)

View File

@ -0,0 +1,134 @@
package come.stormborntales.notevault.ui.viewmodel
import android.content.Context
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.net.Uri
import android.util.Log
import android.webkit.MimeTypeMap
import androidx.lifecycle.ViewModel
import androidx.lifecycle.asLiveData
import androidx.lifecycle.viewModelScope
import come.stormborntales.notevault.data.local.entity.NoteEntity
import come.stormborntales.notevault.data.repository.NoteRepository
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import java.io.File
import androidx.core.graphics.scale
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.stateIn
class NoteViewModel(
private val repository: NoteRepository,
) : ViewModel() {
// Sucheingabe als StateFlow
val searchQuery = MutableStateFlow("")
// Alle Notizen als Flow aus der Datenbank (NICHT blockierend!)
private val allNotes: Flow<List<NoteEntity>> = repository.allNotes
// Gefilterte Notizen basierend auf Sucheingabe
val filteredNotes: StateFlow<List<NoteEntity>> = combine(searchQuery, allNotes) { query, notes ->
if (query.isBlank()) {
notes
} else {
notes.filter {
it.title.contains(query, ignoreCase = true) ||
it.description?.contains(query, ignoreCase = true) == true
}
}
}.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), emptyList())
fun createPreviewImage(context: Context, uri: Uri): File? {
return try {
val inputStream = context.contentResolver.openInputStream(uri)
val originalBitmap = BitmapFactory.decodeStream(inputStream) ?: return null
val previewBitmap = originalBitmap.scale(128, 128, false)
val previewFile = File(context.filesDir, "preview_${System.currentTimeMillis()}.jpg")
previewFile.outputStream().use { out ->
previewBitmap.compress(Bitmap.CompressFormat.JPEG, 75, out)
}
previewFile
} catch (e: Exception) {
e.printStackTrace()
null
}
}
fun addNote(context: Context, title: String, composer: String?, year: Int?, genre: String?, description: String?, selectedUris: List<Uri>, onDone:() -> Unit) {
viewModelScope.launch(Dispatchers.IO) {
val copiedUris = selectedUris.mapNotNull { uri ->
try {
val inputStream = context.contentResolver.openInputStream(uri)
val extension = MimeTypeMap.getSingleton()
.getExtensionFromMimeType(context.contentResolver.getType(uri)) ?: "jpg"
val outputFile = File(context.filesDir, "note_${System.currentTimeMillis()}.$extension")
inputStream?.use { input ->
outputFile.outputStream().use { output ->
input.copyTo(output)
}
}
Log.d("NoteViewModel", "NoteEntityFile" + outputFile.absolutePath)
Uri.fromFile(outputFile).toString() // speichern als String
} catch (e: Exception) {
e.printStackTrace()
null
}
}
if (copiedUris.isNotEmpty()) {
val preview_image_path = createPreviewImage(context, uri = selectedUris[0])
val note = NoteEntity(
title = title,
images = copiedUris, // muss als List<String> gespeichert sein
composer = composer,
year = year,
genre = genre,
description = description,
imagePreview = preview_image_path.toString(),
collectionId = null
)
repository.insert(note)
}
onDone()
}
}
fun deleteNote(note: NoteEntity) {
viewModelScope.launch {
repository.delete(note)
}
}
fun updateNote(
editedNote: NoteEntity,
updatedTitle: String,
updatedComposer: String?,
updatedYear: Int?,
updatedGenre: String?,
updatedDescription: String?,
onDone: () -> Unit
) {
viewModelScope.launch {
editedNote.title = updatedTitle
editedNote.year = updatedYear;
editedNote.composer = updatedComposer
editedNote.genre = updatedGenre
editedNote.description = updatedDescription
repository.update(editedNote)
onDone()
}
}
}

View File

@ -0,0 +1,17 @@
package come.stormborntales.notevault.ui.viewmodel
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import come.stormborntales.notevault.data.repository.NoteRepository
class NoteViewModelFactory(
private val repository: NoteRepository
) : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
if (modelClass.isAssignableFrom(NoteViewModel::class.java)) {
@Suppress("UNCHECKED_CAST")
return NoteViewModel(repository) as T
}
throw IllegalArgumentException("Unknown ViewModel class")
}
}

View File

@ -1,308 +0,0 @@
package core.notevault;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.text.TextUtils;
import android.util.Log;
import android.view.MenuItem;
import android.view.View;
import android.view.Menu;
import android.widget.Toast;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.navigation.fragment.NavHostFragment;
import androidx.work.OneTimeWorkRequest;
import androidx.work.WorkManager;
import com.google.android.material.snackbar.Snackbar;
import com.google.android.material.navigation.NavigationView;
import androidx.navigation.NavController;
import androidx.navigation.Navigation;
import androidx.navigation.ui.AppBarConfiguration;
import androidx.navigation.ui.NavigationUI;
import androidx.drawerlayout.widget.DrawerLayout;
import androidx.appcompat.app.AppCompatActivity;
import core.notevault.data.*;
import core.notevault.databinding.ActivityMainBinding;
import core.notevault.sync.SyncWorker;
import core.notevault.sync.auth.AuthRepository;
import core.notevault.sync.auth.LoginCallback;
import core.notevault.ui.gallery.GalleryFragment;
import core.notevault.ui.gallery.detail.ConcertSongSelector;
import core.notevault.ui.gallery.editor.ConcertEditorDialog;
import core.notevault.ui.home.HomeFragment;
import core.notevault.ui.login.LoginCallBackImpl;
import core.notevault.ui.login.LoginDialogFragment;
import core.notevault.ui.login.RegisterCallback;
import core.notevault.ui.metadatadialog.MetaDataDialog;
import core.notevault.ui.metadatadialog.SongEditDialog;
import core.notevault.util.NoteSheetsUtil;
import java.io.*;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.concurrent.TimeUnit;
public class MainActivity extends AppCompatActivity implements MetaDataDialog.OnMetadataListener,
ConcertEditorDialog.OnConcertEditorListener, ConcertSongSelector.OnSongSelectedListener, LoginCallback, SongEditDialog.SongEditorListener {
private AppBarConfiguration mAppBarConfiguration;
private ActivityMainBinding binding;
private MusicDatabase musicDB;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = ActivityMainBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
setSupportActionBar(binding.appBarMain.toolbar);
DrawerLayout drawer = binding.drawerLayout;
NavigationView navigationView = binding.navView;
// Passing each menu ID as a set of Ids because each
// menu should be considered as top level destinations.
mAppBarConfiguration = new AppBarConfiguration.Builder(
R.id.nav_home, R.id.nav_gallery, R.id.nav_slideshow)
.setOpenableLayout(drawer)
.build();
NavController navController = Navigation.findNavController(this, R.id.nav_host_fragment_content_main);
NavigationUI.setupActionBarWithNavController(this, navController, mAppBarConfiguration);
NavigationUI.setupWithNavController(navigationView, navController);
setupLoginButton();
musicDB = MusicDatabase.getDatabase(this);
scheduleSync();
}
public void scheduleSync() {
// Setze eine Uhrzeit für die Synchronisation
Calendar calendar = Calendar.getInstance();
calendar.set(Calendar.HOUR_OF_DAY,11);
calendar.set(Calendar.MINUTE, 32);
calendar.set(Calendar.SECOND, 0);
long triggerTime = calendar.getTimeInMillis(); // Zeitstempel für den Trigger
// Erstelle die OneTimeWorkRequest für den SyncWorker
OneTimeWorkRequest syncRequest = new OneTimeWorkRequest.Builder(SyncWorker.class)
.setInitialDelay(triggerTime - System.currentTimeMillis(), TimeUnit.MILLISECONDS) // Verzögerung bis zur Ausführung
.build();
// Planen des Workers mit WorkManager
WorkManager.getInstance(getBaseContext()).enqueue(syncRequest);
}
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
MenuItem loginItem = menu.findItem(R.id.action_login);
if (isLoggedIn()) {
loginItem.setIcon(R.drawable.logout); // Setze das Logout-Symbol
} else {
loginItem.setIcon(R.drawable.login); // Setze das Login-Symbol
}
return super.onPrepareOptionsMenu(menu);
}
public void updateLoginButton() {
invalidateOptionsMenu(); // Menü neu zeichnen
}
private void setupLoginButton() {
binding.appBarMain.toolbar.setOnMenuItemClickListener(item -> {
if (item.getItemId() == R.id.action_login) {
if (isLoggedIn()) {
performLogout(); // Logout-Logik aufrufen
} else {
showLoginDialog(); // Zeige das Login-Fenster an
}
return true;
}
return false;
});
}
private boolean isLoggedIn() {
AuthRepository authRepository = new AuthRepository(this);
return !TextUtils.isEmpty(authRepository.getToken());
}
private void showLoginDialog() {
LoginDialogFragment loginDialogFragment = LoginDialogFragment.newInstance(new LoginCallBackImpl(this), new RegisterCallback(this));
loginDialogFragment.show(getSupportFragmentManager(), "login");
}
private void performLogout() {
// Füge hier deine Logout-Logik hinzu
AuthRepository authRepository = new AuthRepository(this);
authRepository.logout(); // Implementiere die Token-Löschung
updateLoginButton();
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
if(id == R.id.action_login) {
openLoginDialog();
return true;
}
return super.onOptionsItemSelected(item);
}
private void openLoginDialog() {
LoginDialogFragment loginDialogFragment = new LoginDialogFragment();
loginDialogFragment.show(getSupportFragmentManager(), "LOGIN_TAG");
}
@Override
public boolean onSupportNavigateUp() {
NavController navController = Navigation.findNavController(this, R.id.nav_host_fragment_content_main);
return NavigationUI.navigateUp(navController, mAppBarConfiguration)
|| super.onSupportNavigateUp();
}
@Override
public void onMetadataEntered(Uri[] uris, String title, String composer, int year, String genre) {
new Thread(() -> {
MusicNote musicNote = new MusicNote(title, composer, year, genre);
long musicNoteID = musicDB.musicNoteDao().insert(musicNote);
musicNote.setMusicNoteId(musicNoteID);
Log.d("MainActivity", "MusicNoteID of inserted song: " + musicNoteID);
Log.d("MainActivity", "MusicNoteID of referenced song: " + musicNote.getMusicNoteId());
for(Uri uri: uris) {
String filePath = saveImageInternally(uri);
NoteSheet noteSheet = new NoteSheet(musicNoteID, filePath);
musicDB.musicNoteDao().insertNoteSheet(noteSheet);
}
runOnUiThread(() -> {
NavHostFragment navHostFragment = (NavHostFragment) getSupportFragmentManager().findFragmentById(R.id.nav_host_fragment_content_main);
Fragment currentFragment = navHostFragment != null ? navHostFragment.getChildFragmentManager().getFragments().get(0) : null;
if(currentFragment instanceof HomeFragment) {
HomeFragment homeFragment = (HomeFragment) currentFragment;
homeFragment.addSong(musicNote);
}
});
}).start();
}
// Speichere eine Kopie des Bilds im internen Speicher
private String saveImageInternally(Uri uri) {
try (InputStream inputStream = getContentResolver().openInputStream(uri)) {
File imageFile = new File(getFilesDir(), "saved_image_" + System.currentTimeMillis() + ".jpg");
try (OutputStream outputStream = Files.newOutputStream(imageFile.toPath())) {
byte[] buffer = new byte[4096];
int bytesRead;
while ((bytesRead = inputStream.read(buffer)) != -1) {
outputStream.write(buffer, 0, bytesRead);
}
}
return imageFile.getAbsolutePath(); // Pfad speichern, um später darauf zuzugreifen
} catch (IOException e) {
e.printStackTrace();
return null;
}
}
@Override
public void onConcertEditorClosed(String concertTitle, String concertDate, int concertID) {
new Thread(() -> {
if(concertID < 0) {
Concert concert = new Concert(concertTitle, concertDate);
Log.d("ConcertEditor", "Saved Concert: " + concertTitle + " on " + concertDate);
musicDB.musicNoteDao().insertConcert(concert);
runOnUiThread(() -> {
NavHostFragment navHostFragment = (NavHostFragment) getSupportFragmentManager().findFragmentById(R.id.nav_host_fragment_content_main);
Fragment currentFragment = navHostFragment != null ? navHostFragment.getChildFragmentManager().getFragments().get(0) : null;
Log.d("MainActivity", "Test for GalleryFragment");
Log.d("MainActivity", "Current Fragment: " + currentFragment.getClass().getSimpleName());
if(currentFragment instanceof GalleryFragment) {
Log.d("MainActivity", "GalleryFragment Found");
GalleryFragment galleryFragment = (GalleryFragment) currentFragment;
galleryFragment.addConcert(concert);
} else {
Log.d("MainActivity", "GalleryFragment Not Found");
}
});
} else {
Concert concert = new Concert(concertID, concertTitle, concertDate);
musicDB.musicNoteDao().updateConcert(concert);
runOnUiThread(() -> {
NavHostFragment navHostFragment = (NavHostFragment) getSupportFragmentManager().findFragmentById(R.id.nav_host_fragment_content_main);
Fragment currentFragment = navHostFragment != null ? navHostFragment.getChildFragmentManager().getFragments().get(0) : null;
Log.d("MainActivity", "Test for GalleryFragment");
Log.d("MainActivity", "Current Fragment: " + currentFragment.getClass().getSimpleName());
if(currentFragment instanceof GalleryFragment) {
Log.d("MainActivity", "GalleryFragment Found");
GalleryFragment galleryFragment = (GalleryFragment) currentFragment;
galleryFragment.updateConcert(concert);
} else {
Log.d("MainActivity", "GalleryFragment Not Found");
}
});
}
}).start();
}
@Override
public void onSongsSelected(List<MusicNote> songs, int concertID) {
Log.d("MainActivity", "Inserted Songs: " + songs.size());
new Thread(() -> {
for(MusicNote musicNote : songs) {
ConcertSong concertSong = new ConcertSong(musicNote.getMusicNoteId(), concertID);
musicDB.musicNoteDao().insertConcertSong(concertSong);
Log.d("MainActivity", "Insert Song: " + musicNote.getTitle());
}
}).start();
}
@Override
public void onSuccess() {
updateLoginButton();
}
@Override
public void onError(String error) {
Toast.makeText(this, error, Toast.LENGTH_LONG).show();
Log.d("LoginError", error);
}
@Override
public void onSongEdited(MusicNote updatedSong) {
new Thread(() -> {
musicDB.musicNoteDao().updateSong(updatedSong);
Log.d("MainActivity", "Updated Song: " + updatedSong.getTitle());
}).start();
NavHostFragment navHostFragment = (NavHostFragment) getSupportFragmentManager().findFragmentById(R.id.nav_host_fragment_content_main);
Fragment currentFragment = navHostFragment != null ? navHostFragment.getChildFragmentManager().getFragments().get(0) : null;
if(currentFragment instanceof HomeFragment) {
HomeFragment homeFragment = (HomeFragment) currentFragment;
homeFragment.updateSong(updatedSong);
} else if(currentFragment instanceof GalleryFragment) {
GalleryFragment galleryFragment = (GalleryFragment) currentFragment;
}
}
}

View File

@ -1,90 +0,0 @@
package core.notevault.data;
import androidx.room.*;
import core.notevault.data.sync.SyncStatus;
import core.notevault.data.sync.SyncStatusConverter;
import java.text.SimpleDateFormat;
import java.time.LocalDateTime;
import java.util.Date;
import java.util.Locale;
@Entity(tableName = "concerts")
@TypeConverters(SyncStatusConverter.class)
public class Concert {
@PrimaryKey(autoGenerate = true)
private int id;
private String serverSpecificConcertID;
private String title;
private String concertDate;
private SyncStatus syncStatus;
private LocalDateTime lastModified;
@Ignore
public Concert(String title, String concertDate) {
this.title = title;
this.concertDate = concertDate;
this.lastModified = LocalDateTime.now();
}
public Concert() {
}
@Ignore
public Concert(int id, String title, String concertDate) {
this.id = id;
this.title = title;
this.concertDate = concertDate;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getConcertDate() {
return concertDate;
}
public void setConcertDate(String concertDate) {
this.concertDate = concertDate;
}
public String getServerSpecificConcertID() {
return serverSpecificConcertID;
}
public void setServerSpecificConcertID(String serverSpecificConcertID) {
this.serverSpecificConcertID = serverSpecificConcertID;
}
public SyncStatus getSyncStatus() {
return syncStatus;
}
public void setSyncStatus(SyncStatus syncStatus) {
this.syncStatus = syncStatus;
}
public LocalDateTime getLastModified() {
return lastModified;
}
public void setLastModified(LocalDateTime lastModified) {
this.lastModified = lastModified;
}
}

View File

@ -1,55 +0,0 @@
package core.notevault.data;
import androidx.room.Entity;
import androidx.room.Ignore;
import androidx.room.PrimaryKey;
@Entity(tableName = "concert_songs")
public class ConcertSong {
@PrimaryKey(autoGenerate = true)
private int id;
private long musicNoteID;
private int concertID;
@Ignore
public ConcertSong(long musicNoteID, int concertID) {
this.musicNoteID = musicNoteID;
this.concertID = concertID;
}
@Ignore
public ConcertSong(int id, long musicNoteID, int concertID) {
this.id = id;
this.musicNoteID = musicNoteID;
this.concertID = concertID;
}
public ConcertSong() {
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public long getMusicNoteID() {
return musicNoteID;
}
public void setMusicNoteID(long musicNoteID) {
this.musicNoteID = musicNoteID;
}
public int getConcertID() {
return concertID;
}
public void setConcertID(int concertID) {
this.concertID = concertID;
}
}

View File

@ -1,20 +0,0 @@
package core.notevault.data;
import androidx.room.TypeConverter;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
public class DateConverter {
// Konvertiere LocalDateTime in String
@TypeConverter
public static String fromLocalDateTime(LocalDateTime localDateTime) {
return localDateTime == null ? null : localDateTime.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME);
}
// Konvertiere String zurück in LocalDateTime
@TypeConverter
public static LocalDateTime toLocalDateTime(String dateTimeString) {
return dateTimeString == null ? null : LocalDateTime.parse(dateTimeString, DateTimeFormatter.ISO_LOCAL_DATE_TIME);
}
}

View File

@ -1,27 +0,0 @@
package core.notevault.data;
import android.content.Context;
import androidx.room.*;
import core.notevault.data.sync.SyncDataObject;
import core.notevault.data.sync.SyncStatusConverter;
@Database(entities = {MusicNote.class, NoteSheet.class, Concert.class, ConcertSong.class, SyncDataObject.class}, version = 2, exportSchema = false)
@TypeConverters(DateConverter.class)
public abstract class MusicDatabase extends RoomDatabase {
public abstract MusicNoteDAO musicNoteDao();
private static MusicDatabase INSTANCE;
public static MusicDatabase getDatabase(final Context context) {
if (INSTANCE == null) {
synchronized (MusicDatabase.class) {
if (INSTANCE == null) {
INSTANCE = Room.databaseBuilder(context.getApplicationContext(),
MusicDatabase.class, "music_database")
.build();
}
}
}
return INSTANCE;
}
}

View File

@ -1,107 +0,0 @@
package core.notevault.data;
import androidx.room.Entity;
import androidx.room.Ignore;
import androidx.room.PrimaryKey;
import core.notevault.data.sync.SyncStatus;
import java.time.LocalDateTime;
@Entity(tableName = "music_notes")
public class MusicNote {
@PrimaryKey(autoGenerate = true)
private long musicNoteId;
private String serverID;
private SyncStatus syncStatus;
private LocalDateTime last_sync;
private String title;
private String composer;
private int year;
private String genre;
@Ignore
public MusicNote(String title, String composer, int year, String genre) {
this.title = title;
this.composer = composer;
this.year = year;
this.genre = genre;
this.syncStatus = SyncStatus.CREATED;
}
public MusicNote(long musicNoteId, String title) {
this.musicNoteId = musicNoteId;
this.title = title;
this.syncStatus = SyncStatus.CREATED;
}
@Ignore
public MusicNote() {
}
public long getMusicNoteId() {
return musicNoteId;
}
public void setMusicNoteId(long musicNoteId) {
this.musicNoteId = musicNoteId;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getComposer() {
return composer;
}
public void setComposer(String composer) {
this.composer = composer;
}
public String getGenre() {
return genre;
}
public void setGenre(String genre) {
this.genre = genre;
}
public int getYear() {
return year;
}
public void setYear(int year) {
this.year = year;
}
public String getServerID() {
return serverID;
}
public void setServerID(String serverID) {
this.serverID = serverID;
}
public SyncStatus getSyncStatus() {
return syncStatus;
}
public void setSyncStatus(SyncStatus syncStatus) {
this.syncStatus = syncStatus;
}
public LocalDateTime getLast_sync() {
return last_sync;
}
public void setLast_sync(LocalDateTime last_sync) {
this.last_sync = last_sync;
}
}

View File

@ -1,89 +0,0 @@
package core.notevault.data;
import androidx.room.*;
import core.notevault.data.sync.SyncDataObject;
import core.notevault.data.sync.SyncStatus;
import core.notevault.sync.synchronisation.songs.creation.SongCreationResponse;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Optional;
@Dao
public interface MusicNoteDAO {
@Insert
long insert(MusicNote musicNote);
@Insert
void insertNoteSheet(NoteSheet noteSheet);
@Query("SELECT * FROM music_notes WHERE syncStatus != 'DELETED'")
List<MusicNote> getAllNotes();
@Query("SELECT * FROM note_sheets WHERE musicNoteId = :musicNoteId")
List<NoteSheet> getNoteSheetsForMusicSong(long musicNoteId);
@Query("SELECT * FROM concerts WHERE syncStatus = :status")
List<Concert> getConcertsWithSyncStatus(SyncStatus status);
@Query("SELECT * FROM music_notes WHERE syncStatus = :status")
List<MusicNote> getSongsWithSyncStatus(SyncStatus status);
@Insert
void insertConcert(Concert concert);
@Query("SELECT * FROM concerts")
List<Concert> getAllConcerts();
@Delete
void deleteConcert(Concert concert);
@Update
void updateConcert(Concert concert);
@Delete
void deleteSong(MusicNote musicNote);
@Insert
void insertConcertSong(ConcertSong concertSong);
@Query("SELECT m.* FROM music_notes m JOIN concert_songs cs ON m.musicNoteId = cs.musicNoteID WHERE cs.id = :concertID")
List<MusicNote> getAllMusicNotesOfConcert(long concertID);
@Query("DELETE FROM concert_songs WHERE concertID = :concertID AND musicNoteID = :songID")
void deleteConcertSong(long songID, int concertID);
@Transaction
default void storeSongSyncResponses(List<SongCreationResponse> songCreationResponses) {
for (SongCreationResponse response : songCreationResponses) {
storeSongSyncResponse(response.getLocalID(), SyncStatus.SYNCED, response.getServerID());
}
}
@Query("UPDATE music_notes SET syncStatus = :status, serverID = :serverID WHERE musicNoteId = :localID")
void storeSongSyncResponse(long localID, SyncStatus status, String serverID);
@Query("UPDATE music_notes SET syncStatus = :status, last_sync = :currentTime WHERE serverID IN (:serverIDs)")
void storeSongSyncModifyResponses(List<String> serverIDs, SyncStatus status, LocalDateTime currentTime);
@Update
void updateSong(MusicNote musicNote);
@Query("DELETE FROM music_notes WHERE serverID IN (:serverIDs)")
void deleteSongs(List<String> serverIDs);
@Query("SELECT * FROM note_sheets ns WHERE ns.syncStatus = :status")
List<NoteSheet> getNoteSheetsWithStatus(SyncStatus status);
@Query("SELECT mn.serverID FROM music_notes mn WHERE mn.musicNoteId =:musicNoteId")
Optional<String> getMusicNoteByLocalID(long musicNoteId);
@Update
void updateNoteSheet(NoteSheet noteSheet);
@Transaction
@Insert(onConflict = OnConflictStrategy.REPLACE) // oder andere Strategie je nach Bedarf
void insertSyncDataObjects(List<SyncDataObject> syncDataObjects);
}

View File

@ -1,56 +0,0 @@
package core.notevault.data;
import androidx.room.Entity;
import androidx.room.PrimaryKey;
import core.notevault.data.sync.SyncStatus;
@Entity(tableName = "note_sheets")
public class NoteSheet {
@PrimaryKey(autoGenerate = true)
private int id;
private SyncStatus syncStatus;
private long musicNoteId;
private String filePath;
public NoteSheet(long musicNoteID, String filePath) {
this.musicNoteId = musicNoteID;
this.filePath = filePath;
this.syncStatus = SyncStatus.CREATED;
}
public NoteSheet() {
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public long getMusicNoteId() {
return musicNoteId;
}
public void setMusicNoteId(long musicNoteId) {
this.musicNoteId = musicNoteId;
}
public String getFilePath() {
return filePath;
}
public void setFilePath(String filePath) {
this.filePath = filePath;
}
public SyncStatus getSyncStatus() {
return syncStatus;
}
public void setSyncStatus(SyncStatus syncStatus) {
this.syncStatus = syncStatus;
}
}

View File

@ -1,49 +0,0 @@
package core.notevault.data.sync;
import androidx.room.Entity;
import androidx.room.Ignore;
import androidx.room.PrimaryKey;
import androidx.room.TypeConverters;
@Entity(tableName = "sync_data_objects")
@TypeConverters(SyncDataObjectTypeConverter.class)
public class SyncDataObject {
@PrimaryKey(autoGenerate = true)
private int id;
private String serverID;
private SyncDataObjectType type;
@Ignore
public SyncDataObject(String serverID, SyncDataObjectType type) {
this.serverID = serverID;
this.type = type;
}
public SyncDataObject() {
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getServerID() {
return serverID;
}
public void setServerID(String serverID) {
this.serverID = serverID;
}
public SyncDataObjectType getType() {
return type;
}
public void setType(SyncDataObjectType type) {
this.type = type;
}
}

View File

@ -1,5 +0,0 @@
package core.notevault.data.sync;
public enum SyncDataObjectType {
MUSIC_NOTE
}

View File

@ -1,15 +0,0 @@
package core.notevault.data.sync;
import androidx.room.TypeConverter;
public class SyncDataObjectTypeConverter {
@TypeConverter
public static SyncDataObjectType fromInt(int value) {
return SyncDataObjectType.values()[value];
}
@TypeConverter
public static int toInt(SyncDataObjectType objectType) {
return objectType.ordinal();
}
}

View File

@ -1,9 +0,0 @@
package core.notevault.data.sync;
public enum SyncStatus {
CREATED,
DELETED,
MODIFIED,
REMOTE_MODIFIED,
SYNCED;
}

View File

@ -1,15 +0,0 @@
package core.notevault.data.sync;
import androidx.room.TypeConverter;
public class SyncStatusConverter {
@TypeConverter
public static SyncStatus fromInt(int value) {
return SyncStatus.values()[value];
}
@TypeConverter
public static int toInt(SyncStatus syncStatus) {
return syncStatus.ordinal();
}
}

View File

@ -1,7 +0,0 @@
package core.notevault.sync;
public interface APICallback {
void onSuccess();
void onError(String error);
}

View File

@ -1,34 +0,0 @@
package core.notevault.sync;
import android.content.Context;
import android.content.SharedPreferences;
import core.notevault.sync.auth.AuthInterceptor;
import core.notevault.sync.auth.TokenManager;
import okhttp3.OkHttpClient;
import okhttp3.logging.HttpLoggingInterceptor;
import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;
public class ApiClient {
private static Retrofit retrofit;
public static Retrofit getRetrofitInstance(Context context) {
if (retrofit == null) {
HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor();
loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
TokenManager tokenManager = new TokenManager(context);
OkHttpClient client = new OkHttpClient.Builder()
.addInterceptor(new AuthInterceptor(tokenManager))
.addInterceptor(loggingInterceptor)
.build();
retrofit = new Retrofit.Builder()
.baseUrl("http://192.168.178.30:8000/")
.client(client)
.addConverterFactory(GsonConverterFactory.create())
.build();
}
return retrofit;
}
}

View File

@ -1,14 +0,0 @@
package core.notevault.sync;
public class StatusResponse {
private String status;
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
}

View File

@ -1,38 +0,0 @@
package core.notevault.sync;
import android.content.Context;
import androidx.annotation.NonNull;
import androidx.work.Worker;
import androidx.work.WorkerParameters;
import core.notevault.data.MusicDatabase;
import core.notevault.sync.synchronisation.*;
import core.notevault.sync.synchronisation.songs.SongSyncWorker;
public class SyncWorker extends Worker{
private SongSyncWorker songSyncWorker;
public SyncWorker(@NonNull Context context, @NonNull WorkerParameters workerParams) {
super(context, workerParams);
SyncService syncService = ApiClient.getRetrofitInstance(context).create(SyncService.class);
MusicDatabase musicDatabase = MusicDatabase.getDatabase(context);
this.songSyncWorker = new SongSyncWorker(syncService, musicDatabase.musicNoteDao(), context);
}
@NonNull
@Override
public Result doWork() {
try {
songSyncWorker.syncSongCreations();
songSyncWorker.syncSongModifications();
songSyncWorker.syncSongDeletions();
songSyncWorker.uploadNoteSheets();
songSyncWorker.fetchModifiedSongs();
return Result.success();
} catch (Exception e) {
e.printStackTrace();
return Result.failure();
}
}
}

View File

@ -1,32 +0,0 @@
package core.notevault.sync.auth;
import android.content.SharedPreferences;
import android.util.Log;
import okhttp3.Interceptor;
import okhttp3.Request;
import okhttp3.Response;
import java.io.IOException;
public class AuthInterceptor implements Interceptor {
private TokenManager tokenManager;
public AuthInterceptor(TokenManager tokenManager) {
this.tokenManager = tokenManager;
}
@Override
public Response intercept(Chain chain) throws IOException {
Request originalRequest = chain.request();
String token = tokenManager.getToken();
if (token == null) {
return chain.proceed(originalRequest);
}
Request authenticatedRequest = originalRequest.newBuilder()
.header("Authorization", "Bearer " + token).build();
return chain.proceed(authenticatedRequest);
}
}

View File

@ -1,80 +0,0 @@
package core.notevault.sync.auth;
import android.content.Context;
import core.notevault.sync.APICallback;
import core.notevault.sync.ApiClient;
import core.notevault.sync.StatusResponse;
import core.notevault.sync.auth.apimodel.LoginRequest;
import core.notevault.sync.auth.apimodel.LoginResponse;
import core.notevault.sync.auth.apimodel.RegisterRequest;
import core.notevault.sync.auth.apimodel.RegisterResponse;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
public class AuthRepository {
private final AuthService authService;
private final TokenManager tokenManager;
public AuthRepository(Context context) {
this.authService = ApiClient.getRetrofitInstance(context).create(AuthService.class);
this.tokenManager = new TokenManager(context);
}
public void performLogin(String email, String password, APICallback callback) {
LoginRequest loginRequest = new LoginRequest(email, password);
authService.login(loginRequest).enqueue(new Callback<LoginResponse>() {
@Override
public void onResponse(Call<LoginResponse> call, Response<LoginResponse> response) {
if (response.isSuccessful() && response.body() != null) {
String token = response.body().getToken();
saveToken(token);
// Erfolgsrückmeldung an den Callback senden
callback.onSuccess();
} else {
// Fehlermeldung an den Callback senden
callback.onError("Login fehlgeschlagen. Überprüfe Benutzername und Passwort.");
}
}
@Override
public void onFailure(Call<LoginResponse> call, Throwable t) {
// Netzwerkfehler an den Callback senden
callback.onError("Netzwerkfehler: " + t.getMessage());
}
});
}
public void performRegistration(String email, String username, String password, APICallback callback) {
RegisterRequest registerRequest = new RegisterRequest(username, password, email);
authService.registration(registerRequest).enqueue(new Callback<StatusResponse>() {
@Override
public void onResponse(Call<StatusResponse> call, Response<StatusResponse> response) {
if(response.isSuccessful() && response.body() != null) {
callback.onSuccess();
} else {
callback.onError("Registration fehlgeschlagen. Überprüfe Benutzername und Passwort.");
}
}
@Override
public void onFailure(Call<StatusResponse> call, Throwable throwable) {
callback.onError("Netzwerkfehler: " + throwable.getMessage());
}
});
}
private void saveToken(String token) {
tokenManager.saveToken(token);
}
public String getToken() {
return tokenManager.getToken();
}
public void logout() {
tokenManager.clearToken();
}
}

View File

@ -1,20 +0,0 @@
package core.notevault.sync.auth;
import core.notevault.sync.StatusResponse;
import core.notevault.sync.auth.apimodel.LoginRequest;
import core.notevault.sync.auth.apimodel.LoginResponse;
import core.notevault.sync.auth.apimodel.RegisterRequest;
import retrofit2.Call;
import retrofit2.http.Body;
import retrofit2.http.Headers;
import retrofit2.http.POST;
public interface AuthService {
@POST("/login/")
Call<LoginResponse> login(@Body LoginRequest loginRequest);
@POST("/register/")
@Headers("Content-Type: application/json")
Call<StatusResponse> registration(@Body RegisterRequest registerRequest);
}

View File

@ -1,7 +0,0 @@
package core.notevault.sync.auth;
public interface LoginCallback {
void onSuccess();
void onError(String error);
}

View File

@ -1,31 +0,0 @@
package core.notevault.sync.auth;
import android.content.Context;
import android.content.SharedPreferences;
public class TokenManager {
private static final String PREF_NAME = "app_preferences";
private static final String KEY_TOKEN = "jwt_token";
private SharedPreferences sharedPreferences;
public TokenManager(Context context) {
sharedPreferences = context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE);
}
public void saveToken(String token) {
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.putString(KEY_TOKEN, token);
editor.apply();
}
public String getToken() {
return sharedPreferences.getString(KEY_TOKEN, null);
}
public void clearToken() {
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.remove(KEY_TOKEN);
editor.apply();
}
}

View File

@ -1,13 +0,0 @@
package core.notevault.sync.auth.apimodel;
public class LoginRequest {
private String email;
private String password;
public LoginRequest(String email, String password) {
this.email = email;
this.password = password;
}
}

View File

@ -1,22 +0,0 @@
package core.notevault.sync.auth.apimodel;
public class LoginResponse {
private String token;
private String username;
public String getToken() {
return token;
}
public void setToken(String token) {
this.token = token;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
}

View File

@ -1,14 +0,0 @@
package core.notevault.sync.auth.apimodel;
public class RegisterRequest {
private String username;
private String email;
private String password;
public RegisterRequest(String username, String password, String email) {
this.username = username;
this.password = password;
this.email = email;
}
}

View File

@ -1,31 +0,0 @@
package core.notevault.sync.auth.apimodel;
public class RegisterResponse {
private String username;
private String email;
public RegisterResponse(String username, String email) {
this.username = username;
this.email = email;
}
public RegisterResponse() {
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
}

View File

@ -1,19 +0,0 @@
package core.notevault.sync.synchronisation;
import java.time.LocalDateTime;
public class FetchRequest {
private LocalDateTime client_last_sync;
public FetchRequest(LocalDateTime client_last_sync) {
this.client_last_sync = client_last_sync;
}
public LocalDateTime getClient_last_sync() {
return client_last_sync;
}
public void setClient_last_sync(LocalDateTime client_last_sync) {
this.client_last_sync = client_last_sync;
}
}

View File

@ -1,19 +0,0 @@
package core.notevault.sync.synchronisation;
import java.util.List;
public class FetchResponse {
private List<String> serverIDs;
public FetchResponse(List<String> serverIDs) {
this.serverIDs = serverIDs;
}
public List<String> getServerIDs() {
return serverIDs;
}
public void setServerIDs(List<String> serverIDs) {
this.serverIDs = serverIDs;
}
}

View File

@ -1,36 +0,0 @@
package core.notevault.sync.synchronisation;
import core.notevault.sync.synchronisation.songs.creation.SongCreationBatchRequest;
import core.notevault.sync.synchronisation.songs.creation.SongCreationBatchResponse;
import core.notevault.sync.synchronisation.songs.deletion.SongDeletionBatchRequest;
import core.notevault.sync.synchronisation.songs.deletion.SongDeletionBatchResponse;
import core.notevault.sync.synchronisation.songs.modification.SongModificationBatchRequest;
import core.notevault.sync.synchronisation.songs.modification.SongModificationBatchResponse;
import core.notevault.sync.synchronisation.songs.notesheets.UploadResponse;
import okhttp3.MultipartBody;
import okhttp3.RequestBody;
import retrofit2.Call;
import retrofit2.http.*;
import java.time.LocalDateTime;
public interface SyncService {
@POST("/sync/songs/create")
Call<SongCreationBatchResponse> performSongCreation(@Body SongCreationBatchRequest syncRequest);
@POST("/sync/songs/modify")
Call<SongModificationBatchResponse> performSongModification(@Body SongModificationBatchRequest syncRequest);
@POST("/sync/songs/delete")
Call<SongDeletionBatchResponse> performSongDeletion(@Body SongDeletionBatchRequest syncRequest);
@Multipart
@POST("/sync/songs/note_sheet/upload")
Call<UploadResponse> uploadNoteSheet(@Part("serverID") RequestBody serverID, @Part("fileName") RequestBody fileName,
@Part MultipartBody.Part image);
@GET("/sync/songs/fetch")
Call<FetchResponse> fetchModifiedSongs(@Query("last_client_sync") LocalDateTime last_client_sync);
}

View File

@ -1,199 +0,0 @@
package core.notevault.sync.synchronisation.songs;
import android.content.Context;
import android.os.AsyncTask;
import android.widget.Toast;
import core.notevault.data.MusicNote;
import core.notevault.data.MusicNoteDAO;
import core.notevault.data.NoteSheet;
import core.notevault.data.sync.SyncDataObject;
import core.notevault.data.sync.SyncDataObjectType;
import core.notevault.data.sync.SyncStatus;
import core.notevault.sync.synchronisation.FetchRequest;
import core.notevault.sync.synchronisation.FetchResponse;
import core.notevault.sync.synchronisation.SyncService;
import core.notevault.sync.synchronisation.songs.creation.SongCreationBatchRequest;
import core.notevault.sync.synchronisation.songs.creation.SongCreationBatchResponse;
import core.notevault.sync.synchronisation.songs.creation.SongCreationRequest;
import core.notevault.sync.synchronisation.songs.deletion.SongDeletionBatchRequest;
import core.notevault.sync.synchronisation.songs.deletion.SongDeletionBatchResponse;
import core.notevault.sync.synchronisation.songs.modification.SongModificationBatchRequest;
import core.notevault.sync.synchronisation.songs.modification.SongModificationBatchResponse;
import core.notevault.sync.synchronisation.songs.modification.SongModificationRequest;
import core.notevault.sync.synchronisation.songs.notesheets.UploadResponse;
import okhttp3.MediaType;
import okhttp3.MultipartBody;
import okhttp3.RequestBody;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
import java.io.File;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.stream.Collectors;
public class SongSyncWorker {
private SyncService syncService;
private MusicNoteDAO database;
private Context context;
private ExecutorService executorService = Executors.newSingleThreadExecutor();
public SongSyncWorker(SyncService syncService, MusicNoteDAO database, Context context) {
this.syncService = syncService;
this.database = database;
this.context = context;
}
private SongCreationBatchRequest buildCreationRequest() {
List<MusicNote> created_songs = database.getSongsWithSyncStatus(SyncStatus.CREATED);
List<SongCreationRequest> songCreationRequests = new ArrayList<>();
for(MusicNote musicNote : created_songs) {
songCreationRequests.add(new SongCreationRequest(musicNote.getMusicNoteId(), musicNote.getTitle(), musicNote.getComposer(), musicNote.getGenre(), musicNote.getYear()));
}
return new SongCreationBatchRequest(songCreationRequests);
}
public void syncSongCreations() {
SongCreationBatchRequest request = buildCreationRequest();
syncService.performSongCreation(request).enqueue(new Callback<SongCreationBatchResponse>() {
@Override
public void onResponse(Call<SongCreationBatchResponse> call, Response<SongCreationBatchResponse> response) {
if(response.isSuccessful() && response.body() != null) {
executorService.execute(() -> {
database.storeSongSyncResponses(response.body().getSongs());
});
} else {
Toast.makeText(context, "Song creation failed", Toast.LENGTH_LONG).show();
}
}
@Override
public void onFailure(Call<SongCreationBatchResponse> call, Throwable throwable) {
Toast.makeText(context, "Song creation failed", Toast.LENGTH_LONG).show();
}
});
}
private SongModificationBatchRequest buildModificationRequest() {
List<MusicNote> modified_songs = database.getSongsWithSyncStatus(SyncStatus.MODIFIED);
List<SongModificationRequest> songModificationRequests = new ArrayList<>();
for(MusicNote musicNote : modified_songs) {
songModificationRequests.add(new SongModificationRequest(musicNote.getServerID(), musicNote.getTitle(), musicNote.getComposer(), musicNote.getGenre(), musicNote.getYear()));
}
return new SongModificationBatchRequest(songModificationRequests);
}
public void syncSongModifications() {
SongModificationBatchRequest request = buildModificationRequest();
syncService.performSongModification(request).enqueue(new Callback<SongModificationBatchResponse>() {
@Override
public void onResponse(Call<SongModificationBatchResponse> call, Response<SongModificationBatchResponse> response) {
executorService.execute(() -> {
database.storeSongSyncModifyResponses(response.body().getUpdated_songs(), SyncStatus.SYNCED, LocalDateTime.now());
});
}
@Override
public void onFailure(Call<SongModificationBatchResponse> call, Throwable throwable) {
Toast.makeText(context, "Sync of Modified Songs failed", Toast.LENGTH_LONG).show();
}
});
}
private SongDeletionBatchRequest buildDeletionBatchRequest() {
List<MusicNote> deleted_songs = database.getSongsWithSyncStatus(SyncStatus.DELETED);
List<String> deleted_song_ids = deleted_songs.stream().map(MusicNote::getServerID).collect(Collectors.toList());
return new SongDeletionBatchRequest(deleted_song_ids);
}
public void syncSongDeletions() {
SongDeletionBatchRequest request = buildDeletionBatchRequest();
syncService.performSongDeletion(request).enqueue(new Callback<SongDeletionBatchResponse>() {
@Override
public void onResponse(Call<SongDeletionBatchResponse> call, Response<SongDeletionBatchResponse> response) {
if(response.isSuccessful() && response.body() != null) {
executorService.execute(() -> {
database.deleteSongs(response.body().getDeletedSongs());
});
}
}
@Override
public void onFailure(Call<SongDeletionBatchResponse> call, Throwable throwable) {
Toast.makeText(context, "Sync of Deleted Songs failed", Toast.LENGTH_LONG).show();
}
});
}
public void uploadNoteSheets() {
List<NoteSheet> noteSheets = database.getNoteSheetsWithStatus(SyncStatus.CREATED);
for(NoteSheet noteSheet : noteSheets) {
Optional<String> serverID = database.getMusicNoteByLocalID(noteSheet.getMusicNoteId());
if(serverID.isPresent()) {
File imageFile = new File(noteSheet.getFilePath());
// Erstelle Request-Bodies für serverID und fileName
RequestBody serverIDBody = RequestBody.create(MediaType.parse("text/plain"), serverID.get());
RequestBody fileNameBody = RequestBody.create(MediaType.parse("text/plain"), imageFile.getName());
// Erstelle den Multipart-Body für die Bilddatei
RequestBody imageRequestBody = RequestBody.create(MediaType.parse("image/*"), imageFile);
MultipartBody.Part imagePart = MultipartBody.Part.createFormData("image", imageFile.getName(), imageRequestBody);
syncService.uploadNoteSheet(serverIDBody, fileNameBody, imagePart).enqueue(new Callback<UploadResponse>() {
@Override
public void onResponse(Call<UploadResponse> call, Response<UploadResponse> response) {
if (response.isSuccessful() && response.body() != null) {
// Erfolg: Antwort verarbeiten
Toast.makeText(context, "Upload erfolgreich", Toast.LENGTH_LONG).show();
executorService.execute(() -> {
noteSheet.setSyncStatus(SyncStatus.SYNCED);
database.updateNoteSheet(noteSheet);
});
} else {
// Fehler: Antwort prüfen
Toast.makeText(context, "Fehler beim Upload: " + response.code(), Toast.LENGTH_LONG).show();
}
}
@Override
public void onFailure(Call<UploadResponse> call, Throwable throwable) {
Toast.makeText(context, "Fehler beim Upload: " + throwable.toString(), Toast.LENGTH_LONG).show();
}
});
}
}
}
public void fetchModifiedSongs() {
FetchRequest request = new FetchRequest(LocalDateTime.MIN);
syncService.fetchModifiedSongs(LocalDateTime.MIN).enqueue(new Callback<FetchResponse>() {
@Override
public void onResponse(Call<FetchResponse> call, Response<FetchResponse> response) {
if(response.isSuccessful() && response.body() != null) {
executorService.execute(() -> {
List<SyncDataObject> syncDataObjects = new ArrayList<>();
for(String serverID: response.body().getServerIDs()) {
syncDataObjects.add(new SyncDataObject(serverID, SyncDataObjectType.MUSIC_NOTE));
}
database.insertSyncDataObjects(syncDataObjects);
});
} else {
Toast.makeText(context, "Fehler beim Fetch: " + response.code(), Toast.LENGTH_LONG).show();
}
}
@Override
public void onFailure(Call<FetchResponse> call, Throwable throwable) {
Toast.makeText(context, "Fehler beim Fetch: " + throwable.getMessage(), Toast.LENGTH_LONG).show();
}
});
}
}

View File

@ -1,11 +0,0 @@
package core.notevault.sync.synchronisation.songs.creation;
import java.util.List;
public class SongCreationBatchRequest {
private List<SongCreationRequest> songs;
public SongCreationBatchRequest(List<SongCreationRequest> songs) {
this.songs = songs;
}
}

View File

@ -1,19 +0,0 @@
package core.notevault.sync.synchronisation.songs.creation;
import java.util.List;
public class SongCreationBatchResponse {
private List<SongCreationResponse> songs;
public SongCreationBatchResponse(List<SongCreationResponse> songs) {
this.songs = songs;
}
public List<SongCreationResponse> getSongs() {
return songs;
}
public void setSongs(List<SongCreationResponse> songs) {
this.songs = songs;
}
}

View File

@ -1,17 +0,0 @@
package core.notevault.sync.synchronisation.songs.creation;
public class SongCreationRequest {
private long localID;
private String title;
private String composer;
private String genre;
private int year;
public SongCreationRequest(long localID, String title, String composer, String genre, int year) {
this.localID = localID;
this.title = title;
this.composer = composer;
this.genre = genre;
this.year = year;
}
}

View File

@ -1,27 +0,0 @@
package core.notevault.sync.synchronisation.songs.creation;
public class SongCreationResponse {
private int localID;
private String serverID;
public SongCreationResponse(int localID, String serverID) {
this.localID = localID;
this.serverID = serverID;
}
public int getLocalID() {
return localID;
}
public void setLocalID(int localID) {
this.localID = localID;
}
public String getServerID() {
return serverID;
}
public void setServerID(String serverID) {
this.serverID = serverID;
}
}

View File

@ -1,19 +0,0 @@
package core.notevault.sync.synchronisation.songs.deletion;
import java.util.List;
public class SongDeletionBatchRequest {
private List<String> songs;
public SongDeletionBatchRequest(List<String> songs) {
this.songs = songs;
}
public List<String> getSongs() {
return songs;
}
public void setSongs(List<String> songs) {
this.songs = songs;
}
}

View File

@ -1,19 +0,0 @@
package core.notevault.sync.synchronisation.songs.deletion;
import java.util.List;
public class SongDeletionBatchResponse {
private List<String> deletedSongs;
public SongDeletionBatchResponse(List<String> deletedSongs) {
this.deletedSongs = deletedSongs;
}
public List<String> getDeletedSongs() {
return deletedSongs;
}
public void setDeletedSongs(List<String> deletedSongs) {
this.deletedSongs = deletedSongs;
}
}

View File

@ -1,11 +0,0 @@
package core.notevault.sync.synchronisation.songs.modification;
import java.util.List;
public class SongModificationBatchRequest {
private List<SongModificationRequest> songs;
public SongModificationBatchRequest(List<SongModificationRequest> songs) {
this.songs = songs;
}
}

View File

@ -1,19 +0,0 @@
package core.notevault.sync.synchronisation.songs.modification;
import java.util.List;
public class SongModificationBatchResponse {
private List<String> songs;
public SongModificationBatchResponse(List<String> songs) {
this.songs = songs;
}
public List<String> getUpdated_songs() {
return songs;
}
public void setUpdated_songs(List<String> updated_songs) {
this.songs = updated_songs;
}
}

View File

@ -1,57 +0,0 @@
package core.notevault.sync.synchronisation.songs.modification;
public class SongModificationRequest {
private String serverID;
private String title;
private String composer;
private String genre;
private int year;
public SongModificationRequest(String serverID, String title, String composer, String genre, int year) {
this.serverID = serverID;
this.title = title;
this.composer = composer;
this.genre = genre;
this.year = year;
}
public String getServerID() {
return serverID;
}
public void setServerID(String serverID) {
this.serverID = serverID;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getComposer() {
return composer;
}
public void setComposer(String composer) {
this.composer = composer;
}
public String getGenre() {
return genre;
}
public void setGenre(String genre) {
this.genre = genre;
}
public int getYear() {
return year;
}
public void setYear(int year) {
this.year = year;
}
}

View File

@ -1,5 +0,0 @@
package core.notevault.sync.synchronisation.songs.notesheets;
public class UploadResponse {
private String serverID;
}

View File

@ -1,86 +0,0 @@
package core.notevault.ui.gallery;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageButton;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import core.notevault.R;
import core.notevault.data.Concert;
import core.notevault.ui.home.NoteSongAdapter;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.List;
public class ConcertAdapter extends RecyclerView.Adapter<ConcertAdapter.ConcertViewHolder> {
private final List<Concert> concertList;
private final OnConcertClickListener concertClickListener;
public interface OnConcertClickListener {
void onDeleteConcert(Concert concert);
void onOpenConcertEditor(Concert concert);
void onConcertClick(Concert concert);
}
public ConcertAdapter(List<Concert> concertList, OnConcertClickListener concertClickListener) {
this.concertList = concertList;
this.concertClickListener = concertClickListener;
}
public ConcertAdapter(OnConcertClickListener onConcertClickListener) {
this.concertClickListener = onConcertClickListener;
this.concertList = new ArrayList<>();
}
@Override
public void onBindViewHolder(@NonNull ConcertViewHolder holder, int position) {
holder.concertTitleView.setText(concertList.get(position).getTitle());
holder.dateHolder.setText(concertList.get(position).getConcertDate());
holder.deleteButton.setOnClickListener(v -> concertClickListener.onDeleteConcert(concertList.get(position)));
holder.editButton.setOnClickListener(v -> concertClickListener.onOpenConcertEditor(concertList.get(position)));
holder.itemView.setOnClickListener(v -> concertClickListener.onConcertClick(concertList.get(position)));
}
@Override
public int getItemCount() {
return concertList.size();
}
@NonNull
@Override
public ConcertViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.concert_item, parent, false);
return new ConcertViewHolder(view);
}
public void updateConcerts(List<Concert> concerts) {
Log.d("ConcertAdapter", "Update Concerts: " + this.concertList.size() + "vs new: " + concerts.size());
this.concertList.clear();
this.concertList.addAll(concerts);
notifyDataSetChanged();
}
public class ConcertViewHolder extends RecyclerView.ViewHolder {
TextView concertTitleView;
TextView dateHolder;
ImageButton deleteButton;
ImageButton editButton;
public ConcertViewHolder(@NonNull @NotNull View itemView) {
super(itemView);
concertTitleView = itemView.findViewById(R.id.concert_title);
dateHolder = itemView.findViewById(R.id.concert_date);
deleteButton = itemView.findViewById(R.id.delete_concert_button);
editButton = itemView.findViewById(R.id.edit_concert_button);
}
}
}

View File

@ -1,153 +0,0 @@
package core.notevault.ui.gallery;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider;
import androidx.navigation.NavController;
import androidx.navigation.NavDirections;
import androidx.navigation.Navigation;
import androidx.recyclerview.widget.DividerItemDecoration;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import core.notevault.R;
import core.notevault.data.Concert;
import core.notevault.data.MusicDatabase;
import core.notevault.data.MusicNote;
import core.notevault.data.MusicNoteDAO;
import core.notevault.databinding.FragmentGalleryBinding;
import core.notevault.ui.gallery.editor.ConcertEditorDialog;
import java.util.List;
public class GalleryFragment extends Fragment {
private FragmentGalleryBinding binding;
private ConcertAdapter concertAdapter;
private GalleryViewModel galleryViewModel;
public View onCreateView(@NonNull LayoutInflater inflater,
ViewGroup container, Bundle savedInstanceState) {
galleryViewModel =
new ViewModelProvider(this).get(GalleryViewModel.class);
binding = FragmentGalleryBinding.inflate(inflater, container, false);
View root = binding.getRoot();
RecyclerView recyclerView = root.findViewById(R.id.concert_recycler_view);
concertAdapter = new ConcertAdapter(new ConcertAdapter.OnConcertClickListener() {
@Override
public void onDeleteConcert(Concert concert) {
deleteConcert(concert);
}
@Override
public void onOpenConcertEditor(Concert concert) {
editConcert(concert);
}
@Override
public void onConcertClick(Concert concert) {
Log.d("GalleryFragment", "Clicked on Concert: " + concert.getTitle());
Bundle bundle = new Bundle();
bundle.putInt("concertID", concert.getId());
bundle.putString("concertTitle", concert.getTitle());
bundle.putString("concertDate", concert.getConcertDate());
NavController navController = Navigation.findNavController(getActivity(), R.id.nav_host_fragment_content_main);
navController.navigate(R.id.concertDetailFragment, bundle);
}
});
recyclerView.setAdapter(concertAdapter);
recyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
DividerItemDecoration dividerItemDecoration = new DividerItemDecoration(recyclerView.getContext(), LinearLayoutManager.VERTICAL);
recyclerView.addItemDecoration(dividerItemDecoration);
FloatingActionButton addConcertBtn = root.findViewById(R.id.add_concert_btn);
addConcertBtn.setOnClickListener(v -> onCreateNewConcert());
galleryViewModel.getConcerts().observe(getViewLifecycleOwner(), newConcerts -> {
concertAdapter.updateConcerts(newConcerts);
});
new LoadConcerts().execute();
return root;
}
public void deleteConcert(Concert concert) {
Log.d("GalleryFragment", "Delete Concert");
new Thread(() -> {
MusicDatabase musicDatabase = MusicDatabase.getDatabase(this.getContext());
MusicNoteDAO musicNoteDAO = musicDatabase.musicNoteDao();
musicNoteDAO.deleteConcert(concert);
new Handler(Looper.getMainLooper()).post(() -> {
galleryViewModel.deleteConcert(concert);
Log.d("GalleryFragment", "Concert deleted successfully and ViewModel updated");
});
}).start();
}
public void editConcert(Concert concert) {
ConcertEditorDialog concertEditorDialog = new ConcertEditorDialog();
Bundle args = new Bundle();
args.putString("concert_title", concert.getTitle());
args.putString("concert_date", concert.getConcertDate());
args.putInt("concertID", concert.getId());
concertEditorDialog.setArguments(args);
concertEditorDialog.show(getParentFragmentManager(), ConcertEditorDialog.TAG);
}
public void updateConcert(Concert concert) {
galleryViewModel.updateConcert(concert);
}
private class LoadConcerts extends AsyncTask<Void, Void, List<Concert>> {
@Override
protected List<Concert> doInBackground(Void... voids) {
MusicDatabase db = MusicDatabase.getDatabase(getContext());
MusicNoteDAO musicNoteDAO = db.musicNoteDao();
List<Concert> concerts = musicNoteDAO.getAllConcerts();
Log.d("GalleryFragment", "Fetching concerts: " + concerts.size());
return concerts;
}
@Override
protected void onPostExecute(List<Concert> concerts) {
Log.d("GalleryFragment", "Concerts size in onPostExecute: " + concerts.size());
// concertAdapter.updateConcerts(concerts); // Aktualisiere den Adapter mit den Titeln
galleryViewModel.setConcerts(concerts);
}
}
public void onCreateNewConcert() {
ConcertEditorDialog concertEditorDialog = new ConcertEditorDialog();
concertEditorDialog.show(getParentFragmentManager(), ConcertEditorDialog.TAG);
}
public void addConcert(Concert concert) {
this.galleryViewModel.addConcert(concert);
}
@Override
public void onDestroyView() {
super.onDestroyView();
binding = null;
}
}

View File

@ -1,58 +0,0 @@
package core.notevault.ui.gallery;
import android.util.Log;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModel;
import core.notevault.data.Concert;
import java.util.List;
public class GalleryViewModel extends ViewModel {
private final MutableLiveData<List<Concert>> concerts;
public GalleryViewModel() {
this.concerts = new MutableLiveData<>();
}
public LiveData<List<Concert>> getConcerts() {
return concerts;
}
public void setConcerts(List<Concert> concerts) {
this.concerts.setValue(concerts);
}
public void addConcert(Concert concert) {
List<Concert> currentConcerts = this.concerts.getValue();
Log.d("GalleryViewModel", "Add Concert");
if(currentConcerts != null) {
currentConcerts.add(concert);
concerts.setValue(currentConcerts);
}
}
public void deleteConcert(Concert concert) {
List<Concert> currentConcerts = concerts.getValue();
if(currentConcerts != null) {
currentConcerts.remove(concert);
concerts.setValue(currentConcerts);
}
}
public void updateConcert(Concert concert) {
List<Concert> currentConcerts = concerts.getValue();
if(currentConcerts != null) {
for(int i = 0; i < currentConcerts.size(); i++) {
if(currentConcerts.get(i).getId() == concert.getId()) {
currentConcerts.get(i).setTitle(concert.getTitle());
currentConcerts.get(i).setConcertDate(concert.getConcertDate());
break;
}
}
concerts.setValue(currentConcerts);
}
}
}

View File

@ -1,119 +0,0 @@
package core.notevault.ui.gallery.detail;
import android.os.AsyncTask;
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider;
import androidx.recyclerview.widget.DividerItemDecoration;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import androidx.room.util.ViewInfo;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import core.notevault.data.Concert;
import core.notevault.data.MusicDatabase;
import core.notevault.data.MusicNote;
import core.notevault.data.MusicNoteDAO;
import core.notevault.databinding.FragmentConcertDetailBinding;
import core.notevault.ui.home.NoteSongAdapter;
import core.notevault.ui.metadatadialog.SongEditDialog;
import java.util.Collections;
import java.util.List;
public class ConcertDetailFragment extends Fragment {
public interface OnConcertSongsLoadedListener {
void onSongsLoaded(List<MusicNote> songs);
}
private FragmentConcertDetailBinding binding;
private Concert concert;
private ConcertDetailViewModel concertDetailViewModel;
private NoteSongAdapter noteSongAdapter;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
concertDetailViewModel = new ViewModelProvider(this).get(ConcertDetailViewModel.class);
binding = FragmentConcertDetailBinding.inflate(inflater, container, false);
int concertId = getArguments().getInt("concertID");
String concertTitle = getArguments().getString("concertTitle");
String concertDate = getArguments().getString("concertDate");
concert = new Concert(concertId, concertTitle, concertDate);
binding.textConcertDetailsTitle.setText(concertTitle);
binding.textConcertDetailsDate.setText(concertDate);
RecyclerView recyclerView = binding.noteRecyclerView;
noteSongAdapter = new NoteSongAdapter(this::deleteSongFromConcert, this::editSongFromConcert);
recyclerView.setAdapter(noteSongAdapter);
recyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
DividerItemDecoration dividerItemDecoration = new DividerItemDecoration(recyclerView.getContext(), LinearLayoutManager.VERTICAL);
recyclerView.addItemDecoration(dividerItemDecoration);
FloatingActionButton fab = binding.addSongConcert;
fab.setOnClickListener(view -> {
ConcertSongSelector concertSongSelector = new ConcertSongSelector();
Bundle bundle = new Bundle();
bundle.putString("concertTitle", concertTitle);
bundle.putString("concertDate", concertDate);
bundle.putInt("concertID", concertId);
concertSongSelector.setArguments(bundle);
concertSongSelector.show(getParentFragmentManager(), ConcertSongSelector.TAG);
});
concertDetailViewModel.getConcertSongs().observe(getViewLifecycleOwner(), songs -> {
noteSongAdapter.updateSongTitles(songs);
});
new LoadConcertSongsTask(concertId, songs -> {
this.concertDetailViewModel.setConcertSongs(songs);
}).execute();
return binding.getRoot();
}
private void deleteSongFromConcert(MusicNote musicNote) {
this.concertDetailViewModel.deleteConcertSong(musicNote);
new Thread(() -> {
MusicNoteDAO musicNoteDAO = MusicDatabase.getDatabase(getContext()).musicNoteDao();
musicNoteDAO.deleteConcertSong(musicNote.getMusicNoteId(), concert.getId());
}).start();
}
private void editSongFromConcert(MusicNote musicNote) {
SongEditDialog songEditDialog = new SongEditDialog();
songEditDialog.setSong(musicNote);
songEditDialog.show(getParentFragmentManager(), SongEditDialog.TAG);
}
private class LoadConcertSongsTask extends AsyncTask<Void, Void, List<MusicNote>> {
private final int concertID;
private final OnConcertSongsLoadedListener listener;
public LoadConcertSongsTask(int concertID, OnConcertSongsLoadedListener listener) {
this.concertID = concertID;
this.listener = listener;
}
@Override
protected List<MusicNote> doInBackground(Void... voids) {
MusicDatabase db = MusicDatabase.getDatabase(getContext());
return db.musicNoteDao().getAllMusicNotesOfConcert(concertID);
}
@Override
protected void onPostExecute(List<MusicNote> songs) {
Log.d("ConcertDetailFragment", "Loaded Concertsongs: " + songs.size());
listener.onSongsLoaded(songs);
}
}
}

View File

@ -1,41 +0,0 @@
package core.notevault.ui.gallery.detail;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModel;
import core.notevault.data.MusicNote;
import java.util.ArrayList;
import java.util.List;
public class ConcertDetailViewModel extends ViewModel {
private final MutableLiveData<List<MusicNote>> concertSongs;
public ConcertDetailViewModel() {
this.concertSongs = new MutableLiveData<>(new ArrayList<>());
}
public LiveData<List<MusicNote>> getConcertSongs() {
return concertSongs;
}
public void setConcertSongs(List<MusicNote> concertSongs) {
this.concertSongs.setValue(concertSongs);
}
public void deleteConcertSong(MusicNote note) {
List<MusicNote> currentConcertSongs = concertSongs.getValue();
if(currentConcertSongs != null) {
currentConcertSongs.remove(note);
this.concertSongs.setValue(currentConcertSongs);
}
}
public void addConcertSongs(List<MusicNote> concertSongs) {
List<MusicNote> currentConcertSongs = this.concertSongs.getValue();
if(currentConcertSongs != null) {
currentConcertSongs.addAll(concertSongs);
this.concertSongs.setValue(currentConcertSongs);
}
}
}

View File

@ -1,116 +0,0 @@
package core.notevault.ui.gallery.detail;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.Context;
import android.os.AsyncTask;
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.SearchView;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.fragment.app.DialogFragment;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import core.notevault.R;
import core.notevault.data.ConcertSong;
import core.notevault.data.MusicDatabase;
import core.notevault.data.MusicNote;
import core.notevault.data.MusicNoteDAO;
import core.notevault.ui.metadatadialog.MetaDataDialog;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class ConcertSongSelector extends DialogFragment {
private ConcertSongSelectorAdapter adapter;
private OnSongSelectedListener listener;
private int concertID;
public interface OnSongSelectedListener {
void onSongsSelected(List<MusicNote> songs, int concertID);
}
@NonNull
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
LayoutInflater inflater = getActivity().getLayoutInflater();
View dialogView = inflater.inflate(R.layout.dialog_concert_song_selector, null);
String concertTitle = getArguments().getString("concertTitle");
String concertDate = getArguments().getString("concertDate");
concertID = getArguments().getInt("concertID");
TextView dialogTitle = dialogView.findViewById(R.id.dialog_title_concert_song_selector);
dialogTitle.setText("Wähle Stücke für das Konzert '" + concertTitle + "' am " + concertDate + " aus");
RecyclerView recyclerView = dialogView.findViewById(R.id.concert_song_selector_recycler);
recyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
adapter = new ConcertSongSelectorAdapter(new ArrayList<>());
recyclerView.setAdapter(adapter);
SearchView searchView = dialogView.findViewById(R.id.search_view);
searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
@Override
public boolean onQueryTextSubmit(String s) {
return false;
}
@Override
public boolean onQueryTextChange(String s) {
adapter.filter(s);
return true;
}
});
fetchAllSongs();
return new AlertDialog.Builder(requireContext())
.setView(dialogView)
.setPositiveButton("Speichern", (dialog, which) -> {
List<MusicNote> selectedSongs = adapter.getSelectedSongs();
listener.onSongsSelected(selectedSongs, concertID);
})
.setNegativeButton("Abbrechen", (dialog, which) -> {} )
.create();
}
@Override
public void onAttach(@NonNull Context context) {
super.onAttach(context);
if (context instanceof ConcertSongSelector.OnSongSelectedListener) {
listener = (ConcertSongSelector.OnSongSelectedListener) context;
} else {
throw new RuntimeException(context.toString()
+ " must implement OnMetadataListener");
}
}
private void fetchAllSongs() {
// Ersetze dies durch das Laden der Songs aus der Datenbank oder einer anderen Quelle
new LoadSongTitlesTask().execute();
}
private class LoadSongTitlesTask extends AsyncTask<Void, Void, List<MusicNote>> {
@Override
protected List<MusicNote> doInBackground(Void... voids) {
MusicDatabase db = MusicDatabase.getDatabase(getContext());
MusicNoteDAO musicNoteDAO = db.musicNoteDao();
return musicNoteDAO.getAllNotes();
}
@Override
protected void onPostExecute(List<MusicNote> songs) {
adapter.setSongs(songs);
}
}
public static String TAG = "ConcertSongSelector";
}

View File

@ -1,90 +0,0 @@
package core.notevault.ui.gallery.detail;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.CheckBox;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import core.notevault.R;
import core.notevault.data.MusicNote;
import java.util.ArrayList;
import java.util.List;
public class ConcertSongSelectorAdapter extends RecyclerView.Adapter<ConcertSongSelectorAdapter.ConcertSongSelectorViewHolder> {
private List<MusicNote> songs;
private List<MusicNote> selectedSongs = new ArrayList<>();
private List<MusicNote> filteredSongs;
public ConcertSongSelectorAdapter(List<MusicNote> songs) {
this.songs = songs;
this.filteredSongs = new ArrayList<>(songs);
}
@NonNull
@Override
public ConcertSongSelectorViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.item_concert_song, parent, false);
return new ConcertSongSelectorViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull ConcertSongSelectorViewHolder holder, int position) {
MusicNote song = filteredSongs.get(position);
holder.bind(song);
}
@Override
public int getItemCount() {
return filteredSongs.size();
}
public List<MusicNote> getSelectedSongs() {
return selectedSongs;
}
public void setSongs(List<MusicNote> musicNotes) {
songs = musicNotes;
filteredSongs = new ArrayList<>(musicNotes);
notifyDataSetChanged();
}
public void filter(String query) {
filteredSongs.clear();
if (query.isEmpty()) {
filteredSongs.addAll(songs);
} else {
for (MusicNote song : songs) {
if (song.getTitle().toLowerCase().contains(query.toLowerCase())) {
filteredSongs.add(song);
}
}
}
notifyDataSetChanged();
}
class ConcertSongSelectorViewHolder extends RecyclerView.ViewHolder {
private final TextView songTitle;
private final CheckBox songCheckbox;
ConcertSongSelectorViewHolder(View itemView) {
super(itemView);
songTitle = itemView.findViewById(R.id.song_title);
songCheckbox = itemView.findViewById(R.id.song_checkbox);
songCheckbox.setOnCheckedChangeListener((buttonView, isChecked) -> {
MusicNote musicNote = filteredSongs.get(getAbsoluteAdapterPosition());
selectedSongs.add(musicNote);
});
}
void bind(MusicNote song) {
songTitle.setText(song.getTitle());
songCheckbox.setChecked(selectedSongs.contains(song));
}
}
}

View File

@ -1,111 +0,0 @@
package core.notevault.ui.gallery.editor;
import android.app.AlertDialog;
import android.app.DatePickerDialog;
import android.app.Dialog;
import android.content.Context;
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.EditText;
import androidx.annotation.NonNull;
import androidx.fragment.app.DialogFragment;
import core.notevault.R;
import core.notevault.data.Concert;
import core.notevault.ui.metadatadialog.MetaDataDialog;
import java.util.Calendar;
import java.util.Date;
public class ConcertEditorDialog extends DialogFragment {
private Concert concert;
private EditText concert_date_input;
private EditText concertTitleInput;
private OnConcertEditorListener listener;
private int concertID = -1;
public void setConcert(Concert concert) {
concert_date_input.setText(concert.getConcertDate());
concertTitleInput.setText(concert.getTitle());
this.concert = concert;
}
public interface OnConcertEditorListener {
void onConcertEditorClosed(String concertTitle, String concertDate, int concertID);
}
public interface OnConcertEditorEditListener {
void onConcertEditorEditClosed(String concertTitle, String concertDate);
}
@NonNull
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
LayoutInflater inflater = requireActivity().getLayoutInflater();
View dialogView = inflater.inflate(R.layout.concert_editor, null);
EditText title_input = dialogView.findViewById(R.id.concert_title_input);
concert_date_input = dialogView.findViewById(R.id.concert_date_input);
concert_date_input.setOnClickListener(v -> showDatePickerDialog());
if(getArguments() != null) {
String concertTitle = getArguments().getString("concert_title");
String concertDate = getArguments().getString("concert_date");
concertID = getArguments().getInt("concertID");
title_input.setText(concertTitle);
concert_date_input.setText(concertDate);
}
return new AlertDialog.Builder(requireContext())
.setView(dialogView)
.setPositiveButton("Speichern", (dialog, which) -> {
String title = title_input.getText().toString();
String concertDate = concert_date_input.getText().toString();
Log.d("ConcertEditor", "ConcertDate: " + concertDate);
listener.onConcertEditorClosed(title, concertDate, concertID);
})
.setNegativeButton("Abbrechen", (dialog, which) -> {} )
.create();
}
@Override
public void onAttach(@NonNull Context context) {
super.onAttach(context);
if (context instanceof OnConcertEditorListener) {
listener = (OnConcertEditorListener) context;
} else {
throw new RuntimeException(context.toString()
+ " must implement OnMetadataListener");
}
}
private void showDatePickerDialog() {
// Erhalte das aktuelle Datum
final Calendar calendar = Calendar.getInstance();
int year = calendar.get(Calendar.YEAR);
int month = calendar.get(Calendar.MONTH);
int day = calendar.get(Calendar.DAY_OF_MONTH);
// Erstelle den DatePickerDialog
DatePickerDialog datePickerDialog = new DatePickerDialog(this.getContext(),
(view, selectedYear, selectedMonth, selectedDay) -> {
// Setze das ausgewählte Datum in das EditText
concert_date_input.setText(selectedDay + "/" + (selectedMonth + 1) + "/" + selectedYear);
}, year, month, day);
// Zeige den Dialog an
datePickerDialog.show();
}
public static String TAG = "ConcertEditorDialog";
}

View File

@ -1,35 +0,0 @@
package core.notevault.ui.home;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.widget.ImageView;
import androidx.appcompat.app.AppCompatActivity;
import androidx.viewpager2.widget.ViewPager2;
import core.notevault.R;
import core.notevault.ui.noteviewer.ImagePagerAdapter;
import java.util.Arrays;
import java.util.List;
public class FullScreenImageActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_fullscreen_image); // Erstelle eine Layout-Datei
ViewPager2 imageView = findViewById(R.id.viewPager); // Angenommen, du hast ein ImageView
// Die URI aus dem Intent erhalten
Intent intent = getIntent();
String[] imageUris = intent.getStringArrayExtra("imageUris");
if (imageUris != null) {
List<String> uriList = Arrays.asList(imageUris);
ImagePagerAdapter adapter = new ImagePagerAdapter(this, uriList);
imageView.setAdapter(adapter); // Setze die URI in das ImageView
} else {
throw new NullPointerException();
}
}
}

View File

@ -1,160 +0,0 @@
package core.notevault.ui.home;
import android.content.Intent;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider;
import androidx.recyclerview.widget.DividerItemDecoration;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import core.notevault.R;
import core.notevault.data.MusicDatabase;
import core.notevault.data.MusicNote;
import core.notevault.data.MusicNoteDAO;
import core.notevault.data.sync.SyncStatus;
import core.notevault.databinding.FragmentHomeBinding;
import core.notevault.ui.metadatadialog.MetaDataDialog;
import core.notevault.ui.metadatadialog.SongEditDialog;
import core.notevault.util.NoteSheetsUtil;
import java.util.List;
public class HomeFragment extends Fragment {
private NoteSongAdapter noteSongAdapter;
private static final int PICK_FILE_REQUEST_CODE = 1;
private FragmentHomeBinding binding;
private HomeViewModel homeViewModel;
public View onCreateView(@NonNull LayoutInflater inflater,
ViewGroup container, Bundle savedInstanceState) {
homeViewModel =
new ViewModelProvider(this).get(HomeViewModel.class);
binding = FragmentHomeBinding.inflate(inflater, container, false);
View root = binding.getRoot();
FloatingActionButton importBtn = root.findViewById(R.id.add_song_concert);
importBtn.setOnClickListener(v -> openFileChooser());
RecyclerView recyclerView = root.findViewById(R.id.note_recycler_view);
noteSongAdapter = new NoteSongAdapter(this::deleteSong, this::editSong);
recyclerView.setAdapter(noteSongAdapter);
recyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
DividerItemDecoration dividerItemDecoration = new DividerItemDecoration(recyclerView.getContext(), LinearLayoutManager.VERTICAL);
recyclerView.addItemDecoration(dividerItemDecoration);
homeViewModel.getNoteTitles().observe(getViewLifecycleOwner(), songs -> {
noteSongAdapter.updateSongTitles(songs);
});
new LoadSongTitlesTask().execute();
return root;
}
private void editSong(MusicNote musicNote) {
//Open Dialog
SongEditDialog songEditDialog = new SongEditDialog();
songEditDialog.setSong(musicNote);
songEditDialog.show(getParentFragmentManager(), SongEditDialog.TAG);
}
public void deleteSong(MusicNote musicNote) {
new Thread(() -> {
MusicDatabase musicDatabase = MusicDatabase.getDatabase(getContext());
MusicNoteDAO musicNoteDAO = musicDatabase.musicNoteDao();
musicNote.setSyncStatus(SyncStatus.DELETED);
musicNoteDAO.updateSong(musicNote);
Log.d("HomeFragment", "Delete Song: " + musicNote.getTitle());
new Handler(Looper.getMainLooper()).post(()-> {
homeViewModel.deleteSong(musicNote);
});
}).start();
}
private class LoadSongTitlesTask extends AsyncTask<Void, Void, List<MusicNote>> {
@Override
protected List<MusicNote> doInBackground(Void... voids) {
MusicDatabase db = MusicDatabase.getDatabase(getContext());
MusicNoteDAO musicNoteDAO = db.musicNoteDao();
return musicNoteDAO.getAllNotes();
}
@Override
protected void onPostExecute(List<MusicNote> songs) {
homeViewModel.setNoteTitles(songs);
}
}
@Override
public void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == PICK_FILE_REQUEST_CODE && resultCode == getActivity().RESULT_OK && data != null) {
if(data.getClipData() != null) {
int count = data.getClipData().getItemCount();
Uri[] uris = new Uri[count];
for(int i = 0; i < count; i++) {
uris[i] = data.getClipData().getItemAt(i).getUri();
}
handleFile(uris);
} else if(data.getData() != null) {
Uri uri = data.getData();
handleFile(uri);
}
}
}
private void handleFile(Uri... uris) {
// Hier kannst du die Logik zum Speichern oder Anzeigen der Datei implementieren
NoteSheetsUtil.sortNoteSheetsByTimestamp(getContext(), uris);
MetaDataDialog metaDataDialog = new MetaDataDialog();
metaDataDialog.setFileUri(uris);
metaDataDialog.show(getParentFragmentManager(), MetaDataDialog.TAG);
Toast.makeText(getActivity(), "Datei ausgewählt: " + uris[0].getPath(), Toast.LENGTH_SHORT).show();
}
private void openFileChooser() {
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
intent.setType("*/*"); // Alle Dateitypen
String[] mimeTypes = {"application/pdf", "image/png", "image/jpeg"};
intent.putExtra(Intent.EXTRA_MIME_TYPES, mimeTypes);
intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true);
intent.addCategory(Intent.CATEGORY_OPENABLE);
startActivityForResult(intent, PICK_FILE_REQUEST_CODE);
}
@Override
public void onDestroyView() {
super.onDestroyView();
binding = null;
}
public void addSong(MusicNote musicNote) {
this.homeViewModel.addSong(musicNote);
}
public void updateSong(MusicNote song) {
this.homeViewModel.updateSong(song);
}
}

View File

@ -1,63 +0,0 @@
package core.notevault.ui.home;
import android.net.Uri;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModel;
import core.notevault.MainActivity;
import core.notevault.data.MusicDatabase;
import core.notevault.data.MusicNote;
import core.notevault.ui.metadatadialog.MetaDataDialog;
import java.util.ArrayList;
import java.util.List;
public class HomeViewModel extends ViewModel {
private final MutableLiveData<List<MusicNote>> noteTitles;
public HomeViewModel() {
noteTitles = new MutableLiveData<>(new ArrayList<>());
}
public LiveData<List<MusicNote>> getNoteTitles() {
return noteTitles;
}
public void setNoteTitles(List<MusicNote> noteTitles) {
this.noteTitles.setValue(noteTitles);
}
public void addSong(MusicNote song) {
List<MusicNote> songs = noteTitles.getValue();
if(songs != null) {
songs.add(song);
this.noteTitles.setValue(songs);
}
}
public void deleteSong(MusicNote musicNote) {
List<MusicNote> currentSongs = noteTitles.getValue();
if(currentSongs != null) {
currentSongs.remove(musicNote);
noteTitles.setValue(currentSongs);
}
}
public void updateSong(MusicNote updatedSong) {
List<MusicNote> currentSongs = noteTitles.getValue();
if(currentSongs != null) {
for(int i=0; i<currentSongs.size(); i++) {
if(currentSongs.get(i).getMusicNoteId() == updatedSong.getMusicNoteId()) {
currentSongs.set(i, updatedSong);
break;
}
}
noteTitles.setValue(currentSongs);
}
}
}

View File

@ -1,157 +0,0 @@
package core.notevault.ui.home;
import android.content.Intent;
import android.os.AsyncTask;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageButton;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import core.notevault.R;
import core.notevault.data.MusicDatabase;
import core.notevault.data.MusicNote;
import core.notevault.data.MusicNoteDAO;
import core.notevault.data.NoteSheet;
import java.util.ArrayList;
import java.util.List;
public class NoteSongAdapter extends RecyclerView.Adapter<NoteSongAdapter.NoteViewHolder> {
private final List<MusicNote> noteTitles;
private final OnSongDeleteListener onSongDeleteListener;
private final OnSongEditListener onSongEditListener;
public interface OnSongDeleteListener {
void onSongDeleted(MusicNote song);
}
public interface OnSongEditListener {
void onSongEdit(MusicNote song);
}
public NoteSongAdapter(List<MusicNote> noteTitles, OnSongDeleteListener onSongDeleteListener, OnSongEditListener onSongEditListener) {
this.noteTitles = noteTitles;
this.onSongDeleteListener = onSongDeleteListener;
this.onSongEditListener = onSongEditListener;
}
public NoteSongAdapter(OnSongDeleteListener onSongDeleteListener, OnSongEditListener onSongEditListener) {
this.onSongDeleteListener = onSongDeleteListener;
this.noteTitles = new ArrayList<>();
this.onSongEditListener = onSongEditListener;
}
@NonNull
@Override
public NoteViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.item_song, parent, false);
return new NoteViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull NoteViewHolder holder, int position) {
holder.titleTextView.setText(noteTitles.get(position).getTitle());
holder.yearTextView.setText(String.valueOf(noteTitles.get(position).getYear()));
holder.compositionTextView.setText(noteTitles.get(position).getComposer());
holder.deleteButton.setOnClickListener(v -> {
onSongDeleteListener.onSongDeleted(noteTitles.get(position));
});
holder.editButton.setOnClickListener(v -> {
onSongEditListener.onSongEdit(noteTitles.get(position));
});
}
@Override
public int getItemCount() {
return noteTitles.size();
}
public void updateSongTitles(List<MusicNote> songs) {
this.noteTitles.clear();
this.noteTitles.addAll(songs);
notifyDataSetChanged();
}
public class NoteViewHolder extends RecyclerView.ViewHolder{
TextView titleTextView;
TextView yearTextView;
TextView compositionTextView;
ImageButton deleteButton;
ImageButton editButton;
NoteViewHolder(View itemView) {
super(itemView);
titleTextView = itemView.findViewById(R.id.text_title);
yearTextView = itemView.findViewById(R.id.text_year);
compositionTextView = itemView.findViewById(R.id.text_composition);
deleteButton = itemView.findViewById(R.id.delete_song_button);
editButton = itemView.findViewById(R.id.edit_song_button);
titleTextView.setOnClickListener(v -> {
int position = getAdapterPosition();
if(position != RecyclerView.NO_POSITION) {
MusicNote musicNote = noteTitles.get(position);
Intent intent = new Intent(itemView.getContext(), FullScreenImageActivity.class);
new LoadNoteSheetsTask(musicNote.getMusicNoteId(),
MusicDatabase.getDatabase(itemView.getContext()).musicNoteDao(), new OnNoteSheedsLoadedListener() {
@Override
public void onNoteSheetsLoaded(List<NoteSheet> noteSheets) {
String[] noteSheetFiles = new String[noteSheets.size()];
for (int i = 0; i < noteSheets.size(); i++) {
noteSheetFiles[i] = noteSheets.get(i).getFilePath();
}
intent.putExtra("imageUris", noteSheetFiles);
itemView.getContext().startActivity(intent);
}
}).execute();
}
});
}
public void bind(MusicNote note) {
titleTextView.setText(note.getTitle());
}
}
public interface OnNoteSheedsLoadedListener {
void onNoteSheetsLoaded(List<NoteSheet> noteSheets);
}
private class LoadNoteSheetsTask extends AsyncTask<Void, Void, List<NoteSheet>> {
private final long musicNoteId;
private final MusicNoteDAO musicNoteDAO;
private final OnNoteSheedsLoadedListener listener;
public LoadNoteSheetsTask(long musicNoteId, MusicNoteDAO musicNoteDAO, OnNoteSheedsLoadedListener listener) {
this.musicNoteId = musicNoteId;
this.musicNoteDAO = musicNoteDAO;
this.listener = listener;
}
@Override
protected List<NoteSheet> doInBackground(Void... voids) {
List<NoteSheet> sheets = musicNoteDAO.getNoteSheetsForMusicSong(this.musicNoteId);
Log.d("LoadedNoteSheetsTask", "ID: " + this.musicNoteId);
Log.d("LoadNoteSheetsTask", "Loaded NoteSheets: " + sheets.size());
return sheets;
}
@Override
protected void onPostExecute(List<NoteSheet> sheets) {
listener.onNoteSheetsLoaded(sheets);
}
}
}

View File

@ -1,27 +0,0 @@
package core.notevault.ui.login;
import android.content.Context;
import android.util.Log;
import android.widget.Toast;
import core.notevault.sync.APICallback;
import core.notevault.sync.auth.LoginCallback;
public class LoginCallBackImpl implements APICallback {
private Context context;
public LoginCallBackImpl(Context context) {
this.context = context;
}
@Override
public void onSuccess() {
Toast.makeText(context, "Login successfull", Toast.LENGTH_SHORT).show();
}
@Override
public void onError(String error) {
Log.d("Login", error);
Toast.makeText(context, "Login not successfull: " + error, Toast.LENGTH_LONG).show();
}
}

View File

@ -1,100 +0,0 @@
package core.notevault.ui.login;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.Context;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.DialogFragment;
import core.notevault.R;
import core.notevault.sync.APICallback;
import core.notevault.sync.auth.AuthRepository;
import core.notevault.sync.auth.LoginCallback;
import retrofit2.Call;
import retrofit2.Callback;
public class LoginDialogFragment extends DialogFragment {
private APICallback loginCallback;
private APICallback registerCallback;
private boolean isLoginMode = true; // Default mode
@Override
public void onAttach(Context context) {
super.onAttach(context);
if (loginCallback == null || registerCallback == null) {
throw new IllegalStateException("LoginDialogFragment must be created using newInstance()");
}
}
public static LoginDialogFragment newInstance(APICallback loginCallback, APICallback registerCallback) {
LoginDialogFragment fragment = new LoginDialogFragment();
fragment.loginCallback = loginCallback;
fragment.registerCallback = registerCallback;
return fragment;
}
@NonNull
@Override
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
/*return new AlertDialog.Builder(requireContext())
.setView(loginView)
.setPositiveButton("Speichern", (dialog, which) -> {
String username = usernameInput.getText().toString();
String password = passwordInput.getText().toString();
authRepository.performLogin(username, password, loginCallback);
} )
.setNegativeButton("Abbrechen", (dialog, which) -> {} )
.create();*/
AlertDialog.Builder builder = new AlertDialog.Builder(requireContext());
LayoutInflater inflater = requireActivity().getLayoutInflater();
View view = inflater.inflate(R.layout.fragment_login_dialog, null);
TextView textViewTitle = view.findViewById(R.id.textViewTitle);
EditText editTextUsername = view.findViewById(R.id.editTextUsername);
EditText editTextEmail = view.findViewById(R.id.editTextEmail);
EditText editTextPassword = view.findViewById(R.id.editTextPassword);
Button buttonAction = view.findViewById(R.id.buttonAction);
TextView textViewSwitch = view.findViewById(R.id.textViewSwitch);
// Handle action button click
buttonAction.setOnClickListener(v -> {
if (isLoginMode) {
// Handle login logic
String email = editTextEmail.getText().toString();
String password = editTextPassword.getText().toString();
AuthRepository authRepository = new AuthRepository(this.getContext());
authRepository.performLogin(email, password, loginCallback);
} else {
// Handle registration logic
String email = editTextEmail.getText().toString();
String password = editTextPassword.getText().toString();
String username = editTextUsername.getText().toString();
AuthRepository authRepository = new AuthRepository(this.getContext());
authRepository.performRegistration(email, username, password, registerCallback);
}
});
// Toggle between Login and Registration
textViewSwitch.setOnClickListener(v -> {
isLoginMode = !isLoginMode;
textViewTitle.setText(isLoginMode ? "Login" : "Register");
editTextUsername.setVisibility(isLoginMode ? View.GONE : View.VISIBLE);
buttonAction.setText(isLoginMode ? "Login" : "Register");
textViewSwitch.setText(isLoginMode ? "Don't have an account? Register" : "Already have an account? Login");
});
builder.setView(view);
return builder.create();
}
}

View File

@ -1,25 +0,0 @@
package core.notevault.ui.login;
import android.content.Context;
import android.util.Log;
import android.widget.Toast;
import core.notevault.sync.APICallback;
public class RegisterCallback implements APICallback {
private Context context;
public RegisterCallback(Context context) {
this.context = context;
}
@Override
public void onSuccess() {
Toast.makeText(context, "Registration successfull", Toast.LENGTH_SHORT).show();
}
@Override
public void onError(String error) {
Log.d("Login", error);
Toast.makeText(context, "Login not successfull: " + error, Toast.LENGTH_LONG).show();
}
}

View File

@ -1,108 +0,0 @@
package core.notevault.ui.metadatadialog;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.provider.OpenableColumns;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.EditText;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.DialogFragment;
import core.notevault.R;
public class MetaDataDialog extends DialogFragment {
private Uri[] fileUri;
public interface OnMetadataListener {
void onMetadataEntered(Uri[] uri, String title, String composer, int year, String genre);
}
private OnMetadataListener listener;
@NonNull
@Override
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
LayoutInflater inflater = requireActivity().getLayoutInflater();
View dialogView = inflater.inflate(R.layout.fragment_metadata_dialog, null); // Ersetze 'your_dialog_layout' durch deinen Dateinamen
EditText title_input = dialogView.findViewById(R.id.title_input);
title_input.setText(this.extraxtTitleFromFilePath());
EditText composer_input = dialogView.findViewById(R.id.composer_input);
EditText year_input = dialogView.findViewById(R.id.year_input);
EditText genre_input = dialogView.findViewById(R.id.genre_input);
return new AlertDialog.Builder(requireContext())
.setView(dialogView)
.setPositiveButton("Speichern", (dialog, which) -> {
String title = title_input.getText().toString();
String composer = composer_input.getText().toString();
String year_string = year_input.getText().toString();
int year = 0;
if(!year_string.isEmpty()) {
year = Integer.parseInt(year_input.getText().toString());
}
String genre = genre_input.getText().toString();
listener.onMetadataEntered(fileUri, title, composer, year, genre);
} )
.setNegativeButton("Abbrechen", (dialog, which) -> {} )
.create();
}
@Override
public void onAttach(@NonNull Context context) {
super.onAttach(context);
if (context instanceof OnMetadataListener) {
listener = (OnMetadataListener) context;
} else {
throw new RuntimeException(context.toString()
+ " must implement OnMetadataListener");
}
}
public Uri[] getFileUri() {
return fileUri;
}
public void setFileUri(Uri[] fileUri) {
this.fileUri = fileUri;
}
private String extraxtTitleFromFilePath() {
String fileName = "";
// Überprüfen, ob die Uri ein Content-Uri ist
if (this.fileUri[0].getScheme().equals("content")) {
// ContentResolver verwenden, um die Datei zu finden
Cursor cursor = requireContext().getContentResolver().query(this.fileUri[0], null, null, null, null);
if (cursor != null) {
int nameIndex = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME);
if (cursor.moveToFirst()) {
// Den Dateinamen aus dem Cursor abfragen
fileName = cursor.getString(nameIndex);
}
cursor.close();
}
} else if (this.fileUri[0].getScheme().equals("file")) {
// Bei einer Datei-Uri einfach den letzten Pfadsegment verwenden
fileName = this.fileUri[0].getLastPathSegment();
}
if(fileName.contains(".")) {
fileName = fileName.substring(0, fileName.lastIndexOf("."));
fileName = fileName.replace("_", " ");
fileName = fileName.replace("-", "");
}
return fileName;
}
public static String TAG = "MetaDataDialog";
}

View File

@ -1,90 +0,0 @@
package core.notevault.ui.metadatadialog;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.Context;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.EditText;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.DialogFragment;
import core.notevault.R;
import core.notevault.data.MusicNote;
import core.notevault.data.sync.SyncStatus;
public class SongEditDialog extends DialogFragment {
private MusicNote song;
private SongEditorListener songEditorListener;
public interface SongEditorListener {
void onSongEdited(MusicNote updatedSong);
}
@NonNull
@Override
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
LayoutInflater inflater = requireActivity().getLayoutInflater();
View dialogView = inflater.inflate(R.layout.song_metadata_edit_dialog, null); // Ersetze 'your_dialog_layout' durch deinen Dateinamen
EditText title_input = dialogView.findViewById(R.id.title_input);
title_input.setText(song.getTitle());
EditText composer_input = dialogView.findViewById(R.id.composer_input);
composer_input.setText(song.getComposer());
EditText year_input = dialogView.findViewById(R.id.year_input);
year_input.setText(String.valueOf(song.getYear()));
EditText genre_input = dialogView.findViewById(R.id.genre_input);
genre_input.setText(song.getGenre());
return new AlertDialog.Builder(requireContext())
.setView(dialogView)
.setPositiveButton("Speichern", (dialog, which) -> {
String title = title_input.getText().toString();
String composer = composer_input.getText().toString();
String year_string = year_input.getText().toString();
int year = 0;
if(!year_string.isEmpty()) {
year = Integer.parseInt(year_input.getText().toString());
}
String genre = genre_input.getText().toString();
song.setTitle(title);
song.setComposer(composer);
song.setGenre(genre);
song.setYear(year);
if(song.getSyncStatus() == SyncStatus.SYNCED) {
song.setSyncStatus(SyncStatus.MODIFIED);
}
songEditorListener.onSongEdited(song);
} )
.setNegativeButton("Abbrechen", (dialog, which) -> {} )
.create();
}
public MusicNote getSong() {
return song;
}
public void setSong(MusicNote song) {
this.song = song;
}
@Override
public void onAttach(@NonNull Context context) {
super.onAttach(context);
if (context instanceof MetaDataDialog.OnMetadataListener) {
songEditorListener = (SongEditorListener) context;
} else {
throw new RuntimeException(context.toString()
+ " must implement OnMetadataListener");
}
}
public static String TAG = "SONGEDITDIALOG";
}

View File

@ -1,50 +0,0 @@
package core.notevault.ui.noteviewer;
import android.content.Context;
import android.net.Uri;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import com.github.chrisbanes.photoview.PhotoView;
import core.notevault.R;
import java.util.List;
public class ImagePagerAdapter extends RecyclerView.Adapter<ImagePagerAdapter.ImageViewHolder> {
private final List<String> imageUris;
private final Context context;
public ImagePagerAdapter(Context context, List<String> imageUris) {
this.context = context;
this.imageUris = imageUris;
}
@NonNull
@Override
public ImageViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(context).inflate(R.layout.item_image, parent, false);
return new ImageViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull ImageViewHolder holder, int position) {
Uri uri = Uri.parse(imageUris.get(position));
holder.photoView.setImageURI(uri);
}
@Override
public int getItemCount() {
return imageUris.size();
}
static class ImageViewHolder extends RecyclerView.ViewHolder {
PhotoView photoView;
ImageViewHolder(View itemView) {
super(itemView);
photoView = itemView.findViewById(R.id.photoView);
}
}
}

View File

@ -1,35 +0,0 @@
package core.notevault.ui.slideshow;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider;
import core.notevault.databinding.FragmentSlideshowBinding;
public class SlideshowFragment extends Fragment {
private FragmentSlideshowBinding binding;
public View onCreateView(@NonNull LayoutInflater inflater,
ViewGroup container, Bundle savedInstanceState) {
SlideshowViewModel slideshowViewModel =
new ViewModelProvider(this).get(SlideshowViewModel.class);
binding = FragmentSlideshowBinding.inflate(inflater, container, false);
View root = binding.getRoot();
final TextView textView = binding.textSlideshow;
slideshowViewModel.getText().observe(getViewLifecycleOwner(), textView::setText);
return root;
}
@Override
public void onDestroyView() {
super.onDestroyView();
binding = null;
}
}

View File

@ -1,19 +0,0 @@
package core.notevault.ui.slideshow;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModel;
public class SlideshowViewModel extends ViewModel {
private final MutableLiveData<String> mText;
public SlideshowViewModel() {
mText = new MutableLiveData<>();
mText.setValue("This is slideshow fragment");
}
public LiveData<String> getText() {
return mText;
}
}

Some files were not shown because too many files have changed in this diff Show More