Compare commits
33 Commits
master
...
notevault-
Author | SHA1 | Date | |
---|---|---|---|
|
390051842f | ||
|
553906e802 | ||
|
8508ec8c10 | ||
|
6f6f8cf605 | ||
|
648bd66ba4 | ||
|
dfabf7e721 | ||
|
6f6e9a3b10 | ||
|
75ae569a55 | ||
|
da53e4507c | ||
|
d059f7ed8e | ||
|
a53bc4bee7 | ||
|
007f6a8501 | ||
|
d221502bef | ||
|
dd0bfc2672 | ||
|
ebe09e0ba1 | ||
|
191c50bac3 | ||
|
b3c418968d | ||
|
272e1f4242 | ||
|
83a6c27c56 | ||
|
1096be3051 | ||
|
7aa30fd4d7 | ||
|
46852a9b5a | ||
|
71bc0623a5 | ||
|
2f38b0eed7 | ||
|
7e04b7c9f3 | ||
|
c04dbe565a | ||
|
d9b59ba07c | ||
|
cf9b66b648 | ||
|
ae85c7db5a | ||
|
417477685d | ||
|
6a6a7bfa90 | ||
|
899f3a254d | ||
|
06e5ce1301 |
46
.gitea/workflows/job.yaml
Normal file
46
.gitea/workflows/job.yaml
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
+name: Build and Upload APK
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- notevault-3 # Trigger für den Hauptbranch
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- notevault-3 # Trigger für Pull Requests in den Hauptbranch
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
# Schritt 1: Checkout des Repositories
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
|
# Schritt 2: Setup JDK
|
||||||
|
- name: Set up JDK 11
|
||||||
|
uses: actions/setup-java@v2
|
||||||
|
with:
|
||||||
|
java-version: '11'
|
||||||
|
|
||||||
|
# Schritt 3: Installiere Gradle und Baue die APK
|
||||||
|
- name: Build APK
|
||||||
|
run: |
|
||||||
|
sudo apt-get update
|
||||||
|
sudo apt-get install -y wget unzip
|
||||||
|
wget https://services.gradle.org/distributions/gradle-7.4-bin.zip
|
||||||
|
unzip gradle-7.4-bin.zip
|
||||||
|
export PATH=$PWD/gradle-7.4/bin:$PATH
|
||||||
|
./gradlew clean assembleRelease
|
||||||
|
|
||||||
|
# Schritt 4: Upload der APK
|
||||||
|
- name: Upload APK to Gitea Releases
|
||||||
|
uses: pappasam/gitea-release-action@v1
|
||||||
|
with:
|
||||||
|
gitea_token: ${{ secrets.REGISTRY_PASSWORD }} # Dein Gitea API-Token
|
||||||
|
gitea_url: 'https://git.fawkes100.de' # Deine Gitea-URL
|
||||||
|
owner: ${{ secrets.REGISTRY_USER }} # Dein Gitea-Benutzername
|
||||||
|
repo: 'NoteVault' # Dein Repository-Name
|
||||||
|
tag_name: 'v${{ github.sha }}' # Der Tag für die Version
|
||||||
|
file: 'app/build/outputs/apk/release/app-release.apk' # Pfad zur APK-Datei
|
||||||
|
|
3
.gitignore
vendored
3
.gitignore
vendored
@ -13,3 +13,6 @@
|
|||||||
.externalNativeBuild
|
.externalNativeBuild
|
||||||
.cxx
|
.cxx
|
||||||
local.properties
|
local.properties
|
||||||
|
/music_database
|
||||||
|
/music_database-shm
|
||||||
|
/music_database-wal
|
||||||
|
1
.idea/.name
Normal file
1
.idea/.name
Normal file
@ -0,0 +1 @@
|
|||||||
|
NoteVault
|
@ -1,5 +0,0 @@
|
|||||||
<component name="ProjectCodeStyleConfiguration">
|
|
||||||
<state>
|
|
||||||
<option name="PREFERRED_PROJECT_CODE_STYLE" value="Default" />
|
|
||||||
</state>
|
|
||||||
</component>
|
|
@ -1,7 +1,7 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<project version="4">
|
<project version="4">
|
||||||
<component name="DataSourceManagerImpl" format="xml" multifile-model="true">
|
<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="eb23f694-6586-450b-8f6f-a75731d36b96">
|
||||||
<driver-ref>sqlite.xerial</driver-ref>
|
<driver-ref>sqlite.xerial</driver-ref>
|
||||||
<synchronize>true</synchronize>
|
<synchronize>true</synchronize>
|
||||||
<jdbc-driver>org.sqlite.JDBC</jdbc-driver>
|
<jdbc-driver>org.sqlite.JDBC</jdbc-driver>
|
||||||
@ -10,26 +10,6 @@
|
|||||||
<property name="com.intellij.clouds.kubernetes.db.enabled" value="false" />
|
<property name="com.intellij.clouds.kubernetes.db.enabled" value="false" />
|
||||||
</jdbc-additional-properties>
|
</jdbc-additional-properties>
|
||||||
<working-dir>$ProjectFileDir$</working-dir>
|
<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-additional-properties>
|
|
||||||
<property name="com.intellij.clouds.kubernetes.db.enabled" value="false" />
|
|
||||||
</jdbc-additional-properties>
|
|
||||||
<working-dir>$ProjectFileDir$</working-dir>
|
|
||||||
<libraries>
|
<libraries>
|
||||||
<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>
|
<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>
|
||||||
|
@ -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>
|
|
@ -4,6 +4,14 @@
|
|||||||
<selectionStates>
|
<selectionStates>
|
||||||
<SelectionState runConfigName="app">
|
<SelectionState runConfigName="app">
|
||||||
<option name="selectionMode" value="DROPDOWN" />
|
<option name="selectionMode" value="DROPDOWN" />
|
||||||
|
<DropdownSelection timestamp="2025-04-10T18:07:32.446414465Z">
|
||||||
|
<Target type="DEFAULT_BOOT">
|
||||||
|
<handle>
|
||||||
|
<DeviceId pluginId="PhysicalDevice" identifier="serial=R52N50NLGRT" />
|
||||||
|
</handle>
|
||||||
|
</Target>
|
||||||
|
</DropdownSelection>
|
||||||
|
<DialogSelection />
|
||||||
</SelectionState>
|
</SelectionState>
|
||||||
</selectionStates>
|
</selectionStates>
|
||||||
</component>
|
</component>
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
<option name="linkedExternalProjectsSettings">
|
<option name="linkedExternalProjectsSettings">
|
||||||
<GradleProjectSettings>
|
<GradleProjectSettings>
|
||||||
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
||||||
<option name="gradleJvm" value="21" />
|
<option name="gradleJvm" value="#GRADLE_LOCAL_JAVA_HOME" />
|
||||||
<option name="modules">
|
<option name="modules">
|
||||||
<set>
|
<set>
|
||||||
<option value="$PROJECT_DIR$" />
|
<option value="$PROJECT_DIR$" />
|
||||||
|
17
.idea/runConfigurations.xml
Normal file
17
.idea/runConfigurations.xml
Normal 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>
|
@ -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>
|
|
@ -1,6 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<project version="4">
|
<project version="4">
|
||||||
<component name="VcsDirectoryMappings">
|
<component name="VcsDirectoryMappings">
|
||||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
<mapping directory="" vcs="Git" />
|
||||||
</component>
|
</component>
|
||||||
</project>
|
</project>
|
@ -1,16 +1,15 @@
|
|||||||
plugins {
|
plugins {
|
||||||
alias(libs.plugins.android.application)
|
alias(libs.plugins.android.application)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
namespace = "core.notevault"
|
namespace = "com.stormtales.notevault"
|
||||||
compileSdk = 35
|
compileSdk = 34
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId = "core.notevault"
|
applicationId = "com.stormtales.notevault"
|
||||||
minSdk = 28
|
minSdk = 29
|
||||||
targetSdk = 35
|
targetSdk = 34
|
||||||
versionCode = 1
|
versionCode = 1
|
||||||
versionName = "1.0"
|
versionName = "1.0"
|
||||||
|
|
||||||
@ -22,14 +21,10 @@ android {
|
|||||||
isMinifyEnabled = false
|
isMinifyEnabled = false
|
||||||
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
|
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 {
|
compileOptions {
|
||||||
sourceCompatibility = JavaVersion.VERSION_1_8
|
sourceCompatibility = JavaVersion.VERSION_11
|
||||||
targetCompatibility = JavaVersion.VERSION_1_8
|
targetCompatibility = JavaVersion.VERSION_11
|
||||||
}
|
}
|
||||||
buildFeatures {
|
buildFeatures {
|
||||||
viewBinding = true
|
viewBinding = true
|
||||||
@ -45,25 +40,14 @@ dependencies {
|
|||||||
implementation(libs.lifecycle.viewmodel.ktx)
|
implementation(libs.lifecycle.viewmodel.ktx)
|
||||||
implementation(libs.navigation.fragment)
|
implementation(libs.navigation.fragment)
|
||||||
implementation(libs.navigation.ui)
|
implementation(libs.navigation.ui)
|
||||||
implementation(libs.room.common)
|
implementation(libs.room.runtime)
|
||||||
implementation("androidx.recyclerview:recyclerview:1.3.2")
|
implementation(libs.annotation)
|
||||||
implementation("com.github.chrisbanes:PhotoView:2.3.0")
|
|
||||||
implementation(libs.car.ui.lib)
|
|
||||||
testImplementation(libs.junit)
|
testImplementation(libs.junit)
|
||||||
androidTestImplementation(libs.ext.junit)
|
androidTestImplementation(libs.ext.junit)
|
||||||
androidTestImplementation(libs.espresso.core)
|
androidTestImplementation(libs.espresso.core)
|
||||||
|
annotationProcessor(libs.room.compiler)
|
||||||
|
implementation("com.github.chrisbanes:PhotoView:2.3.0")
|
||||||
implementation("com.squareup.retrofit2:retrofit:2.9.0")
|
implementation("com.squareup.retrofit2:retrofit:2.9.0")
|
||||||
implementation("com.squareup.retrofit2:converter-gson:2.9.0")
|
implementation("com.squareup.retrofit2:converter-gson:2.9.0")
|
||||||
implementation("com.squareup.okhttp3:logging-interceptor:4.9.1")
|
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")
|
|
||||||
}
|
}
|
@ -1,4 +1,4 @@
|
|||||||
package core.notevault;
|
package com.stormtales.notevault;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import androidx.test.platform.app.InstrumentationRegistry;
|
import androidx.test.platform.app.InstrumentationRegistry;
|
||||||
@ -20,6 +20,6 @@ public class ExampleInstrumentedTest {
|
|||||||
public void useAppContext() {
|
public void useAppContext() {
|
||||||
// Context of the app under test.
|
// Context of the app under test.
|
||||||
Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
|
Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
|
||||||
assertEquals("core.notevault", appContext.getPackageName());
|
assertEquals("com.stormtales.notevault", appContext.getPackageName());
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,18 +1,17 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:tools="http://schemas.android.com/tools">
|
xmlns:tools="http://schemas.android.com/tools">
|
||||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
|
||||||
<uses-permission android:name="android.permission.INTERNET" />
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
<application
|
<application
|
||||||
android:allowBackup="true"
|
android:allowBackup="true"
|
||||||
android:dataExtractionRules="@xml/data_extraction_rules"
|
android:dataExtractionRules="@xml/data_extraction_rules"
|
||||||
android:fullBackupContent="@xml/backup_rules"
|
android:fullBackupContent="@xml/backup_rules"
|
||||||
|
android:networkSecurityConfig="@xml/network_security_config"
|
||||||
android:icon="@mipmap/ic_launcher"
|
android:icon="@mipmap/ic_launcher"
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
android:roundIcon="@mipmap/ic_launcher_round"
|
android:roundIcon="@mipmap/ic_launcher_round"
|
||||||
android:supportsRtl="true"
|
android:supportsRtl="true"
|
||||||
android:theme="@style/Theme.NoteVault"
|
android:theme="@style/Theme.NoteVault"
|
||||||
android:usesCleartextTraffic="true"
|
|
||||||
tools:targetApi="31">
|
tools:targetApi="31">
|
||||||
<activity
|
<activity
|
||||||
android:name=".MainActivity"
|
android:name=".MainActivity"
|
||||||
@ -26,11 +25,10 @@
|
|||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
<activity android:name=".ui.home.FullScreenImageActivity"
|
<activity android:name=".ui.sheetdisplay.NoteSheetDisplayActivity"
|
||||||
android:theme="@style/Theme.AppCompat.NoActionBar">
|
android:theme="@style/Theme.AppCompat.NoActionBar">
|
||||||
<!-- Intent Filter hinzufügen, wenn Activity vom Launcher oder externen Apps aufgerufen werden soll -->
|
<!-- Intent Filter hinzufügen, wenn Activity vom Launcher oder externen Apps aufgerufen werden soll -->
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
</manifest>
|
</manifest>
|
90
app/src/main/java/com/stormtales/notevault/MainActivity.java
Normal file
90
app/src/main/java/com/stormtales/notevault/MainActivity.java
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
package com.stormtales.notevault;
|
||||||
|
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.view.MenuItem;
|
||||||
|
import android.view.Menu;
|
||||||
|
import android.widget.Toast;
|
||||||
|
import androidx.lifecycle.ViewModelProvider;
|
||||||
|
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 com.stormtales.notevault.databinding.ActivityMainBinding;
|
||||||
|
import com.stormtales.notevault.network.APICallback;
|
||||||
|
import com.stormtales.notevault.network.auth.AuthService;
|
||||||
|
import com.stormtales.notevault.ui.login.LoginDialog;
|
||||||
|
import com.stormtales.notevault.ui.login.LoginViewModel;
|
||||||
|
|
||||||
|
public class MainActivity extends AppCompatActivity {
|
||||||
|
|
||||||
|
private AppBarConfiguration mAppBarConfiguration;
|
||||||
|
private ActivityMainBinding binding;
|
||||||
|
private LoginViewModel loginViewModel;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
|
binding = ActivityMainBinding.inflate(getLayoutInflater());
|
||||||
|
setContentView(binding.getRoot());
|
||||||
|
|
||||||
|
setSupportActionBar(binding.appBarMain.toolbar);
|
||||||
|
binding.appBarMain.toolbar.setOnMenuItemClickListener(item -> {
|
||||||
|
if (item.getItemId() == R.id.auth_action) {
|
||||||
|
showLoginDialog();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
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);
|
||||||
|
|
||||||
|
loginViewModel = new ViewModelProvider(this).get(LoginViewModel.class);
|
||||||
|
loginViewModel.setAuthService(new AuthService(getApplicationContext()));
|
||||||
|
loginViewModel.getIsLoggedIn().observe(this, isLoggedIn -> {
|
||||||
|
this.invalidateOptionsMenu();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@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 onSupportNavigateUp() {
|
||||||
|
NavController navController = Navigation.findNavController(this, R.id.nav_host_fragment_content_main);
|
||||||
|
return NavigationUI.navigateUp(navController, mAppBarConfiguration)
|
||||||
|
|| super.onSupportNavigateUp();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onPrepareOptionsMenu(Menu menu) {
|
||||||
|
MenuItem loginItem = menu.findItem(R.id.auth_action);
|
||||||
|
if (Boolean.TRUE.equals(loginViewModel.getIsLoggedIn().getValue())) {
|
||||||
|
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 showLoginDialog() {
|
||||||
|
LoginDialog loginDialog = new LoginDialog(loginViewModel);
|
||||||
|
loginDialog.show(getSupportFragmentManager(), "login");
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,33 @@
|
|||||||
|
package com.stormtales.notevault.data;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import androidx.room.Database;
|
||||||
|
import androidx.room.Room;
|
||||||
|
import androidx.room.RoomDatabase;
|
||||||
|
import androidx.room.TypeConverters;
|
||||||
|
import com.stormtales.notevault.data.entities.NoteSheet;
|
||||||
|
import com.stormtales.notevault.data.entities.Song;
|
||||||
|
import com.stormtales.notevault.data.dao.SongDao;
|
||||||
|
import com.stormtales.notevault.data.sync.DateConverter;
|
||||||
|
import com.stormtales.notevault.data.sync.SyncStatusConverter;
|
||||||
|
|
||||||
|
@Database(entities = {Song.class, NoteSheet.class}, version = 1, exportSchema = false)
|
||||||
|
@TypeConverters({SyncStatusConverter.class, DateConverter.class})
|
||||||
|
public abstract class MusicDatabase extends RoomDatabase {
|
||||||
|
public abstract SongDao getSongTable();
|
||||||
|
|
||||||
|
private static volatile 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;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,87 @@
|
|||||||
|
package com.stormtales.notevault.data.dao;
|
||||||
|
|
||||||
|
import androidx.lifecycle.LiveData;
|
||||||
|
import androidx.room.*;
|
||||||
|
import com.stormtales.notevault.data.entities.NoteSheet;
|
||||||
|
import com.stormtales.notevault.data.entities.Song;
|
||||||
|
import com.stormtales.notevault.data.sync.SyncStatus;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Dao
|
||||||
|
public interface SongDao {
|
||||||
|
@Insert
|
||||||
|
long insert(Song song);
|
||||||
|
|
||||||
|
@Query("SELECT * FROM song WHERE syncStatus != 1")
|
||||||
|
List<Song> getAllSongs();
|
||||||
|
|
||||||
|
@Update
|
||||||
|
void update(Song song);
|
||||||
|
|
||||||
|
@Insert
|
||||||
|
void insert(List<NoteSheet> noteSheets);
|
||||||
|
|
||||||
|
@Delete
|
||||||
|
void deleteNoteSheets(List<NoteSheet> noteSheets);
|
||||||
|
|
||||||
|
@Query("SELECT * FROM NoteSheet WHERE songID = :songID")
|
||||||
|
List<NoteSheet> getNoteSheetsBySong(int songID);
|
||||||
|
|
||||||
|
@Query("SELECT localFileName FROM NoteSheet WHERE songID = :songID")
|
||||||
|
List<String> getNoteSheetFilesBySongID(int songID);
|
||||||
|
|
||||||
|
@Query("SELECT * FROM Song WHERE syncStatus = :syncStatus")
|
||||||
|
List<Song> getSongsBySyncStatus(SyncStatus syncStatus);
|
||||||
|
|
||||||
|
@Update
|
||||||
|
void updateSongs(List<Song> songs);
|
||||||
|
|
||||||
|
@Query("SELECT * FROM Song WHERE localID IN (:localIDs)")
|
||||||
|
List<Song> getSongsByLocalIDs(List<Integer> localIDs);
|
||||||
|
|
||||||
|
@Query("SELECT * FROM Song WHERE serverID IN (:serverIDs)")
|
||||||
|
List<Song> getSongsByServerIDs(List<String> serverIDs);
|
||||||
|
|
||||||
|
@Query("DELETE FROM Song WHERE serverID IN (:serverIDs)")
|
||||||
|
void deleteSongsByServerIDs(List<String> serverIDs);
|
||||||
|
|
||||||
|
@Query("DELETE FROM SONG WHERE localID IN (:localIDs)")
|
||||||
|
void deleteSongsByLocalIDs(List<Integer> localIDs);
|
||||||
|
|
||||||
|
@Query("SELECT * FROM NoteSheet WHERE songID IN (:localSongIDs)")
|
||||||
|
List<NoteSheet> getNoteSheetsBySongIDs(List<Integer> localSongIDs);
|
||||||
|
|
||||||
|
@Query("SELECT * FROM NoteSheet WHERE songID IN (:songIDs)")
|
||||||
|
List<NoteSheet> getNoteSheetsByLocalFiles(List<Integer> songIDs);
|
||||||
|
|
||||||
|
@Update
|
||||||
|
void updateNoteSheets(List<NoteSheet> uploadedNoteSheets);
|
||||||
|
|
||||||
|
@Query("SELECT localID FROM Song WHERE serverID IN (:serverIDs)")
|
||||||
|
List<Integer> getLocalSongIDsByServerIDs(List<String> serverIDs);
|
||||||
|
|
||||||
|
@Query("SELECT * FROM NoteSheet WHERE serverFileName IN (:serverFileNames)")
|
||||||
|
List<NoteSheet> getNoteSheetsByServerFileNames(List<String> serverFileNames);
|
||||||
|
|
||||||
|
@Query("SELECT * FROM NoteSheet WHERE syncStatus = :status")
|
||||||
|
List<NoteSheet> getNoteSheetsBySyncStatus(SyncStatus status);
|
||||||
|
|
||||||
|
@Query("SELECT * FROM NoteSheet WHERE songID IN (:localSongIDs)")
|
||||||
|
List<NoteSheet> getNoteSheetFilesBySongIDs(List<Integer> localSongIDs);
|
||||||
|
|
||||||
|
@Insert
|
||||||
|
void insertSongs(List<Song> onlyRemoteExistingSongs);
|
||||||
|
|
||||||
|
@Delete
|
||||||
|
void deleteSong(Song song);
|
||||||
|
|
||||||
|
@Query("DELETE FROM NoteSheet WHERE songID = :localID")
|
||||||
|
void deleteNoteSheetsByLocalSongID(int localID);
|
||||||
|
|
||||||
|
@Update
|
||||||
|
void updateNoteSheet(NoteSheet noteSheet);
|
||||||
|
|
||||||
|
@Query("SELECT * FROM NoteSheet WHERE serverFileName =:serverFileName")
|
||||||
|
NoteSheet getNoteSheetByServerFileName(String serverFileName);
|
||||||
|
}
|
@ -0,0 +1,101 @@
|
|||||||
|
package com.stormtales.notevault.data.entities;
|
||||||
|
|
||||||
|
import androidx.room.Entity;
|
||||||
|
import androidx.room.Ignore;
|
||||||
|
import androidx.room.PrimaryKey;
|
||||||
|
import com.stormtales.notevault.data.sync.SyncStatus;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
public class NoteSheet {
|
||||||
|
@PrimaryKey(autoGenerate = true)
|
||||||
|
private int localID;
|
||||||
|
private int songID;
|
||||||
|
private String localFileName;
|
||||||
|
private String serverFileName;
|
||||||
|
private String hash;
|
||||||
|
|
||||||
|
private SyncStatus syncStatus;
|
||||||
|
|
||||||
|
@Ignore
|
||||||
|
public NoteSheet(String localFileName, String hash) {
|
||||||
|
this.localFileName = localFileName;
|
||||||
|
this.hash = hash;
|
||||||
|
this.syncStatus = SyncStatus.CREATED;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Ignore
|
||||||
|
public NoteSheet(int songID, String localFileName, String serverFileName, String hash) {
|
||||||
|
this.songID = songID;
|
||||||
|
this.localFileName = localFileName;
|
||||||
|
this.serverFileName = serverFileName;
|
||||||
|
this.hash = hash;
|
||||||
|
this.syncStatus = SyncStatus.REMOTE_MODIFIED;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public NoteSheet() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getLocalID() {
|
||||||
|
return localID;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLocalID(int localID) {
|
||||||
|
this.localID = localID;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getLocalFileName() {
|
||||||
|
return localFileName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLocalFileName(String localFileName) {
|
||||||
|
this.localFileName = localFileName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getHash() {
|
||||||
|
return hash;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setHash(String hash) {
|
||||||
|
this.hash = hash;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getServerFileName() {
|
||||||
|
return serverFileName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setServerFileName(String serverFileName) {
|
||||||
|
this.serverFileName = serverFileName;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (o == null || getClass() != o.getClass()) return false;
|
||||||
|
NoteSheet noteSheet = (NoteSheet) o;
|
||||||
|
return localID == noteSheet.localID && Objects.equals(localFileName, noteSheet.localFileName) && Objects.equals(hash, noteSheet.hash);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hash(localID, localFileName, hash);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getSongID() {
|
||||||
|
return songID;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSongID(int songID) {
|
||||||
|
this.songID = songID;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SyncStatus getSyncStatus() {
|
||||||
|
return syncStatus;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSyncStatus(SyncStatus syncStatus) {
|
||||||
|
this.syncStatus = syncStatus;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,131 @@
|
|||||||
|
package com.stormtales.notevault.data.entities;
|
||||||
|
|
||||||
|
import androidx.room.Entity;
|
||||||
|
import androidx.room.Ignore;
|
||||||
|
import androidx.room.PrimaryKey;
|
||||||
|
import com.stormtales.notevault.data.sync.SyncStatus;
|
||||||
|
import com.stormtales.notevault.network.sync.models.SongModel;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
public class Song {
|
||||||
|
@PrimaryKey(autoGenerate = true)
|
||||||
|
private int localID;
|
||||||
|
|
||||||
|
private String serverID;
|
||||||
|
private SyncStatus syncStatus;
|
||||||
|
private LocalDateTime syncTime;
|
||||||
|
|
||||||
|
/**Meta Data of Song*/
|
||||||
|
private String title;
|
||||||
|
private String composer;
|
||||||
|
private String genre;
|
||||||
|
private int year;
|
||||||
|
|
||||||
|
@Ignore
|
||||||
|
public Song(String title, String composer, String genre, int year) {
|
||||||
|
this.title = title;
|
||||||
|
this.composer = composer;
|
||||||
|
this.genre = genre;
|
||||||
|
this.year = year;
|
||||||
|
this.syncStatus = SyncStatus.CREATED;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Ignore
|
||||||
|
public Song(SongModel songModel) {
|
||||||
|
this.serverID = songModel.getServerID();
|
||||||
|
this.title = songModel.getTitle();
|
||||||
|
this.composer = songModel.getComposer();
|
||||||
|
this.genre = songModel.getGenre();
|
||||||
|
this.year = songModel.getYear();
|
||||||
|
this.syncStatus = SyncStatus.SYNCED;
|
||||||
|
this.syncTime = LocalDateTime.now();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Song() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Ignore
|
||||||
|
public Song(String serverID) {
|
||||||
|
this.serverID = serverID;
|
||||||
|
this.syncStatus = SyncStatus.REMOTE_MODIFIED;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SyncStatus getSyncStatus() {
|
||||||
|
return syncStatus;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSyncStatus(SyncStatus syncStatus) {
|
||||||
|
this.syncStatus = syncStatus;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LocalDateTime getSyncTime() {
|
||||||
|
return syncTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSyncTime(LocalDateTime syncTime) {
|
||||||
|
this.syncTime = syncTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (o == null || getClass() != o.getClass()) return false;
|
||||||
|
Song song = (Song) o;
|
||||||
|
return localID == song.localID && Objects.equals(serverID, song.serverID);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hash(localID, serverID);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,15 @@
|
|||||||
|
package com.stormtales.notevault.data.entities;
|
||||||
|
|
||||||
|
import androidx.room.Embedded;
|
||||||
|
import androidx.room.Relation;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class SongNoteSheet {
|
||||||
|
@Embedded public Song song;
|
||||||
|
@Relation(
|
||||||
|
parentColumn = "localID",
|
||||||
|
entityColumn = "songID"
|
||||||
|
)
|
||||||
|
public List<NoteSheet> noteSheets;
|
||||||
|
}
|
@ -0,0 +1,23 @@
|
|||||||
|
package com.stormtales.notevault.data.model;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Data class that captures user information for logged in users retrieved from LoginRepository
|
||||||
|
*/
|
||||||
|
public class LoggedInUser {
|
||||||
|
|
||||||
|
private String userId;
|
||||||
|
private String displayName;
|
||||||
|
|
||||||
|
public LoggedInUser(String userId, String displayName) {
|
||||||
|
this.userId = userId;
|
||||||
|
this.displayName = displayName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getUserId() {
|
||||||
|
return userId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDisplayName() {
|
||||||
|
return displayName;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,86 @@
|
|||||||
|
package com.stormtales.notevault.data.repositories;
|
||||||
|
|
||||||
|
import android.app.Application;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.os.Looper;
|
||||||
|
import androidx.lifecycle.LiveData;
|
||||||
|
import com.stormtales.notevault.data.MusicDatabase;
|
||||||
|
import com.stormtales.notevault.data.dao.SongDao;
|
||||||
|
import com.stormtales.notevault.data.entities.NoteSheet;
|
||||||
|
import com.stormtales.notevault.data.entities.Song;
|
||||||
|
import com.stormtales.notevault.data.sync.SyncStatus;
|
||||||
|
import com.stormtales.notevault.utils.NoteSheetsUtil;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
|
||||||
|
public class SongRepository {
|
||||||
|
private SongDao songDao;
|
||||||
|
public SongRepository(Context context) {
|
||||||
|
MusicDatabase database = MusicDatabase.getDatabase(context);
|
||||||
|
songDao = database.getSongTable();
|
||||||
|
}
|
||||||
|
public void insert(Song song, Callback<Long> callback) {
|
||||||
|
Executors.newSingleThreadExecutor().execute(() -> {
|
||||||
|
long result = songDao.insert(song);
|
||||||
|
Handler mainHandler = new Handler(Looper.getMainLooper());
|
||||||
|
mainHandler.post(()-> callback.onResult(result));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface LoadHomeViewModelCallback<T> {
|
||||||
|
void onResult(T result);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void getAllSongs(LoadHomeViewModelCallback<List<Song>> callback) {
|
||||||
|
ExecutorService executorService = Executors.newSingleThreadExecutor();
|
||||||
|
executorService.execute(() -> {
|
||||||
|
List<Song> songs = songDao.getAllSongs();
|
||||||
|
Handler mainHandler = new Handler(Looper.getMainLooper());
|
||||||
|
mainHandler.post(()-> callback.onResult(songs));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void updateSong(Song song) {
|
||||||
|
Executors.newSingleThreadExecutor().execute(() -> {
|
||||||
|
songDao.update(song);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void insertNoteSheets(List<NoteSheet> noteSheets) {
|
||||||
|
Executors.newSingleThreadExecutor().execute(() -> {
|
||||||
|
songDao.insert(noteSheets);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface Callback<T> {
|
||||||
|
void onResult(T result);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void deleteSong(Song song) {
|
||||||
|
Executors.newSingleThreadExecutor().execute(() -> {
|
||||||
|
song.setSyncStatus(SyncStatus.DELETED);
|
||||||
|
songDao.update(song);
|
||||||
|
|
||||||
|
List<NoteSheet> noteSheets = songDao.getNoteSheetsBySong(song.getLocalID());
|
||||||
|
for(NoteSheet noteSheet : noteSheets) {
|
||||||
|
try {
|
||||||
|
NoteSheetsUtil.deleteNoteSheet(noteSheet.getLocalFileName());
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
songDao.deleteNoteSheets(noteSheets);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void getNoteSheetFilesBySongID(int songID, Callback<List<String>> callback) {
|
||||||
|
Executors.newSingleThreadExecutor().execute(()-> {
|
||||||
|
List<String> noteSheetFiles = songDao.getNoteSheetFilesBySongID(songID);
|
||||||
|
callback.onResult(noteSheetFiles);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,303 @@
|
|||||||
|
package com.stormtales.notevault.data.repositories;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.os.Looper;
|
||||||
|
import androidx.recyclerview.widget.AsyncListUtil;
|
||||||
|
import com.stormtales.notevault.data.MusicDatabase;
|
||||||
|
import com.stormtales.notevault.data.dao.SongDao;
|
||||||
|
import com.stormtales.notevault.data.entities.NoteSheet;
|
||||||
|
import com.stormtales.notevault.data.entities.Song;
|
||||||
|
import com.stormtales.notevault.data.sync.SyncStatus;
|
||||||
|
import com.stormtales.notevault.network.sync.models.*;
|
||||||
|
import com.stormtales.notevault.utils.NoteSheetsUtil;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
public class SongSyncRepository {
|
||||||
|
private SongDao songDao;
|
||||||
|
public SongSyncRepository(Context context) {
|
||||||
|
MusicDatabase database = MusicDatabase.getDatabase(context);
|
||||||
|
songDao = database.getSongTable();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void loadCreatedSongs(LoadDataCallback<List<Song>> callback) {
|
||||||
|
Executors.newSingleThreadExecutor().execute(() -> {
|
||||||
|
List<Song> createdSongs = songDao.getSongsBySyncStatus(SyncStatus.CREATED);
|
||||||
|
Handler mainHandler = new Handler(Looper.getMainLooper());
|
||||||
|
mainHandler.post(()-> callback.onResult(createdSongs));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void loadModifiedSongs(LoadDataCallback<Map<Song, List<NoteSheet>>> callback) {
|
||||||
|
Executors.newSingleThreadExecutor().execute(() -> {
|
||||||
|
Map<Song, List<NoteSheet>> result = new HashMap<>();
|
||||||
|
List<Song> modifiedSongs = songDao.getSongsBySyncStatus(SyncStatus.MODIFIED);
|
||||||
|
for(Song song : modifiedSongs) {
|
||||||
|
List<NoteSheet> noteSheets = songDao.getNoteSheetsBySong(song.getLocalID());
|
||||||
|
result.put(song, noteSheets);
|
||||||
|
}
|
||||||
|
Handler mainHandler = new Handler(Looper.getMainLooper());
|
||||||
|
mainHandler.post(()-> callback.onResult(result));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void loadDeletedSongs(LoadDataCallback<List<Song>> callback) {
|
||||||
|
Executors.newSingleThreadExecutor().execute(() -> {
|
||||||
|
List<Song> deletedSongs = songDao.getSongsBySyncStatus(SyncStatus.DELETED);
|
||||||
|
Handler mainHandler = new Handler(Looper.getMainLooper());
|
||||||
|
mainHandler.post(()-> callback.onResult(deletedSongs));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void markCreatedSongsAsSynced(BatchCreateResponse createdSongs) {
|
||||||
|
Executors.newSingleThreadExecutor().execute(() -> {
|
||||||
|
List<Integer> localIDs = createdSongs.getCreateResponses().stream().map(CreateResponse::getLocalID).collect(Collectors.toList());
|
||||||
|
List<Song> requestedSongs = songDao.getSongsByLocalIDs(localIDs);
|
||||||
|
|
||||||
|
for(Song song : requestedSongs) {
|
||||||
|
song.setSyncTime(LocalDateTime.now());
|
||||||
|
song.setSyncStatus(SyncStatus.SYNCED);
|
||||||
|
|
||||||
|
for(CreateResponse createResponse : createdSongs.getCreateResponses()) {
|
||||||
|
if(createResponse.getLocalID() == song.getLocalID()) {
|
||||||
|
song.setServerID(createResponse.getServerID());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
songDao.updateSongs(requestedSongs);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void markSongsAsSynced(List<Song> songs) {
|
||||||
|
for(Song song : songs) {
|
||||||
|
song.setSyncStatus(SyncStatus.SYNCED);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void markModifiedSongsAsSynced(SongModifyBatchResponse modifyBatchResponse) {
|
||||||
|
Executors.newSingleThreadExecutor().execute(() -> {
|
||||||
|
List<String> serverIDs = modifyBatchResponse.getModifiedServerObjects().stream().map(SongModifyResponse::getServerID).collect(Collectors.toList());
|
||||||
|
List<Song> requestedSongs = songDao.getSongsByServerIDs(serverIDs);
|
||||||
|
for(Song song : requestedSongs) {
|
||||||
|
song.setSyncStatus(SyncStatus.SYNCED);
|
||||||
|
song.setSyncTime(LocalDateTime.now());
|
||||||
|
|
||||||
|
|
||||||
|
for(SongModifyResponse modifyResponse : modifyBatchResponse.getModifiedServerObjects()) {
|
||||||
|
if(modifyResponse.getServerID().equals(song.getServerID())) {
|
||||||
|
List<NoteSheet> outdatedNoteSheets = songDao.getNoteSheetsByServerFileNames(modifyResponse.getOutdated_note_sheets());
|
||||||
|
for(NoteSheet noteSheet : outdatedNoteSheets) {
|
||||||
|
noteSheet.setSyncStatus(SyncStatus.MODIFIED);
|
||||||
|
}
|
||||||
|
songDao.updateNoteSheets(outdatedNoteSheets);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
songDao.updateSongs(requestedSongs);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void markDeletedSongsAsSynced(List<String> remoteDeletedSongs, List<Integer> localDeletedSongs) {
|
||||||
|
Executors.newSingleThreadExecutor().execute(() -> {
|
||||||
|
songDao.deleteSongsByServerIDs(remoteDeletedSongs);
|
||||||
|
songDao.deleteSongsByLocalIDs(localDeletedSongs);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void getNoteSheetFilesBySongIDs(LoadDataCallback<List<NoteSheet>> callback) {
|
||||||
|
Executors.newSingleThreadExecutor().execute(() -> {
|
||||||
|
List<NoteSheet> noteSheets = songDao.getNoteSheetsBySyncStatus(SyncStatus.CREATED);
|
||||||
|
Handler mainHandler = new Handler(Looper.getMainLooper());
|
||||||
|
mainHandler.post(()-> callback.onResult(noteSheets));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void markCreatedNoteSheetsAsSynced(List<UploadResponse> uploadResponses) {
|
||||||
|
Executors.newSingleThreadExecutor().execute(() -> {
|
||||||
|
List<String> serverSongIDs = uploadResponses.stream().map(UploadResponse::getServerID).collect(Collectors.toList());
|
||||||
|
List<Integer> localSongIDs = songDao.getLocalSongIDsByServerIDs(serverSongIDs);
|
||||||
|
|
||||||
|
List<NoteSheet> uploadedNoteSheets = songDao.getNoteSheetsBySongIDs(localSongIDs);
|
||||||
|
for(UploadResponse uploadResponse : uploadResponses) {
|
||||||
|
for(NoteSheet noteSheet : uploadedNoteSheets) {
|
||||||
|
if(new File(noteSheet.getLocalFileName()).getName().equals(uploadResponse.getLocalFile())) {
|
||||||
|
noteSheet.setServerFileName(uploadResponse.getServerFile());
|
||||||
|
noteSheet.setSyncStatus(SyncStatus.SYNCED);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
songDao.updateNoteSheets(uploadedNoteSheets);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void loadModifiedNoteSheets(LoadDataCallback<List<NoteSheet>> callback) {
|
||||||
|
Executors.newSingleThreadExecutor().execute(() -> {
|
||||||
|
List<NoteSheet> noteSheets = songDao.getNoteSheetsBySyncStatus(SyncStatus.MODIFIED);
|
||||||
|
Handler mainHandler = new Handler(Looper.getMainLooper());
|
||||||
|
mainHandler.post(()-> callback.onResult(noteSheets));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void markModifiedNoteSheetsAsSynced(List<UploadResponse> uploadResponses) {
|
||||||
|
Executors.newSingleThreadExecutor().execute(() -> {
|
||||||
|
List<String> serverFileNames = uploadResponses.stream().map(UploadResponse::getServerFile).collect(Collectors.toList());
|
||||||
|
|
||||||
|
|
||||||
|
List<NoteSheet> noteSheets = songDao.getNoteSheetsByServerFileNames(serverFileNames);
|
||||||
|
for(NoteSheet noteSheet : noteSheets) {
|
||||||
|
noteSheet.setSyncStatus(SyncStatus.SYNCED);
|
||||||
|
}
|
||||||
|
songDao.updateNoteSheets(noteSheets);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void markSongsAsRemotelyModified(List<String> serverIDs, LoadDataCallback<List<Song>> callback) {
|
||||||
|
Executors.newSingleThreadExecutor().execute(() -> {
|
||||||
|
List<Song> songs = songDao.getSongsByServerIDs(serverIDs);
|
||||||
|
List<Song> createdSongs = new ArrayList<>();
|
||||||
|
for(Song song : songs) {
|
||||||
|
song.setSyncStatus(SyncStatus.REMOTE_MODIFIED);
|
||||||
|
}
|
||||||
|
|
||||||
|
for(String serverID : serverIDs) {
|
||||||
|
boolean foundServerSong = false;
|
||||||
|
for(Song song : songs) {
|
||||||
|
if(song.getServerID().equals(serverID)) {
|
||||||
|
foundServerSong = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!foundServerSong) {
|
||||||
|
Song song = new Song(serverID);
|
||||||
|
createdSongs.add(song);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
songDao.updateSongs(songs);
|
||||||
|
songDao.insertSongs(createdSongs);
|
||||||
|
callback.onResult(createdSongs);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void loadRemotelyModifiedSongs(LoadDataCallback<List<Song>> callback) {
|
||||||
|
Executors.newSingleThreadExecutor().execute(() -> {
|
||||||
|
List<Song> songs = songDao.getSongsBySyncStatus(SyncStatus.REMOTE_MODIFIED);
|
||||||
|
Handler mainHandler = new Handler(Looper.getMainLooper());
|
||||||
|
mainHandler.post(()-> callback.onResult(songs));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void saveRemoteSongs(List<SongModel> remoteSongs, LoadDataCallback<List<NoteSheet>> callback) {
|
||||||
|
Executors.newSingleThreadExecutor().execute(() ->{
|
||||||
|
List<String> serverIDs = remoteSongs.stream().map(SongModel::getServerID).collect(Collectors.toList());
|
||||||
|
List<Song> songs = songDao.getSongsByServerIDs(serverIDs);
|
||||||
|
List<Integer> localSongIDs = songDao.getLocalSongIDsByServerIDs(serverIDs);
|
||||||
|
List<NoteSheet> noteSheets = songDao.getNoteSheetsBySongIDs(localSongIDs);
|
||||||
|
|
||||||
|
List<Song> onlyRemoteExistingSongs = new ArrayList<>();
|
||||||
|
List<NoteSheet> outdatedNoteSheets = new ArrayList<>();
|
||||||
|
List<NoteSheet> onlyRemoteNoteSheets = new ArrayList<>();
|
||||||
|
for(SongModel songModel : remoteSongs) {
|
||||||
|
boolean found = false;
|
||||||
|
for(Song song : songs) {
|
||||||
|
if(song.getServerID().equals(songModel.getServerID())) {
|
||||||
|
found = true;
|
||||||
|
if(!songModel.isDeleted()) {
|
||||||
|
song.setSyncStatus(SyncStatus.SYNCED);
|
||||||
|
song.setSyncTime(LocalDateTime.now());
|
||||||
|
|
||||||
|
song.setTitle(songModel.getTitle());
|
||||||
|
song.setComposer(songModel.getComposer());
|
||||||
|
song.setGenre(songModel.getGenre());
|
||||||
|
song.setYear(songModel.getYear());
|
||||||
|
|
||||||
|
for(RemotelyModifiedNoteSheetModel remoteNoteSheet : songModel.getNote_sheets()) {
|
||||||
|
if(noteSheets.isEmpty()) {
|
||||||
|
NoteSheet noteSheet = new NoteSheet(song.getLocalID(), remoteNoteSheet.getFilename(), remoteNoteSheet.getServer_filename(), null);
|
||||||
|
onlyRemoteNoteSheets.add(noteSheet);
|
||||||
|
outdatedNoteSheets.add(noteSheet);
|
||||||
|
} else {
|
||||||
|
boolean foundNoteSheet = false;
|
||||||
|
for(NoteSheet noteSheet : noteSheets) {
|
||||||
|
if(remoteNoteSheet.getServer_filename().equals(noteSheet.getServerFileName())) {
|
||||||
|
foundNoteSheet = true;
|
||||||
|
if(!remoteNoteSheet.getHash().equals(noteSheet.getHash())) {
|
||||||
|
outdatedNoteSheets.add(noteSheet);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!foundNoteSheet) {
|
||||||
|
NoteSheet noteSheet = new NoteSheet(song.getLocalID(), remoteNoteSheet.getFilename(), remoteNoteSheet.getServer_filename(), null);
|
||||||
|
onlyRemoteNoteSheets.add(noteSheet);
|
||||||
|
outdatedNoteSheets.add(noteSheet);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
removeSong(song);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!found) {
|
||||||
|
Song song = new Song(songModel);
|
||||||
|
onlyRemoteExistingSongs.add(song);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
songDao.updateSongs(songs);
|
||||||
|
songDao.insertSongs(onlyRemoteExistingSongs);
|
||||||
|
songDao.insert(onlyRemoteNoteSheets);
|
||||||
|
|
||||||
|
Handler mainHandler = new Handler(Looper.getMainLooper());
|
||||||
|
mainHandler.post(()-> callback.onResult(outdatedNoteSheets));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void removeSong(Song song) {
|
||||||
|
List<NoteSheet> noteSheets = songDao.getNoteSheetsBySong(song.getLocalID());
|
||||||
|
for(NoteSheet noteSheet : noteSheets) {
|
||||||
|
try {
|
||||||
|
NoteSheetsUtil.deleteNoteSheet(noteSheet.getLocalFileName());
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
songDao.deleteNoteSheets(noteSheets);
|
||||||
|
songDao.deleteSong(song);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public void saveUpdatedNoteSheet(String serverFileName, String localFileName, String hash) {
|
||||||
|
Executors.newSingleThreadExecutor().execute(()-> {
|
||||||
|
NoteSheet noteSheet = songDao.getNoteSheetByServerFileName(serverFileName);
|
||||||
|
noteSheet.setHash(hash);
|
||||||
|
noteSheet.setLocalFileName(localFileName);
|
||||||
|
songDao.updateNoteSheet(noteSheet);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface LoadDataCallback<T> {
|
||||||
|
void onResult(T result);
|
||||||
|
}
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
package core.notevault.data;
|
package com.stormtales.notevault.data.sync;
|
||||||
|
|
||||||
import androidx.room.TypeConverter;
|
import androidx.room.TypeConverter;
|
||||||
|
|
@ -1,4 +1,4 @@
|
|||||||
package core.notevault.data.sync;
|
package com.stormtales.notevault.data.sync;
|
||||||
|
|
||||||
public enum SyncStatus {
|
public enum SyncStatus {
|
||||||
CREATED,
|
CREATED,
|
@ -1,4 +1,4 @@
|
|||||||
package core.notevault.data.sync;
|
package com.stormtales.notevault.data.sync;
|
||||||
|
|
||||||
import androidx.room.TypeConverter;
|
import androidx.room.TypeConverter;
|
||||||
|
|
@ -1,7 +1,6 @@
|
|||||||
package core.notevault.sync;
|
package com.stormtales.notevault.network;
|
||||||
|
|
||||||
public interface APICallback {
|
public interface APICallback {
|
||||||
|
|
||||||
void onSuccess();
|
void onSuccess();
|
||||||
void onError(String error);
|
void onError(String error);
|
||||||
}
|
}
|
@ -1,15 +1,16 @@
|
|||||||
package core.notevault.sync;
|
package com.stormtales.notevault.network;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import core.notevault.sync.auth.AuthInterceptor;
|
import com.stormtales.notevault.network.auth.AuthInterceptor;
|
||||||
import core.notevault.sync.auth.TokenManager;
|
import com.stormtales.notevault.network.auth.TokenManager;
|
||||||
import okhttp3.OkHttpClient;
|
import okhttp3.OkHttpClient;
|
||||||
import okhttp3.logging.HttpLoggingInterceptor;
|
import okhttp3.logging.HttpLoggingInterceptor;
|
||||||
import retrofit2.Retrofit;
|
import retrofit2.Retrofit;
|
||||||
import retrofit2.converter.gson.GsonConverterFactory;
|
import retrofit2.converter.gson.GsonConverterFactory;
|
||||||
|
|
||||||
public class ApiClient {
|
public class NetworkModule {
|
||||||
|
private static final String BASE_URL = "https://notevault.fawkes100.de/";
|
||||||
private static Retrofit retrofit;
|
private static Retrofit retrofit;
|
||||||
|
|
||||||
public static Retrofit getRetrofitInstance(Context context) {
|
public static Retrofit getRetrofitInstance(Context context) {
|
||||||
@ -24,11 +25,12 @@ public class ApiClient {
|
|||||||
.build();
|
.build();
|
||||||
|
|
||||||
retrofit = new Retrofit.Builder()
|
retrofit = new Retrofit.Builder()
|
||||||
.baseUrl("http://192.168.178.30:8000/")
|
.baseUrl(BASE_URL)
|
||||||
.client(client)
|
.client(client)
|
||||||
.addConverterFactory(GsonConverterFactory.create())
|
.addConverterFactory(GsonConverterFactory.create())
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
return retrofit;
|
return retrofit;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -1,4 +1,4 @@
|
|||||||
package core.notevault.sync;
|
package com.stormtales.notevault.network;
|
||||||
|
|
||||||
public class StatusResponse {
|
public class StatusResponse {
|
||||||
|
|
@ -1,15 +1,12 @@
|
|||||||
package core.notevault.sync.auth;
|
package com.stormtales.notevault.network.auth;
|
||||||
|
|
||||||
import core.notevault.sync.StatusResponse;
|
import com.stormtales.notevault.network.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.Call;
|
||||||
import retrofit2.http.Body;
|
import retrofit2.http.Body;
|
||||||
import retrofit2.http.Headers;
|
import retrofit2.http.Headers;
|
||||||
import retrofit2.http.POST;
|
import retrofit2.http.POST;
|
||||||
|
|
||||||
public interface AuthService {
|
public interface AuthAPI {
|
||||||
|
|
||||||
@POST("/login/")
|
@POST("/login/")
|
||||||
Call<LoginResponse> login(@Body LoginRequest loginRequest);
|
Call<LoginResponse> login(@Body LoginRequest loginRequest);
|
@ -1,12 +1,9 @@
|
|||||||
package core.notevault.sync.auth;
|
package com.stormtales.notevault.network.auth;
|
||||||
|
|
||||||
import android.content.SharedPreferences;
|
|
||||||
import android.util.Log;
|
|
||||||
import okhttp3.Interceptor;
|
import okhttp3.Interceptor;
|
||||||
import okhttp3.Request;
|
import okhttp3.Request;
|
||||||
import okhttp3.Response;
|
import okhttp3.Response;
|
||||||
|
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
public class AuthInterceptor implements Interceptor {
|
public class AuthInterceptor implements Interceptor {
|
@ -1,37 +1,35 @@
|
|||||||
package core.notevault.sync.auth;
|
package com.stormtales.notevault.network.auth;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import core.notevault.sync.APICallback;
|
import android.text.TextUtils;
|
||||||
import core.notevault.sync.ApiClient;
|
import com.stormtales.notevault.network.APICallback;
|
||||||
import core.notevault.sync.StatusResponse;
|
import com.stormtales.notevault.network.NetworkModule;
|
||||||
import core.notevault.sync.auth.apimodel.LoginRequest;
|
import com.stormtales.notevault.network.StatusResponse;
|
||||||
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.Call;
|
||||||
import retrofit2.Callback;
|
import retrofit2.Callback;
|
||||||
import retrofit2.Response;
|
import retrofit2.Response;
|
||||||
|
|
||||||
public class AuthRepository {
|
public class AuthService {
|
||||||
private final AuthService authService;
|
|
||||||
|
private final AuthAPI authAPI;
|
||||||
private final TokenManager tokenManager;
|
private final TokenManager tokenManager;
|
||||||
|
|
||||||
public AuthRepository(Context context) {
|
public AuthService(Context context) {
|
||||||
this.authService = ApiClient.getRetrofitInstance(context).create(AuthService.class);
|
this.authAPI = NetworkModule.getRetrofitInstance(context).create(AuthAPI.class);
|
||||||
this.tokenManager = new TokenManager(context);
|
this.tokenManager = new TokenManager(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void performLogin(String email, String password, APICallback callback) {
|
public void performLogin(String email, String password, LoginCallback callback) {
|
||||||
LoginRequest loginRequest = new LoginRequest(email, password);
|
LoginRequest loginRequest = new LoginRequest(email, password);
|
||||||
|
|
||||||
authService.login(loginRequest).enqueue(new Callback<LoginResponse>() {
|
authAPI.login(loginRequest).enqueue(new Callback<LoginResponse>() {
|
||||||
@Override
|
@Override
|
||||||
public void onResponse(Call<LoginResponse> call, Response<LoginResponse> response) {
|
public void onResponse(Call<LoginResponse> call, Response<LoginResponse> response) {
|
||||||
if (response.isSuccessful() && response.body() != null) {
|
if (response.isSuccessful() && response.body() != null) {
|
||||||
String token = response.body().getToken();
|
String token = response.body().getToken();
|
||||||
saveToken(token);
|
saveToken(token);
|
||||||
// Erfolgsrückmeldung an den Callback senden
|
// Erfolgsrückmeldung an den Callback senden
|
||||||
callback.onSuccess();
|
callback.onSuccess(response.body());
|
||||||
} else {
|
} else {
|
||||||
// Fehlermeldung an den Callback senden
|
// Fehlermeldung an den Callback senden
|
||||||
callback.onError("Login fehlgeschlagen. Überprüfe Benutzername und Passwort.");
|
callback.onError("Login fehlgeschlagen. Überprüfe Benutzername und Passwort.");
|
||||||
@ -48,7 +46,7 @@ public class AuthRepository {
|
|||||||
|
|
||||||
public void performRegistration(String email, String username, String password, APICallback callback) {
|
public void performRegistration(String email, String username, String password, APICallback callback) {
|
||||||
RegisterRequest registerRequest = new RegisterRequest(username, password, email);
|
RegisterRequest registerRequest = new RegisterRequest(username, password, email);
|
||||||
authService.registration(registerRequest).enqueue(new Callback<StatusResponse>() {
|
authAPI.registration(registerRequest).enqueue(new Callback<StatusResponse>() {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onResponse(Call<StatusResponse> call, Response<StatusResponse> response) {
|
public void onResponse(Call<StatusResponse> call, Response<StatusResponse> response) {
|
||||||
@ -77,4 +75,13 @@ public class AuthRepository {
|
|||||||
public void logout() {
|
public void logout() {
|
||||||
tokenManager.clearToken();
|
tokenManager.clearToken();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isLoggedIn() {
|
||||||
|
return !TextUtils.isEmpty(tokenManager.getToken());
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface LoginCallback {
|
||||||
|
void onSuccess(LoginResponse loginResponse);
|
||||||
|
void onError(String error);
|
||||||
|
}
|
||||||
}
|
}
|
@ -1,4 +1,4 @@
|
|||||||
package core.notevault.sync.auth.apimodel;
|
package com.stormtales.notevault.network.auth;
|
||||||
|
|
||||||
public class LoginRequest {
|
public class LoginRequest {
|
||||||
private String email;
|
private String email;
|
||||||
@ -8,6 +8,4 @@ public class LoginRequest {
|
|||||||
this.email = email;
|
this.email = email;
|
||||||
this.password = password;
|
this.password = password;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
@ -1,4 +1,4 @@
|
|||||||
package core.notevault.sync.auth.apimodel;
|
package com.stormtales.notevault.network.auth;
|
||||||
|
|
||||||
public class LoginResponse {
|
public class LoginResponse {
|
||||||
private String token;
|
private String token;
|
@ -1,4 +1,4 @@
|
|||||||
package core.notevault.sync.auth.apimodel;
|
package com.stormtales.notevault.network.auth;
|
||||||
|
|
||||||
public class RegisterRequest {
|
public class RegisterRequest {
|
||||||
private String username;
|
private String username;
|
@ -1,4 +1,4 @@
|
|||||||
package core.notevault.sync.auth;
|
package com.stormtales.notevault.network.auth;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
@ -0,0 +1,45 @@
|
|||||||
|
package com.stormtales.notevault.network.sync;
|
||||||
|
|
||||||
|
import com.stormtales.notevault.network.auth.LoginRequest;
|
||||||
|
import com.stormtales.notevault.network.auth.LoginResponse;
|
||||||
|
import com.stormtales.notevault.network.sync.models.*;
|
||||||
|
import okhttp3.MultipartBody;
|
||||||
|
import okhttp3.RequestBody;
|
||||||
|
import okhttp3.ResponseBody;
|
||||||
|
import retrofit2.Call;
|
||||||
|
import retrofit2.http.*;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
public interface SongSyncAPI {
|
||||||
|
|
||||||
|
@POST("/sync/songs/create")
|
||||||
|
Call<BatchCreateResponse> syncCreatedSongs(@Body SongCreateBatchRequest songCreateBatchRequest);
|
||||||
|
|
||||||
|
@POST("/sync/songs/modify")
|
||||||
|
Call<SongModifyBatchResponse> syncModifiedSongs(@Body SongModifyBatchRequest songModifyBatchRequest);
|
||||||
|
|
||||||
|
@POST("/sync/songs/delete")
|
||||||
|
Call<BatchModifyResponse> syncDeletedSongs(@Body SongBatchDeleteRequest songBatchDeleteRequest);
|
||||||
|
|
||||||
|
@Multipart
|
||||||
|
@POST("/sync/songs/note_sheet/upload")
|
||||||
|
Call<UploadResponse> uploadNoteSheet(@Part("serverID")RequestBody serverID, @Part("fileName") RequestBody fileName, @Part MultipartBody.Part image);
|
||||||
|
|
||||||
|
@Multipart
|
||||||
|
@POST("/sync/songs/note_sheet/update")
|
||||||
|
Call<UploadResponse> updateNoteSheet(@Part("server_filename") RequestBody server_filename, @Part MultipartBody.Part image);
|
||||||
|
|
||||||
|
@GET("/sync/songs/fetch")
|
||||||
|
Call<FetchResponse> fetchRemoteModifiedSongs(@Query(value = "last_client_sync") String last_client_sync);
|
||||||
|
|
||||||
|
@GET("/sync/songs/get/")
|
||||||
|
Call<SongModel> fetchRemotelyModifiedSongData(@Query(value = "songID") String songID);
|
||||||
|
|
||||||
|
@GET("/sync/songs/get/notesheet/")
|
||||||
|
Call<ResponseBody> downloadNotesheet(@Query("server_filename") String server_filename);
|
||||||
|
|
||||||
|
@Multipart
|
||||||
|
@POST("/ai/regognize/title")
|
||||||
|
Call<AIRecognizedSong> recognizeTitle(@Part MultipartBody.Part image);
|
||||||
|
}
|
@ -0,0 +1,154 @@
|
|||||||
|
package com.stormtales.notevault.network.sync;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.net.Uri;
|
||||||
|
import com.stormtales.notevault.data.entities.NoteSheet;
|
||||||
|
import com.stormtales.notevault.data.entities.Song;
|
||||||
|
import com.stormtales.notevault.data.repositories.SongRepository;
|
||||||
|
import com.stormtales.notevault.data.repositories.SongSyncRepository;
|
||||||
|
import com.stormtales.notevault.network.sync.models.*;
|
||||||
|
import com.stormtales.notevault.ui.gallery.GalleryViewModel;
|
||||||
|
import com.stormtales.notevault.utils.NoteSheetsUtil;
|
||||||
|
import com.stormtales.notevault.utils.Tupel;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
public class SongSyncModule {
|
||||||
|
private SongRepository songRepository;
|
||||||
|
private SongSyncRepository songSyncRepository;
|
||||||
|
private SongSyncService songSyncService;
|
||||||
|
private GalleryViewModel syncViewModel;
|
||||||
|
private Context context;
|
||||||
|
|
||||||
|
public SongSyncModule(Context context, GalleryViewModel syncViewModel) {
|
||||||
|
this.songRepository = new SongRepository(context);
|
||||||
|
this.songSyncRepository = new SongSyncRepository(context);
|
||||||
|
this.songSyncService = new SongSyncService(context);
|
||||||
|
this.syncViewModel = syncViewModel;
|
||||||
|
this.context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void syncCreatedSongs() {
|
||||||
|
songSyncRepository.loadCreatedSongs(result -> {
|
||||||
|
songSyncService.syncCreatedSongs(result, response -> {
|
||||||
|
songSyncRepository.markCreatedSongsAsSynced(response);
|
||||||
|
if(response.getCreateResponses().isEmpty()) {
|
||||||
|
syncViewModel.finishCreateSongSyncing();
|
||||||
|
} else {
|
||||||
|
uploadCreatedNoteSheets(result, response);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void uploadCreatedNoteSheets(List<Song> result, BatchCreateResponse response) {
|
||||||
|
List<Integer> songIDs = result.stream().map(Song::getLocalID).collect(Collectors.toList());
|
||||||
|
songSyncRepository.getNoteSheetFilesBySongIDs(noteSheets -> {
|
||||||
|
songSyncService.uploadNoteSheetsOfCreatedSongs(noteSheets, response, uploadResponses -> {
|
||||||
|
songSyncRepository.markCreatedNoteSheetsAsSynced(uploadResponses);
|
||||||
|
syncViewModel.finishCreateSongSyncing();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void syncModifiedSongs() {
|
||||||
|
songSyncRepository.loadModifiedSongs(result -> {
|
||||||
|
songSyncService.syncModifiedSongs(result, (FinishSongSyncingCallback<SongModifyBatchResponse>) response -> {
|
||||||
|
songSyncRepository.markModifiedSongsAsSynced(response);
|
||||||
|
uploadModifiedNoteSheets();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void uploadModifiedNoteSheets() {
|
||||||
|
songSyncRepository.loadModifiedNoteSheets(modifiedNoteSheets -> {
|
||||||
|
if(modifiedNoteSheets.isEmpty()) {
|
||||||
|
syncViewModel.finishModifiedSongSyncinc();
|
||||||
|
} else {
|
||||||
|
songSyncService.uploadModifiedNoteSheets(modifiedNoteSheets, new SongSyncService.UploadNoteSheetCallback() {
|
||||||
|
@Override
|
||||||
|
public void finishUploadNoteSheets(List<UploadResponse> uploadResponses) {
|
||||||
|
songSyncRepository.markModifiedNoteSheetsAsSynced(uploadResponses);
|
||||||
|
syncViewModel.finishModifiedSongSyncinc();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void syncDeletedSongs() {
|
||||||
|
songSyncRepository.loadDeletedSongs(result -> {
|
||||||
|
songSyncService.syncDeletedSong(result, (remoteDeletedSongs, localDeletedSongs) -> {
|
||||||
|
songSyncRepository.markDeletedSongsAsSynced(remoteDeletedSongs, localDeletedSongs);
|
||||||
|
syncViewModel.finishDeleteSongSyncinc();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void fetchRemoteModifiedSongs() {
|
||||||
|
//Todo: Determine Last Client Sync; for testing use LocalDateTime.MIN
|
||||||
|
songSyncService.fetchRemoteModifiedSongs(LocalDateTime.now().minusDays(35), new SongSyncRepository.LoadDataCallback<FetchResponse>() {
|
||||||
|
@Override
|
||||||
|
public void onResult(FetchResponse result) {
|
||||||
|
songSyncRepository.markSongsAsRemotelyModified(result.getServerIDs(), createdSongs -> {
|
||||||
|
getRemotelyModifiedSongData(createdSongs);
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void getRemotelyModifiedSongData(List<Song> freshlyCreatedSongs) {
|
||||||
|
songSyncRepository.loadRemotelyModifiedSongs(songs -> {
|
||||||
|
for(Song song : freshlyCreatedSongs) {
|
||||||
|
boolean freshlyCreatedSongFound = false;
|
||||||
|
for(Song s : songs) {
|
||||||
|
if(s.getServerID().equals(song.getServerID())) {
|
||||||
|
freshlyCreatedSongFound = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(!freshlyCreatedSongFound) {
|
||||||
|
songs.add(song);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
if(songs.isEmpty()) {
|
||||||
|
syncViewModel.finishFetching();
|
||||||
|
} else {
|
||||||
|
songSyncService.getRemotelyModifiedSongData(songs, remoteSongs -> {
|
||||||
|
if(remoteSongs.isEmpty()) {
|
||||||
|
syncViewModel.finishFetching();
|
||||||
|
} else {
|
||||||
|
songSyncRepository.saveRemoteSongs(remoteSongs, this::downloadNoteSheets);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void downloadNoteSheets(List<NoteSheet> noteSheets) {
|
||||||
|
for(NoteSheet noteSheet : noteSheets) {
|
||||||
|
songSyncService.downloadNoteSheet(noteSheet, responseBody -> {
|
||||||
|
String localFilename = noteSheet.getServerFileName().substring(36);
|
||||||
|
|
||||||
|
Tupel<String, String> result = NoteSheetsUtil.saveImageFromServer(responseBody, context.getFilesDir());
|
||||||
|
songSyncRepository.saveUpdatedNoteSheet(noteSheet.getServerFileName(), result.getValue00(), result.getValue01());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
syncViewModel.finishFetching();
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface FinishSongCreateSyncingCallback {
|
||||||
|
void finishSongSyncing(BatchCreateResponse response);
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface FinishSongSyncingCallback<T> {
|
||||||
|
void finishSongSyncing(T response);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,246 @@
|
|||||||
|
package com.stormtales.notevault.network.sync;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.util.Log;
|
||||||
|
import com.stormtales.notevault.data.entities.NoteSheet;
|
||||||
|
import com.stormtales.notevault.data.entities.Song;
|
||||||
|
import com.stormtales.notevault.data.repositories.SongSyncRepository;
|
||||||
|
import com.stormtales.notevault.network.NetworkModule;
|
||||||
|
import com.stormtales.notevault.network.auth.AuthAPI;
|
||||||
|
import com.stormtales.notevault.network.sync.models.*;
|
||||||
|
import com.stormtales.notevault.ui.gallery.GalleryViewModel;
|
||||||
|
import okhttp3.MediaType;
|
||||||
|
import okhttp3.MultipartBody;
|
||||||
|
import okhttp3.RequestBody;
|
||||||
|
import okhttp3.ResponseBody;
|
||||||
|
import retrofit2.Call;
|
||||||
|
import retrofit2.Callback;
|
||||||
|
import retrofit2.Response;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.time.format.DateTimeFormatter;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public class SongSyncService {
|
||||||
|
private final SongSyncAPI songSyncAPI;
|
||||||
|
private GalleryViewModel syncStatusViewModel;
|
||||||
|
|
||||||
|
public SongSyncService(Context context) {
|
||||||
|
songSyncAPI = NetworkModule.getRetrofitInstance(context).create(SongSyncAPI.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void syncCreatedSongs(List<Song> songs, SongSyncModule.FinishSongCreateSyncingCallback callback) {
|
||||||
|
List<SongCreateRequest> createRequests = new ArrayList<>();
|
||||||
|
for(Song song : songs) {
|
||||||
|
createRequests.add(new SongCreateRequest(song));
|
||||||
|
}
|
||||||
|
SongCreateBatchRequest songCreateBatchRequest = new SongCreateBatchRequest(createRequests);
|
||||||
|
songSyncAPI.syncCreatedSongs(songCreateBatchRequest).enqueue(new Callback<BatchCreateResponse>() {
|
||||||
|
@Override
|
||||||
|
public void onResponse(Call<BatchCreateResponse> call, Response<BatchCreateResponse> response) {
|
||||||
|
callback.finishSongSyncing(response.body());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(Call<BatchCreateResponse> call, Throwable throwable) {
|
||||||
|
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void uploadNoteSheetsOfCreatedSongs(List<NoteSheet> noteSheets, BatchCreateResponse batchCreateResponse, UploadNoteSheetCallback callback) {
|
||||||
|
List<UploadResponse> uploadResponses = new ArrayList<>();
|
||||||
|
for(CreateResponse createResponse : batchCreateResponse.getCreateResponses()) {
|
||||||
|
for(NoteSheet noteSheet : noteSheets) {
|
||||||
|
if(noteSheet.getSongID() == createResponse.getLocalID()) {
|
||||||
|
File imageFile = new File(noteSheet.getLocalFileName());
|
||||||
|
|
||||||
|
RequestBody serverID = RequestBody.create(MediaType.parse("text/plain"), createResponse.getServerID());
|
||||||
|
RequestBody fileName = RequestBody.create(MediaType.parse("text/plain"), imageFile.getName());
|
||||||
|
|
||||||
|
RequestBody requestFile = RequestBody.create(MediaType.parse("image/**"), new File(noteSheet.getLocalFileName()));
|
||||||
|
MultipartBody.Part image = MultipartBody.Part.createFormData("image", noteSheet.getLocalFileName(), requestFile);
|
||||||
|
|
||||||
|
songSyncAPI.uploadNoteSheet(serverID, fileName, image).enqueue(new Callback<UploadResponse>() {
|
||||||
|
@Override
|
||||||
|
public void onResponse(Call<UploadResponse> call, Response<UploadResponse> response) {
|
||||||
|
if(response.isSuccessful() && response.body() != null) {
|
||||||
|
uploadResponses.add(response.body());
|
||||||
|
if(uploadResponses.size() == noteSheets.size()) {
|
||||||
|
callback.finishUploadNoteSheets(uploadResponses);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(Call<UploadResponse> call, Throwable throwable) {
|
||||||
|
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void syncModifiedSongs(Map<Song, List<NoteSheet>> songs, SongSyncModule.FinishSongSyncingCallback<SongModifyBatchResponse> callback) {
|
||||||
|
List<SongModifyRequest> modifyRequests = new ArrayList<>();
|
||||||
|
for(Map.Entry<Song, List<NoteSheet>> entry: songs.entrySet()) {
|
||||||
|
modifyRequests.add(new SongModifyRequest(entry.getKey(), entry.getValue()));
|
||||||
|
}
|
||||||
|
SongModifyBatchRequest songModifyBatchRequest = new SongModifyBatchRequest(modifyRequests);
|
||||||
|
songSyncAPI.syncModifiedSongs(songModifyBatchRequest).enqueue(new Callback<SongModifyBatchResponse>() {
|
||||||
|
@Override
|
||||||
|
public void onResponse(Call<SongModifyBatchResponse> call, Response<SongModifyBatchResponse> response) {
|
||||||
|
callback.finishSongSyncing(response.body());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(Call<SongModifyBatchResponse> call, Throwable throwable) {
|
||||||
|
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void syncDeletedSong(List<Song> songs, SyncDeletedSongsCallback callback) {
|
||||||
|
List<String> deleteRequests = new ArrayList<>();
|
||||||
|
List<Integer> deletedNotSyncedSongs = new ArrayList<>();
|
||||||
|
for(Song song : songs) {
|
||||||
|
if(song.getServerID() == null) {
|
||||||
|
deletedNotSyncedSongs.add(song.getLocalID());
|
||||||
|
} else {
|
||||||
|
deleteRequests.add(song.getServerID());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SongBatchDeleteRequest songBatchDeleteRequest = new SongBatchDeleteRequest(deleteRequests);
|
||||||
|
songSyncAPI.syncDeletedSongs(songBatchDeleteRequest).enqueue(new Callback<BatchModifyResponse>() {
|
||||||
|
@Override
|
||||||
|
public void onResponse(Call<BatchModifyResponse> call, Response<BatchModifyResponse> response) {
|
||||||
|
if(response.isSuccessful() && response.body() != null) {
|
||||||
|
callback.finishSongSyncing(response.body().getModifiedServerObjects(), deletedNotSyncedSongs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(Call<BatchModifyResponse> call, Throwable throwable) {
|
||||||
|
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void uploadModifiedNoteSheets(List<NoteSheet> modifiedNoteSheets, UploadNoteSheetCallback callback) {
|
||||||
|
List<UploadResponse> uploadResponses = new ArrayList<>();
|
||||||
|
for(NoteSheet noteSheet : modifiedNoteSheets) {
|
||||||
|
RequestBody server_filename = RequestBody.create(MediaType.parse("text/plain"), noteSheet.getServerFileName());
|
||||||
|
|
||||||
|
RequestBody requestFile = RequestBody.create(MediaType.parse("image/**"), new File(noteSheet.getLocalFileName()));
|
||||||
|
MultipartBody.Part image = MultipartBody.Part.createFormData("image", noteSheet.getLocalFileName(), requestFile);
|
||||||
|
|
||||||
|
songSyncAPI.updateNoteSheet(server_filename, image).enqueue(new Callback<UploadResponse>() {
|
||||||
|
@Override
|
||||||
|
public void onResponse(Call<UploadResponse> call, Response<UploadResponse> response) {
|
||||||
|
if(response.isSuccessful() && response.body() != null) {
|
||||||
|
uploadResponses.add(response.body());
|
||||||
|
if(uploadResponses.size() == modifiedNoteSheets.size()) {
|
||||||
|
callback.finishUploadNoteSheets(uploadResponses);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Log.d("SongSyncService", "Something went wrong");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(Call<UploadResponse> call, Throwable throwable) {
|
||||||
|
Log.e("SongSyncService", "Upload failed: " + throwable.getMessage(), throwable);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void fetchRemoteModifiedSongs(LocalDateTime lastClientSync, SongSyncRepository.LoadDataCallback<FetchResponse> callback) {
|
||||||
|
String formattedTime = lastClientSync.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME);
|
||||||
|
songSyncAPI.fetchRemoteModifiedSongs(formattedTime).enqueue(new Callback<FetchResponse>() {
|
||||||
|
@Override
|
||||||
|
public void onResponse(Call<FetchResponse> call, Response<FetchResponse> response) {
|
||||||
|
if(response.isSuccessful() && response.body() != null) {
|
||||||
|
callback.onResult(response.body());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(Call<FetchResponse> call, Throwable throwable) {
|
||||||
|
Log.d("SongSyncService", "Fetch failed: " + throwable.getMessage(), throwable);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void getRemotelyModifiedSongData(List<Song> remotelyModifiedSongs, SongSyncRepository.LoadDataCallback<List<SongModel>> callback) {
|
||||||
|
List<SongModel> songModels = new ArrayList<>();
|
||||||
|
for(Song song : remotelyModifiedSongs) {
|
||||||
|
songSyncAPI.fetchRemotelyModifiedSongData(song.getServerID()).enqueue(new Callback<SongModel>() {
|
||||||
|
@Override
|
||||||
|
public void onResponse(Call<SongModel> call, Response<SongModel> response) {
|
||||||
|
if(response.isSuccessful() && response.body() != null) {
|
||||||
|
songModels.add(response.body());
|
||||||
|
if(songModels.size() == remotelyModifiedSongs.size()) {
|
||||||
|
callback.onResult(songModels);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(Call<SongModel> call, Throwable throwable) {
|
||||||
|
Log.d("SongSyncService", "Fetch failed: " + throwable.getMessage(), throwable);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void downloadNoteSheet(NoteSheet noteSheet, SongSyncRepository.LoadDataCallback<ResponseBody> callback) {
|
||||||
|
songSyncAPI.downloadNotesheet(noteSheet.getServerFileName()).enqueue(new Callback<ResponseBody>() {
|
||||||
|
@Override
|
||||||
|
public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
|
||||||
|
if(response.isSuccessful() && response.body() != null) {
|
||||||
|
callback.onResult(response.body());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(Call<ResponseBody> call, Throwable throwable) {
|
||||||
|
Log.d("SongSyncService", "Download failed: " + throwable.getMessage(), throwable);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void recognizeTitle(String firstNoteSheet, RecognizedSongCallback callback) {
|
||||||
|
RequestBody requestFile = RequestBody.create(MediaType.parse("image/jpeg"), new File(firstNoteSheet));
|
||||||
|
MultipartBody.Part image = MultipartBody.Part.createFormData("image", firstNoteSheet, requestFile);
|
||||||
|
songSyncAPI.recognizeTitle(image).enqueue(new Callback<AIRecognizedSong>() {
|
||||||
|
@Override
|
||||||
|
public void onResponse(Call<AIRecognizedSong> call, Response<AIRecognizedSong> response) {
|
||||||
|
callback.onRecognizedSong(response.body());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(Call<AIRecognizedSong> call, Throwable throwable) {
|
||||||
|
Log.d("SongSyncService", "Recognition failed: " + throwable.getMessage(), throwable);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public interface SyncDeletedSongsCallback {
|
||||||
|
void finishSongSyncing(List<String> remoteDeletedSongs, List<Integer> localDeletedSongs);
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface UploadNoteSheetCallback {
|
||||||
|
void finishUploadNoteSheets(List<UploadResponse> uploadResponses);
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface RecognizedSongCallback {
|
||||||
|
void onRecognizedSong(AIRecognizedSong aiRecognizedSong);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,40 @@
|
|||||||
|
package com.stormtales.notevault.network.sync.models;
|
||||||
|
|
||||||
|
public class AIRecognizedSong {
|
||||||
|
private String title;
|
||||||
|
private int year;
|
||||||
|
private String composer;
|
||||||
|
private String description;
|
||||||
|
|
||||||
|
public String getTitle() {
|
||||||
|
return title;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTitle(String title) {
|
||||||
|
this.title = title;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getYear() {
|
||||||
|
return year;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setYear(int year) {
|
||||||
|
this.year = year;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getComposer() {
|
||||||
|
return composer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setComposer(String composer) {
|
||||||
|
this.composer = composer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDescription() {
|
||||||
|
return description;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDescription(String description) {
|
||||||
|
this.description = description;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,15 @@
|
|||||||
|
package com.stormtales.notevault.network.sync.models;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class BatchCreateResponse {
|
||||||
|
private List<CreateResponse> createResponses;
|
||||||
|
|
||||||
|
public List<CreateResponse> getCreateResponses() {
|
||||||
|
return createResponses;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCreateResponses(List<CreateResponse> createResponses) {
|
||||||
|
this.createResponses = createResponses;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,15 @@
|
|||||||
|
package com.stormtales.notevault.network.sync.models;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class BatchModifyResponse {
|
||||||
|
private List<String> modifiedServerObjects;
|
||||||
|
|
||||||
|
public List<String> getModifiedServerObjects() {
|
||||||
|
return modifiedServerObjects;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setModifiedServerObjects(List<String> modifiedServerObjects) {
|
||||||
|
this.modifiedServerObjects = modifiedServerObjects;
|
||||||
|
}
|
||||||
|
}
|
@ -1,14 +1,9 @@
|
|||||||
package core.notevault.sync.synchronisation.songs.creation;
|
package com.stormtales.notevault.network.sync.models;
|
||||||
|
|
||||||
public class SongCreationResponse {
|
public class CreateResponse {
|
||||||
private int localID;
|
private int localID;
|
||||||
private String serverID;
|
private String serverID;
|
||||||
|
|
||||||
public SongCreationResponse(int localID, String serverID) {
|
|
||||||
this.localID = localID;
|
|
||||||
this.serverID = serverID;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getLocalID() {
|
public int getLocalID() {
|
||||||
return localID;
|
return localID;
|
||||||
}
|
}
|
@ -1,14 +1,10 @@
|
|||||||
package core.notevault.sync.synchronisation;
|
package com.stormtales.notevault.network.sync.models;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public class FetchResponse {
|
public class FetchResponse {
|
||||||
private List<String> serverIDs;
|
private List<String> serverIDs;
|
||||||
|
|
||||||
public FetchResponse(List<String> serverIDs) {
|
|
||||||
this.serverIDs = serverIDs;
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<String> getServerIDs() {
|
public List<String> getServerIDs() {
|
||||||
return serverIDs;
|
return serverIDs;
|
||||||
}
|
}
|
@ -0,0 +1,27 @@
|
|||||||
|
package com.stormtales.notevault.network.sync.models;
|
||||||
|
|
||||||
|
public class NoteSheetModifyRequest {
|
||||||
|
private String serverFileName;
|
||||||
|
private String clientHash;
|
||||||
|
|
||||||
|
public NoteSheetModifyRequest(String serverFileName, String clientHash) {
|
||||||
|
this.serverFileName = serverFileName;
|
||||||
|
this.clientHash = clientHash;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getServerFileName() {
|
||||||
|
return serverFileName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setServerFileName(String serverFileName) {
|
||||||
|
this.serverFileName = serverFileName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getClientHash() {
|
||||||
|
return clientHash;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setClientHash(String clientHash) {
|
||||||
|
this.clientHash = clientHash;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,31 @@
|
|||||||
|
package com.stormtales.notevault.network.sync.models;
|
||||||
|
|
||||||
|
public class RemotelyModifiedNoteSheetModel {
|
||||||
|
private String filename;
|
||||||
|
private String server_filename;
|
||||||
|
private String hash;
|
||||||
|
|
||||||
|
public String getFilename() {
|
||||||
|
return filename;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFilename(String filename) {
|
||||||
|
this.filename = filename;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getServer_filename() {
|
||||||
|
return server_filename;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setServer_filename(String server_filename) {
|
||||||
|
this.server_filename = server_filename;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getHash() {
|
||||||
|
return hash;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setHash(String hash) {
|
||||||
|
this.hash = hash;
|
||||||
|
}
|
||||||
|
}
|
@ -1,11 +1,11 @@
|
|||||||
package core.notevault.sync.synchronisation.songs.deletion;
|
package com.stormtales.notevault.network.sync.models;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public class SongDeletionBatchRequest {
|
public class SongBatchDeleteRequest {
|
||||||
private List<String> songs;
|
private List<String> songs;
|
||||||
|
|
||||||
public SongDeletionBatchRequest(List<String> songs) {
|
public SongBatchDeleteRequest(List<String> songs) {
|
||||||
this.songs = songs;
|
this.songs = songs;
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,20 @@
|
|||||||
|
package com.stormtales.notevault.network.sync.models;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class SongCreateBatchRequest {
|
||||||
|
|
||||||
|
private List<SongCreateRequest> songs;
|
||||||
|
|
||||||
|
public SongCreateBatchRequest(List<SongCreateRequest> songs) {
|
||||||
|
this.songs = songs;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<SongCreateRequest> getSongs() {
|
||||||
|
return songs;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSongs(List<SongCreateRequest> songs) {
|
||||||
|
this.songs = songs;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,67 @@
|
|||||||
|
package com.stormtales.notevault.network.sync.models;
|
||||||
|
|
||||||
|
import com.stormtales.notevault.data.entities.Song;
|
||||||
|
|
||||||
|
public class SongCreateRequest {
|
||||||
|
private int localID;
|
||||||
|
private String title;
|
||||||
|
private String composer;
|
||||||
|
private String genre;
|
||||||
|
private int year;
|
||||||
|
|
||||||
|
public SongCreateRequest(int localID, String title, String composer, String genre, int year) {
|
||||||
|
this.localID = localID;
|
||||||
|
this.title = title;
|
||||||
|
this.composer = composer;
|
||||||
|
this.genre = genre;
|
||||||
|
this.year = year;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SongCreateRequest(Song song) {
|
||||||
|
this.localID = song.getLocalID();
|
||||||
|
this.title = song.getTitle();
|
||||||
|
this.composer = song.getComposer();
|
||||||
|
this.genre = song.getGenre();
|
||||||
|
this.year = song.getYear();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getLocalID() {
|
||||||
|
return localID;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLocalID(int localID) {
|
||||||
|
this.localID = localID;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
@ -1,19 +1,15 @@
|
|||||||
package core.notevault.sync.synchronisation.songs.modification;
|
package com.stormtales.notevault.network.sync.models;
|
||||||
|
|
||||||
public class SongModificationRequest {
|
import java.util.List;
|
||||||
|
|
||||||
|
public class SongModel {
|
||||||
private String serverID;
|
private String serverID;
|
||||||
private String title;
|
private String title;
|
||||||
private String composer;
|
private String composer;
|
||||||
private String genre;
|
private String genre;
|
||||||
private int year;
|
private int year;
|
||||||
|
private List<RemotelyModifiedNoteSheetModel> note_sheets;
|
||||||
public SongModificationRequest(String serverID, String title, String composer, String genre, int year) {
|
private boolean deleted;
|
||||||
this.serverID = serverID;
|
|
||||||
this.title = title;
|
|
||||||
this.composer = composer;
|
|
||||||
this.genre = genre;
|
|
||||||
this.year = year;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getServerID() {
|
public String getServerID() {
|
||||||
return serverID;
|
return serverID;
|
||||||
@ -54,4 +50,20 @@ public class SongModificationRequest {
|
|||||||
public void setYear(int year) {
|
public void setYear(int year) {
|
||||||
this.year = year;
|
this.year = year;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<RemotelyModifiedNoteSheetModel> getNote_sheets() {
|
||||||
|
return note_sheets;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setNote_sheets(List<RemotelyModifiedNoteSheetModel> note_sheets) {
|
||||||
|
this.note_sheets = note_sheets;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isDeleted() {
|
||||||
|
return deleted;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDeleted(boolean deleted) {
|
||||||
|
this.deleted = deleted;
|
||||||
|
}
|
||||||
}
|
}
|
@ -0,0 +1,19 @@
|
|||||||
|
package com.stormtales.notevault.network.sync.models;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class SongModifyBatchRequest {
|
||||||
|
private List<SongModifyRequest> songs;
|
||||||
|
|
||||||
|
public SongModifyBatchRequest(List<SongModifyRequest> songs) {
|
||||||
|
this.songs = songs;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<SongModifyRequest> getSongs() {
|
||||||
|
return songs;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSongs(List<SongModifyRequest> songs) {
|
||||||
|
this.songs = songs;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,16 @@
|
|||||||
|
package com.stormtales.notevault.network.sync.models;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class SongModifyBatchResponse {
|
||||||
|
|
||||||
|
private List<SongModifyResponse> modifiedServerObjects;
|
||||||
|
|
||||||
|
public List<SongModifyResponse> getModifiedServerObjects() {
|
||||||
|
return modifiedServerObjects;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setModifiedServerObjects(List<SongModifyResponse> modifiedServerObjects) {
|
||||||
|
this.modifiedServerObjects = modifiedServerObjects;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,82 @@
|
|||||||
|
package com.stormtales.notevault.network.sync.models;
|
||||||
|
|
||||||
|
import com.stormtales.notevault.data.entities.NoteSheet;
|
||||||
|
import com.stormtales.notevault.data.entities.Song;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
public class SongModifyRequest {
|
||||||
|
|
||||||
|
private String serverID;
|
||||||
|
private String title;
|
||||||
|
private String composer;
|
||||||
|
private String genre;
|
||||||
|
private int year;
|
||||||
|
private List<NoteSheetModifyRequest> noteSheets;
|
||||||
|
|
||||||
|
public SongModifyRequest(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 SongModifyRequest(Song song, List<NoteSheet> noteSheets) {
|
||||||
|
this.serverID = song.getServerID();
|
||||||
|
this.title = song.getTitle();
|
||||||
|
this.composer = song.getComposer();
|
||||||
|
this.genre = song.getGenre();
|
||||||
|
this.year = song.getYear();
|
||||||
|
this.noteSheets = noteSheets.stream().map(noteSheet -> new NoteSheetModifyRequest(noteSheet.getServerFileName(), noteSheet.getHash())).collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<NoteSheetModifyRequest> getNoteSheets() {
|
||||||
|
return noteSheets;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setNoteSheets(List<NoteSheetModifyRequest> noteSheets) {
|
||||||
|
this.noteSheets = noteSheets;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,25 @@
|
|||||||
|
package com.stormtales.notevault.network.sync.models;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class SongModifyResponse {
|
||||||
|
|
||||||
|
private String serverID;
|
||||||
|
private List<String> outdated_note_sheets;
|
||||||
|
|
||||||
|
public List<String> getOutdated_note_sheets() {
|
||||||
|
return outdated_note_sheets;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOutdated_note_sheets(List<String> outdated_note_sheets) {
|
||||||
|
this.outdated_note_sheets = outdated_note_sheets;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getServerID() {
|
||||||
|
return serverID;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setServerID(String serverID) {
|
||||||
|
this.serverID = serverID;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,31 @@
|
|||||||
|
package com.stormtales.notevault.network.sync.models;
|
||||||
|
|
||||||
|
public class UploadResponse {
|
||||||
|
private String serverID;
|
||||||
|
private String localFile;
|
||||||
|
private String serverFile;
|
||||||
|
|
||||||
|
public String getServerID() {
|
||||||
|
return serverID;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setServerID(String serverID) {
|
||||||
|
this.serverID = serverID;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getLocalFile() {
|
||||||
|
return localFile;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLocalFile(String localFile) {
|
||||||
|
this.localFile = localFile;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getServerFile() {
|
||||||
|
return serverFile;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setServerFile(String serverFile) {
|
||||||
|
this.serverFile = serverFile;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,124 @@
|
|||||||
|
package com.stormtales.notevault.ui.gallery;
|
||||||
|
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.Button;
|
||||||
|
import android.widget.ProgressBar;
|
||||||
|
import android.widget.TextView;
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.fragment.app.Fragment;
|
||||||
|
import androidx.lifecycle.ViewModelProvider;
|
||||||
|
import com.stormtales.notevault.data.repositories.SongRepository;
|
||||||
|
import com.stormtales.notevault.databinding.FragmentGalleryBinding;
|
||||||
|
import com.stormtales.notevault.network.sync.SongSyncModule;
|
||||||
|
import com.stormtales.notevault.network.sync.SongSyncService;
|
||||||
|
|
||||||
|
|
||||||
|
public class GalleryFragment extends Fragment {
|
||||||
|
|
||||||
|
GalleryViewModel galleryViewModel;
|
||||||
|
private FragmentGalleryBinding binding;
|
||||||
|
private ProgressBar progress_sync_created_songs;
|
||||||
|
private Button sync_created_songs_btn;
|
||||||
|
private ProgressBar progress_sync_modified_songs;
|
||||||
|
private Button sync_modified_songs_btn;
|
||||||
|
private ProgressBar progress_sync_deleted_songs;
|
||||||
|
private Button sync_deleted_songs_btn;
|
||||||
|
|
||||||
|
private ProgressBar progress_fetching_songs;
|
||||||
|
private Button sync_fetching_songs_btn;
|
||||||
|
|
||||||
|
|
||||||
|
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();
|
||||||
|
|
||||||
|
progress_sync_created_songs = binding.progressSyncCreatedSongs;
|
||||||
|
sync_created_songs_btn = binding.syncCreatedBtn;
|
||||||
|
sync_created_songs_btn.setOnClickListener(v -> onSyncCreatedSongs());
|
||||||
|
|
||||||
|
progress_sync_modified_songs = binding.progressSyncModifiedSongs;
|
||||||
|
sync_modified_songs_btn = binding.syncModifiedBtn;
|
||||||
|
sync_modified_songs_btn.setOnClickListener(v -> onSyncModifiedSogs());
|
||||||
|
|
||||||
|
progress_sync_deleted_songs = binding.progressSyncDeletedSongs;
|
||||||
|
sync_deleted_songs_btn = binding.syncDeletedBtn;
|
||||||
|
sync_deleted_songs_btn.setOnClickListener(v -> onSyncDeletedSongs());
|
||||||
|
|
||||||
|
progress_fetching_songs = binding.progressFetchSongs;
|
||||||
|
sync_fetching_songs_btn = binding.fetchSongsBtn;
|
||||||
|
sync_fetching_songs_btn.setOnClickListener(v -> onFetchRemoteModifiedSongs());
|
||||||
|
|
||||||
|
galleryViewModel.setSongSyncModule(new SongSyncModule(getContext(), galleryViewModel));
|
||||||
|
|
||||||
|
galleryViewModel.getIsCreatedSongSyncing().observe(getViewLifecycleOwner(), isCreatedSyncinc -> {
|
||||||
|
if(isCreatedSyncinc) {
|
||||||
|
progress_sync_created_songs.setIndeterminate(true);
|
||||||
|
sync_created_songs_btn.setEnabled(false);
|
||||||
|
} else {
|
||||||
|
progress_sync_created_songs.setIndeterminate(false);
|
||||||
|
sync_created_songs_btn.setEnabled(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
galleryViewModel.getIsModifiedSongSyncing().observe(getViewLifecycleOwner(), isModifiedSyncinc -> {
|
||||||
|
if(isModifiedSyncinc) {
|
||||||
|
progress_sync_modified_songs.setIndeterminate(true);
|
||||||
|
sync_modified_songs_btn.setEnabled(false);
|
||||||
|
} else {
|
||||||
|
progress_sync_modified_songs.setIndeterminate(false);
|
||||||
|
sync_modified_songs_btn.setEnabled(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
galleryViewModel.getIsDeletedSongSyncing().observe(getViewLifecycleOwner(), isDeletedSyncinc -> {
|
||||||
|
if(isDeletedSyncinc) {
|
||||||
|
progress_sync_deleted_songs.setIndeterminate(true);
|
||||||
|
sync_deleted_songs_btn.setEnabled(false);
|
||||||
|
} else {
|
||||||
|
progress_sync_deleted_songs.setIndeterminate(false);
|
||||||
|
sync_deleted_songs_btn.setEnabled(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
galleryViewModel.getIsFetchingActive().observe(getViewLifecycleOwner(), isFetchingActiveSyncinc -> {
|
||||||
|
if(isFetchingActiveSyncinc) {
|
||||||
|
progress_fetching_songs.setIndeterminate(true);
|
||||||
|
sync_fetching_songs_btn.setEnabled(false);
|
||||||
|
} else {
|
||||||
|
progress_fetching_songs.setIndeterminate(false);
|
||||||
|
sync_fetching_songs_btn.setEnabled(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onFetchRemoteModifiedSongs() {
|
||||||
|
galleryViewModel.startFetchingActive();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onSyncDeletedSongs() {
|
||||||
|
galleryViewModel.startDeletedSongSyncing();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDestroyView() {
|
||||||
|
super.onDestroyView();
|
||||||
|
binding = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onSyncCreatedSongs() {
|
||||||
|
galleryViewModel.startCreateSongSyncing();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void onSyncModifiedSogs() {
|
||||||
|
galleryViewModel.startModifiedSongSyncinc();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,92 @@
|
|||||||
|
package com.stormtales.notevault.ui.gallery;
|
||||||
|
|
||||||
|
import androidx.lifecycle.LiveData;
|
||||||
|
import androidx.lifecycle.MutableLiveData;
|
||||||
|
import androidx.lifecycle.ViewModel;
|
||||||
|
import com.stormtales.notevault.data.repositories.SongRepository;
|
||||||
|
import com.stormtales.notevault.network.sync.SongSyncModule;
|
||||||
|
|
||||||
|
public class GalleryViewModel extends ViewModel {
|
||||||
|
|
||||||
|
private final MutableLiveData<Boolean> isCreatedSongSyncing;
|
||||||
|
private final MutableLiveData<Boolean> isModifiedSongSyncing;
|
||||||
|
private final MutableLiveData<Boolean> isDeletedSongSyncing;
|
||||||
|
private final MutableLiveData<Boolean> isFetchingActive;
|
||||||
|
private SongSyncModule songSyncModule;
|
||||||
|
|
||||||
|
public GalleryViewModel() {
|
||||||
|
|
||||||
|
isCreatedSongSyncing = new MutableLiveData<>();
|
||||||
|
isCreatedSongSyncing.setValue(false);
|
||||||
|
|
||||||
|
isModifiedSongSyncing = new MutableLiveData<>();
|
||||||
|
isModifiedSongSyncing.setValue(false);
|
||||||
|
|
||||||
|
isDeletedSongSyncing = new MutableLiveData<>();
|
||||||
|
isDeletedSongSyncing.setValue(false);
|
||||||
|
|
||||||
|
isFetchingActive = new MutableLiveData<>();
|
||||||
|
isFetchingActive.setValue(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public MutableLiveData<Boolean> getIsCreatedSongSyncing() {
|
||||||
|
return isCreatedSongSyncing;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void startCreateSongSyncing() {
|
||||||
|
if(this.songSyncModule == null) throw new RuntimeException("SongSyncModule is not initialized");
|
||||||
|
this.isCreatedSongSyncing.setValue(true);
|
||||||
|
|
||||||
|
this.songSyncModule.syncCreatedSongs();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void finishCreateSongSyncing() {
|
||||||
|
this.isCreatedSongSyncing.setValue(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSongSyncModule(SongSyncModule songSyncModule) {
|
||||||
|
this.songSyncModule = songSyncModule;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void startModifiedSongSyncinc() {
|
||||||
|
if(this.songSyncModule == null) throw new RuntimeException("SongSyncModule is not initialized");
|
||||||
|
this.isModifiedSongSyncing.setValue(true);
|
||||||
|
this.songSyncModule.syncModifiedSongs();
|
||||||
|
}
|
||||||
|
|
||||||
|
public MutableLiveData<Boolean> getIsModifiedSongSyncing() {
|
||||||
|
return isModifiedSongSyncing;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void finishModifiedSongSyncinc() {
|
||||||
|
this.isModifiedSongSyncing.setValue(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public MutableLiveData<Boolean> getIsDeletedSongSyncing() {
|
||||||
|
return isDeletedSongSyncing;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void startDeletedSongSyncing() {
|
||||||
|
if(this.songSyncModule == null) throw new RuntimeException("SongSyncModule is not initialized");
|
||||||
|
this.isDeletedSongSyncing.setValue(true);
|
||||||
|
this.songSyncModule.syncDeletedSongs();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void finishDeleteSongSyncinc() {
|
||||||
|
this.isDeletedSongSyncing.setValue(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void startFetchingActive() {
|
||||||
|
if(this.songSyncModule == null) throw new RuntimeException("SongSyncModule is not initialized");
|
||||||
|
this.isFetchingActive.setValue(true);
|
||||||
|
this.songSyncModule.fetchRemoteModifiedSongs();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void finishFetching() {
|
||||||
|
this.isFetchingActive.setValue(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public MutableLiveData<Boolean> getIsFetchingActive() {
|
||||||
|
return isFetchingActive;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,138 @@
|
|||||||
|
package com.stormtales.notevault.ui.home;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.TextView;
|
||||||
|
import android.widget.Toast;
|
||||||
|
import androidx.activity.result.ActivityResult;
|
||||||
|
import androidx.activity.result.ActivityResultCallback;
|
||||||
|
import androidx.activity.result.ActivityResultLauncher;
|
||||||
|
import androidx.activity.result.contract.ActivityResultContracts;
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
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 com.stormtales.notevault.R;
|
||||||
|
import com.stormtales.notevault.data.entities.Song;
|
||||||
|
import com.stormtales.notevault.data.repositories.SongRepository;
|
||||||
|
import com.stormtales.notevault.databinding.FragmentHomeBinding;
|
||||||
|
import com.stormtales.notevault.ui.sheetdisplay.NoteSheetDisplayActivity;
|
||||||
|
import com.stormtales.notevault.ui.songeditor.SongEditorDialog;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class HomeFragment extends Fragment {
|
||||||
|
|
||||||
|
private FragmentHomeBinding binding;
|
||||||
|
private RecyclerView recyclerView;
|
||||||
|
private SongAdapter songAdapter;
|
||||||
|
private HomeViewModel homeViewModel;
|
||||||
|
private static final int PICK_FILE_REQUEST_CODE = 1;
|
||||||
|
|
||||||
|
public View onCreateView(@NonNull LayoutInflater inflater,
|
||||||
|
ViewGroup container, Bundle savedInstanceState) {
|
||||||
|
homeViewModel = new ViewModelProvider(this).get(HomeViewModel.class);
|
||||||
|
homeViewModel.setSongRepository(new SongRepository(this.getContext()));
|
||||||
|
|
||||||
|
binding = FragmentHomeBinding.inflate(inflater, container, false);
|
||||||
|
View root = binding.getRoot();
|
||||||
|
recyclerView = root.findViewById(R.id.recycler_view_songs);
|
||||||
|
|
||||||
|
ActivityResultLauncher<Intent> launcher = registerForActivityResult(
|
||||||
|
new ActivityResultContracts.StartActivityForResult(),
|
||||||
|
activityResult -> {
|
||||||
|
getActivity();
|
||||||
|
if(activityResult.getResultCode() == Activity.RESULT_OK && activityResult.getData() != null) {
|
||||||
|
if(activityResult.getData().getClipData() != null) {
|
||||||
|
int fileNumber = activityResult.getData().getClipData().getItemCount();
|
||||||
|
Uri[] files = new Uri[fileNumber];
|
||||||
|
for(int i = 0; i < fileNumber; i++) {
|
||||||
|
files[i] = activityResult.getData().getClipData().getItemAt(i).getUri();
|
||||||
|
Toast.makeText(requireContext(), "Uri: " + files[i].toString(), Toast.LENGTH_SHORT).show();
|
||||||
|
}
|
||||||
|
handleSelectedNoteSheets(files);
|
||||||
|
} else {
|
||||||
|
Uri uri = activityResult.getData().getData();
|
||||||
|
Toast.makeText(requireContext(), "Uri: " + uri.toString(), Toast.LENGTH_SHORT).show();
|
||||||
|
handleSelectedNoteSheets(uri);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
FloatingActionButton importBtn = root.findViewById(R.id.addSongBtn);
|
||||||
|
importBtn.setOnClickListener(v -> {
|
||||||
|
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);
|
||||||
|
|
||||||
|
|
||||||
|
launcher.launch(intent);
|
||||||
|
});
|
||||||
|
|
||||||
|
// RecyclerView einrichten
|
||||||
|
songAdapter = new SongAdapter(new ArrayList<>(), this::onEditSong, this::onDeleteSong, this::onOpenSong);
|
||||||
|
recyclerView.setAdapter(songAdapter);
|
||||||
|
recyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
|
||||||
|
DividerItemDecoration dividerItemDecoration = new DividerItemDecoration(recyclerView.getContext(), LinearLayoutManager.VERTICAL);
|
||||||
|
recyclerView.addItemDecoration(dividerItemDecoration);
|
||||||
|
|
||||||
|
// Beobachte Änderungen in der Song-Liste
|
||||||
|
homeViewModel.getAllSongsLive().observe(getViewLifecycleOwner(), songs -> {
|
||||||
|
songAdapter.updateData(songs);
|
||||||
|
});
|
||||||
|
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDestroyView() {
|
||||||
|
super.onDestroyView();
|
||||||
|
binding = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleSelectedNoteSheets(Uri... files) {
|
||||||
|
SongEditorDialog songEditorDialog = new SongEditorDialog();
|
||||||
|
songEditorDialog.setNoteSheetFiles(files);
|
||||||
|
songEditorDialog.setHomeViewModel(homeViewModel);
|
||||||
|
songEditorDialog.show(getParentFragmentManager(), "songEditorDialog");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onEditSong(Song song) {
|
||||||
|
SongEditorDialog songEditorDialog = new SongEditorDialog();
|
||||||
|
songEditorDialog.setEditedSong(song);
|
||||||
|
songEditorDialog.setHomeViewModel(homeViewModel);
|
||||||
|
songEditorDialog.show(getParentFragmentManager(), "songEditorDialog");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onDeleteSong(Song song) {
|
||||||
|
this.homeViewModel.deleteSong(song);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onOpenSong(Song song) {
|
||||||
|
homeViewModel.getSongRepository().getNoteSheetFilesBySongID(song.getLocalID(), result -> {
|
||||||
|
String[] noteSheetFiles = new String[result.size()];
|
||||||
|
for(int i = 0; i < result.size(); i++) {
|
||||||
|
noteSheetFiles[i] = result.get(i);
|
||||||
|
}
|
||||||
|
Intent intent = new Intent(getContext(), NoteSheetDisplayActivity.class);
|
||||||
|
intent.putExtra("imageUris", noteSheetFiles);
|
||||||
|
getContext().startActivity(intent);
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,88 @@
|
|||||||
|
package com.stormtales.notevault.ui.home;
|
||||||
|
|
||||||
|
import android.util.Log;
|
||||||
|
import androidx.lifecycle.LiveData;
|
||||||
|
import androidx.lifecycle.MutableLiveData;
|
||||||
|
import androidx.lifecycle.ViewModel;
|
||||||
|
import com.stormtales.notevault.data.entities.NoteSheet;
|
||||||
|
import com.stormtales.notevault.data.entities.Song;
|
||||||
|
import com.stormtales.notevault.data.repositories.SongRepository;
|
||||||
|
import com.stormtales.notevault.data.sync.SyncStatus;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class HomeViewModel extends ViewModel {
|
||||||
|
|
||||||
|
private MutableLiveData<List<Song>> allSongs;
|
||||||
|
private SongRepository songRepository;
|
||||||
|
|
||||||
|
public HomeViewModel() {
|
||||||
|
List<Song> currentSongs = new ArrayList<>();
|
||||||
|
this.allSongs = new MutableLiveData<>(currentSongs);
|
||||||
|
/*this.allSongs.setValue(songRepository.getAllSongs());*/
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addSong(Song song, List<NoteSheet> noteSheetList) {
|
||||||
|
songRepository.insert(song, internalSongID -> {
|
||||||
|
for(NoteSheet noteSheet : noteSheetList) {
|
||||||
|
noteSheet.setSongID(Math.toIntExact(internalSongID));
|
||||||
|
}
|
||||||
|
songRepository.insertNoteSheets(noteSheetList);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
List<Song> currentSongs = allSongs.getValue();
|
||||||
|
if(currentSongs == null) {
|
||||||
|
currentSongs = new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Neue Liste erstellen und den Song hinzufügen
|
||||||
|
List<Song> updatedSongs = new ArrayList<>(currentSongs);
|
||||||
|
updatedSongs.add(song);
|
||||||
|
// Neue Liste in MutableLiveData setzen
|
||||||
|
allSongs.setValue(updatedSongs);
|
||||||
|
Log.d("HomeViewModel", "Song added. Total songs: " + updatedSongs.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
public LiveData<List<Song>> getAllSongsLive() {
|
||||||
|
return allSongs;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSongRepository(SongRepository songRepository) {
|
||||||
|
this.songRepository = songRepository;
|
||||||
|
loadAllSongs();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void loadAllSongs() {
|
||||||
|
songRepository.getAllSongs(songs->allSongs.setValue(songs));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void deleteSong(Song song) {
|
||||||
|
songRepository.deleteSong(song);
|
||||||
|
|
||||||
|
List<Song> currentSongs = allSongs.getValue();
|
||||||
|
if(currentSongs != null) {
|
||||||
|
currentSongs.remove(song);
|
||||||
|
allSongs.setValue(currentSongs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void updateSong(Song editedSong) {
|
||||||
|
List<Song> currentSongs = allSongs.getValue();
|
||||||
|
if(currentSongs != null) {
|
||||||
|
for(int i = 0; i < currentSongs.size(); i++) {
|
||||||
|
if(currentSongs.get(i).getLocalID() == editedSong.getLocalID()) {
|
||||||
|
currentSongs.set(i, editedSong);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
songRepository.updateSong(editedSong);
|
||||||
|
allSongs.setValue(currentSongs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public SongRepository getSongRepository() {
|
||||||
|
return songRepository;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,99 @@
|
|||||||
|
package com.stormtales.notevault.ui.home;
|
||||||
|
|
||||||
|
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 com.stormtales.notevault.R;
|
||||||
|
import com.stormtales.notevault.data.entities.Song;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class SongAdapter extends RecyclerView.Adapter<SongAdapter.SongViewHolder> {
|
||||||
|
|
||||||
|
private List<Song> songList;
|
||||||
|
private OnEditSongListener onEditSongListener;
|
||||||
|
private OnDeleteSongListener onDeleteSongListener;
|
||||||
|
private OnOpenSongListener onOpenSongListener;
|
||||||
|
|
||||||
|
public SongAdapter(List<Song> songList, OnEditSongListener onEditSongListener, OnDeleteSongListener onDeleteSongListener, OnOpenSongListener onOpenSongListener) {
|
||||||
|
this.songList = songList;
|
||||||
|
this.onEditSongListener = onEditSongListener;
|
||||||
|
this.onDeleteSongListener = onDeleteSongListener;
|
||||||
|
this.onOpenSongListener = onOpenSongListener;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void updateData(List<Song> songs) {
|
||||||
|
if (songs == null) {
|
||||||
|
this.songList = new ArrayList<>();
|
||||||
|
} else {
|
||||||
|
this.songList = new ArrayList<>(songs);
|
||||||
|
}
|
||||||
|
notifyDataSetChanged();
|
||||||
|
Log.d("SongAdapter", "Data updated: " + this.songList.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
public class SongViewHolder extends RecyclerView.ViewHolder {
|
||||||
|
TextView textYear, textComposition, textTitle;
|
||||||
|
ImageButton editButton, deleteButton;
|
||||||
|
|
||||||
|
public SongViewHolder(View itemView) {
|
||||||
|
super(itemView);
|
||||||
|
textYear = itemView.findViewById(R.id.text_year);
|
||||||
|
textComposition = itemView.findViewById(R.id.text_composition);
|
||||||
|
textTitle = itemView.findViewById(R.id.text_title);
|
||||||
|
editButton = itemView.findViewById(R.id.edit_song_button);
|
||||||
|
deleteButton = itemView.findViewById(R.id.delete_song_button);
|
||||||
|
|
||||||
|
textTitle.setOnClickListener(v -> {
|
||||||
|
int position = getAdapterPosition();
|
||||||
|
if (position != RecyclerView.NO_POSITION) {
|
||||||
|
Song song = songList.get(position);
|
||||||
|
onOpenSongListener.onOpenSong(song);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public @NotNull SongViewHolder onCreateViewHolder(@NonNull @NotNull ViewGroup parent, int i) {
|
||||||
|
View itemView = LayoutInflater.from(parent.getContext())
|
||||||
|
.inflate(R.layout.item_song, parent, false);
|
||||||
|
return new SongViewHolder(itemView);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBindViewHolder(@NonNull @NotNull SongAdapter.SongViewHolder holder, int position) {
|
||||||
|
Song song = songList.get(position);
|
||||||
|
holder.textYear.setText(String.valueOf(song.getYear()));
|
||||||
|
holder.textComposition.setText(song.getComposer());
|
||||||
|
holder.textTitle.setText(song.getTitle());
|
||||||
|
|
||||||
|
holder.editButton.setOnClickListener(v -> onEditSongListener.onEditSong(song));
|
||||||
|
holder.deleteButton.setOnClickListener(v -> onDeleteSongListener.onDeleteSong(song));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getItemCount() {
|
||||||
|
return songList.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface OnEditSongListener {
|
||||||
|
void onEditSong(Song song);
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface OnDeleteSongListener {
|
||||||
|
void onDeleteSong(Song song);
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface OnOpenSongListener {
|
||||||
|
void onOpenSong(Song song);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,156 @@
|
|||||||
|
package com.stormtales.notevault.ui.login;
|
||||||
|
|
||||||
|
import android.app.AlertDialog;
|
||||||
|
import android.app.Dialog;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.text.Editable;
|
||||||
|
import android.text.TextWatcher;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.view.KeyEvent;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.inputmethod.EditorInfo;
|
||||||
|
import android.widget.*;
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.fragment.app.DialogFragment;
|
||||||
|
import androidx.lifecycle.Observer;
|
||||||
|
import androidx.lifecycle.ViewModelProvider;
|
||||||
|
import com.stormtales.notevault.R;
|
||||||
|
import com.stormtales.notevault.network.APICallback;
|
||||||
|
import com.stormtales.notevault.network.auth.AuthService;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
public class LoginDialog extends DialogFragment {
|
||||||
|
|
||||||
|
private LoginViewModel loginViewModel;
|
||||||
|
private boolean isLoginMode = true;
|
||||||
|
private Dialog dialog;
|
||||||
|
|
||||||
|
private Button loginButton;
|
||||||
|
private ProgressBar loadingProgressBar;
|
||||||
|
private EditText editTextUsername;
|
||||||
|
private EditText editTextPassword;
|
||||||
|
private EditText editTextEmail;
|
||||||
|
private TextView textViewTitle;
|
||||||
|
private TextView textViewSwitch;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAttach(Context context) {
|
||||||
|
super.onAttach(context);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public LoginDialog(LoginViewModel loginViewModel) {
|
||||||
|
this.loginViewModel = loginViewModel;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
|
||||||
|
AlertDialog.Builder builder = new AlertDialog.Builder(requireContext());
|
||||||
|
LayoutInflater inflater = requireActivity().getLayoutInflater();
|
||||||
|
View view = inflater.inflate(R.layout.fragment_login_dialog, null);
|
||||||
|
|
||||||
|
textViewTitle = view.findViewById(R.id.textViewTitle);
|
||||||
|
editTextUsername = view.findViewById(R.id.editTextUsername);
|
||||||
|
editTextEmail = view.findViewById(R.id.editTextEmail);
|
||||||
|
editTextPassword = view.findViewById(R.id.editTextPassword);
|
||||||
|
loginButton = view.findViewById(R.id.buttonAction);
|
||||||
|
textViewSwitch = view.findViewById(R.id.textViewSwitch);
|
||||||
|
loadingProgressBar = view.findViewById(R.id.loading);
|
||||||
|
// Handle action button click
|
||||||
|
|
||||||
|
|
||||||
|
// Toggle between Login and Registration
|
||||||
|
textViewSwitch.setOnClickListener(v -> {
|
||||||
|
isLoginMode = !isLoginMode;
|
||||||
|
textViewTitle.setText(isLoginMode ? "Login" : "Register");
|
||||||
|
editTextUsername.setVisibility(isLoginMode ? View.GONE : View.VISIBLE);
|
||||||
|
loginButton.setText(isLoginMode ? "Login" : "Register");
|
||||||
|
textViewSwitch.setText(isLoginMode ? "Don't have an account? Register" : "Already have an account? Login");
|
||||||
|
});
|
||||||
|
|
||||||
|
builder.setView(view);
|
||||||
|
dialog = builder.create();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
TextWatcher afterTextChangedListener = new TextWatcher() {
|
||||||
|
@Override
|
||||||
|
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onTextChanged(CharSequence s, int start, int before, int count) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void afterTextChanged(Editable s) {
|
||||||
|
loginViewModel.updateLoginData(editTextUsername.getText().toString(), editTextPassword.getText().toString());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
editTextUsername.addTextChangedListener(afterTextChangedListener);
|
||||||
|
editTextPassword.addTextChangedListener(afterTextChangedListener);
|
||||||
|
editTextPassword.setOnEditorActionListener(new TextView.OnEditorActionListener() {
|
||||||
|
@Override
|
||||||
|
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
|
||||||
|
if(actionId == EditorInfo.IME_ACTION_DONE) {
|
||||||
|
loginViewModel.performLogin(editTextUsername.getText().toString(), editTextPassword.getText().toString(), LoginDialog.this::onSuccessFullLogin);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
loginButton.setOnClickListener(v -> {
|
||||||
|
loadingProgressBar.setVisibility(View.VISIBLE);
|
||||||
|
String email = editTextEmail.getText().toString();
|
||||||
|
String password = editTextPassword.getText().toString();
|
||||||
|
if (isLoginMode) {
|
||||||
|
// Handle login logic
|
||||||
|
this.loginViewModel.performLogin(email, password, this::onSuccessFullLogin);
|
||||||
|
} else {
|
||||||
|
// Handle registration logic
|
||||||
|
String username = editTextUsername.getText().toString();
|
||||||
|
this.loginViewModel.performRegistration(email, password, username, this::onSuccessFullRegistration);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return dialog;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onSuccessFullRegistration() {
|
||||||
|
Toast.makeText(getContext(), "Successfully registered", Toast.LENGTH_LONG).show();
|
||||||
|
this.isLoginMode = true;
|
||||||
|
textViewTitle.setText(isLoginMode ? "Login" : "Register");
|
||||||
|
editTextUsername.setVisibility(isLoginMode ? View.GONE : View.VISIBLE);
|
||||||
|
loginButton.setText(isLoginMode ? "Login" : "Register");
|
||||||
|
textViewSwitch.setText(isLoginMode ? "Don't have an account? Register" : "Already have an account? Login");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onViewCreated(@NonNull @NotNull View view, @Nullable @org.jetbrains.annotations.Nullable Bundle savedInstanceState) {
|
||||||
|
super.onViewCreated(view, savedInstanceState);
|
||||||
|
loginViewModel.getLoginFormState().observe(getViewLifecycleOwner(), new Observer<LoginFormState>() {
|
||||||
|
@Override
|
||||||
|
public void onChanged(LoginFormState loginFormState) {
|
||||||
|
if(loginFormState == null) return;
|
||||||
|
loginButton.setEnabled(loginFormState.isDataValid());
|
||||||
|
if(loginFormState.getUsernameError() != null) {
|
||||||
|
editTextUsername.setError(getString(loginFormState.getUsernameError()));
|
||||||
|
}
|
||||||
|
if(loginFormState.getPasswordError() != null) {
|
||||||
|
editTextPassword.setError(getString(loginFormState.getPasswordError()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void onSuccessFullLogin() {
|
||||||
|
this.dialog.dismiss();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,37 @@
|
|||||||
|
package com.stormtales.notevault.ui.login;
|
||||||
|
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
public class LoginFormState {
|
||||||
|
@Nullable
|
||||||
|
private Integer usernameError;
|
||||||
|
@Nullable
|
||||||
|
private Integer passwordError;
|
||||||
|
private boolean isDataValid;
|
||||||
|
|
||||||
|
LoginFormState(@Nullable Integer usernameError, @Nullable Integer passwordError) {
|
||||||
|
this.usernameError = usernameError;
|
||||||
|
this.passwordError = passwordError;
|
||||||
|
this.isDataValid = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
LoginFormState(boolean isDataValid) {
|
||||||
|
this.usernameError = null;
|
||||||
|
this.passwordError = null;
|
||||||
|
this.isDataValid = isDataValid;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
Integer getUsernameError() {
|
||||||
|
return usernameError;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
Integer getPasswordError() {
|
||||||
|
return passwordError;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean isDataValid() {
|
||||||
|
return isDataValid;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,116 @@
|
|||||||
|
package com.stormtales.notevault.ui.login;
|
||||||
|
|
||||||
|
import android.util.Log;
|
||||||
|
import android.util.Patterns;
|
||||||
|
import android.widget.Toast;
|
||||||
|
import androidx.lifecycle.MutableLiveData;
|
||||||
|
import androidx.lifecycle.ViewModel;
|
||||||
|
import com.stormtales.notevault.R;
|
||||||
|
import com.stormtales.notevault.network.APICallback;
|
||||||
|
import com.stormtales.notevault.network.auth.AuthService;
|
||||||
|
import com.stormtales.notevault.network.auth.LoginResponse;
|
||||||
|
|
||||||
|
public class LoginViewModel extends ViewModel {
|
||||||
|
|
||||||
|
private final MutableLiveData<String> username;
|
||||||
|
private final MutableLiveData<Boolean> isLoggedIn;
|
||||||
|
private MutableLiveData<LoginFormState> loginFormState = new MutableLiveData<>();
|
||||||
|
private AuthService authService;
|
||||||
|
|
||||||
|
public LoginViewModel() {
|
||||||
|
username = new MutableLiveData<>("Not Logged In");
|
||||||
|
isLoggedIn = new MutableLiveData<>(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void performLogin(String email, String password, SuccessFullLoginCallback successFullLoginCallback) {
|
||||||
|
if(authService != null) {
|
||||||
|
authService.performLogin(email, password, new LoginCallBackImpl(successFullLoginCallback));
|
||||||
|
} else {
|
||||||
|
throw new RuntimeException("AuthService is not provided to LoginViewModel");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void performRegistration(String email, String password, String username, SuccessFullLoginCallback successFullLoginCallback) {
|
||||||
|
if(authService != null) {
|
||||||
|
authService.performRegistration(email, username, password, new APICallback() {
|
||||||
|
@Override
|
||||||
|
public void onSuccess() {
|
||||||
|
successFullLoginCallback.onSuccess();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onError(String error) {
|
||||||
|
Log.d("LoginService", error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAuthService(AuthService authService) {
|
||||||
|
this.authService = authService;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MutableLiveData<Boolean> getIsLoggedIn() {
|
||||||
|
return isLoggedIn;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MutableLiveData<String> getUsername() {
|
||||||
|
return username;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void updateLoginData(String username, String password) {
|
||||||
|
if(!isUserNameValid(username)) {
|
||||||
|
loginFormState.setValue(new LoginFormState(R.string.invalid_username, null));
|
||||||
|
} else if(!isPasswordValid(password)) {
|
||||||
|
loginFormState.setValue(new LoginFormState(null, R.string.invalid_password));
|
||||||
|
} else {
|
||||||
|
loginFormState.setValue(new LoginFormState(true));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class LoginCallBackImpl implements AuthService.LoginCallback {
|
||||||
|
|
||||||
|
private final SuccessFullLoginCallback successFullLoginCallback;
|
||||||
|
|
||||||
|
public LoginCallBackImpl(SuccessFullLoginCallback successFullLoginCallback) {
|
||||||
|
this.successFullLoginCallback = successFullLoginCallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSuccess(LoginResponse loginResponse) {
|
||||||
|
username.setValue(loginResponse.getUsername());
|
||||||
|
isLoggedIn.setValue(true);
|
||||||
|
successFullLoginCallback.onSuccess();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onError(String error) {
|
||||||
|
Log.d("LoginService", error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface SuccessFullLoginCallback {
|
||||||
|
void onSuccess();
|
||||||
|
}
|
||||||
|
|
||||||
|
public MutableLiveData<LoginFormState> getLoginFormState() {
|
||||||
|
return loginFormState;
|
||||||
|
}
|
||||||
|
|
||||||
|
// A placeholder username validation check
|
||||||
|
private boolean isUserNameValid(String username) {
|
||||||
|
if (username == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (username.contains("@")) {
|
||||||
|
return Patterns.EMAIL_ADDRESS.matcher(username).matches();
|
||||||
|
} else {
|
||||||
|
return !username.trim().isEmpty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// A placeholder password validation check
|
||||||
|
private boolean isPasswordValid(String password) {
|
||||||
|
return password != null && password.trim().length() > 5;
|
||||||
|
}
|
||||||
|
}
|
@ -1,4 +1,5 @@
|
|||||||
package core.notevault.ui.noteviewer;
|
package com.stormtales.notevault.ui.sheetdisplay;
|
||||||
|
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
@ -8,7 +9,7 @@ import android.view.ViewGroup;
|
|||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
import com.github.chrisbanes.photoview.PhotoView;
|
import com.github.chrisbanes.photoview.PhotoView;
|
||||||
import core.notevault.R;
|
import com.stormtales.notevault.R;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
@ -1,25 +1,26 @@
|
|||||||
package core.notevault.ui.home;
|
package com.stormtales.notevault.ui.sheetdisplay;
|
||||||
|
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.net.Uri;
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.widget.ImageView;
|
|
||||||
import androidx.appcompat.app.AppCompatActivity;
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
import androidx.viewpager2.widget.ViewPager2;
|
import androidx.viewpager2.widget.ViewPager2;
|
||||||
import core.notevault.R;
|
import com.google.android.material.tabs.TabLayout;
|
||||||
import core.notevault.ui.noteviewer.ImagePagerAdapter;
|
import com.google.android.material.tabs.TabLayoutMediator;
|
||||||
|
import com.stormtales.notevault.R;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public class FullScreenImageActivity extends AppCompatActivity {
|
public class NoteSheetDisplayActivity extends AppCompatActivity {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
setContentView(R.layout.activity_fullscreen_image); // Erstelle eine Layout-Datei
|
setContentView(R.layout.activity_note_sheet_viewer); // Erstelle eine Layout-Datei
|
||||||
|
|
||||||
ViewPager2 imageView = findViewById(R.id.viewPager); // Angenommen, du hast ein ImageView
|
ViewPager2 imageView = findViewById(R.id.viewPager); // Angenommen, du hast ein ImageView
|
||||||
|
TabLayout tabLayout = findViewById(R.id.tabLayout);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Die URI aus dem Intent erhalten
|
// Die URI aus dem Intent erhalten
|
||||||
Intent intent = getIntent();
|
Intent intent = getIntent();
|
||||||
@ -28,6 +29,10 @@ public class FullScreenImageActivity extends AppCompatActivity {
|
|||||||
List<String> uriList = Arrays.asList(imageUris);
|
List<String> uriList = Arrays.asList(imageUris);
|
||||||
ImagePagerAdapter adapter = new ImagePagerAdapter(this, uriList);
|
ImagePagerAdapter adapter = new ImagePagerAdapter(this, uriList);
|
||||||
imageView.setAdapter(adapter); // Setze die URI in das ImageView
|
imageView.setAdapter(adapter); // Setze die URI in das ImageView
|
||||||
|
|
||||||
|
new TabLayoutMediator(tabLayout, imageView,
|
||||||
|
(tab, position) -> tab.setText("Sheet " + (position + 1)) // Hier kannst du auch andere Labels verwenden
|
||||||
|
).attach();
|
||||||
} else {
|
} else {
|
||||||
throw new NullPointerException();
|
throw new NullPointerException();
|
||||||
}
|
}
|
@ -1,4 +1,4 @@
|
|||||||
package core.notevault.ui.slideshow;
|
package com.stormtales.notevault.ui.slideshow;
|
||||||
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
@ -8,7 +8,7 @@ import android.widget.TextView;
|
|||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.fragment.app.Fragment;
|
import androidx.fragment.app.Fragment;
|
||||||
import androidx.lifecycle.ViewModelProvider;
|
import androidx.lifecycle.ViewModelProvider;
|
||||||
import core.notevault.databinding.FragmentSlideshowBinding;
|
import com.stormtales.notevault.databinding.FragmentSlideshowBinding;
|
||||||
|
|
||||||
public class SlideshowFragment extends Fragment {
|
public class SlideshowFragment extends Fragment {
|
||||||
|
|
@ -1,4 +1,4 @@
|
|||||||
package core.notevault.ui.slideshow;
|
package com.stormtales.notevault.ui.slideshow;
|
||||||
|
|
||||||
import androidx.lifecycle.LiveData;
|
import androidx.lifecycle.LiveData;
|
||||||
import androidx.lifecycle.MutableLiveData;
|
import androidx.lifecycle.MutableLiveData;
|
@ -0,0 +1,193 @@
|
|||||||
|
package com.stormtales.notevault.ui.songeditor;
|
||||||
|
|
||||||
|
import android.app.AlertDialog;
|
||||||
|
import android.app.Dialog;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.text.Layout;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.widget.Button;
|
||||||
|
import android.widget.EditText;
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.fragment.app.DialogFragment;
|
||||||
|
import androidx.fragment.app.Fragment;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import androidx.lifecycle.ViewModelProvider;
|
||||||
|
import com.stormtales.notevault.R;
|
||||||
|
import com.stormtales.notevault.data.entities.NoteSheet;
|
||||||
|
import com.stormtales.notevault.data.entities.Song;
|
||||||
|
import com.stormtales.notevault.data.sync.SyncStatus;
|
||||||
|
import com.stormtales.notevault.network.sync.SongSyncService;
|
||||||
|
import com.stormtales.notevault.network.sync.models.AIRecognizedSong;
|
||||||
|
import com.stormtales.notevault.ui.home.HomeViewModel;
|
||||||
|
import com.stormtales.notevault.utils.NoteSheetsUtil;
|
||||||
|
import com.stormtales.notevault.utils.Tupel;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
|
||||||
|
public class SongEditorDialog extends DialogFragment {
|
||||||
|
|
||||||
|
Dialog dialog;
|
||||||
|
private Uri[] noteSheetFiles;
|
||||||
|
private HomeViewModel homeViewModel;
|
||||||
|
|
||||||
|
private Song editedSong;
|
||||||
|
private SongSyncService songSyncService;
|
||||||
|
|
||||||
|
private View progressbar;
|
||||||
|
public SongEditorDialog() {
|
||||||
|
// Required empty public constructor
|
||||||
|
this.songSyncService = new SongSyncService(this.getContext());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public @NotNull Dialog onCreateDialog(@Nullable @org.jetbrains.annotations.Nullable Bundle savedInstanceState) {
|
||||||
|
LayoutInflater inflater = LayoutInflater.from(getContext());
|
||||||
|
View dialogView = inflater.inflate(R.layout.fragment_song_editor_dialog, null);
|
||||||
|
|
||||||
|
|
||||||
|
dialogView.findViewById(R.id.btnCancel).setOnClickListener(v-> onCancel());
|
||||||
|
dialogView.findViewById(R.id.btnSave).setOnClickListener(v -> onSave());
|
||||||
|
|
||||||
|
if(this.noteSheetFiles == null || noteSheetFiles.length == 0) {
|
||||||
|
dialogView.findViewById(R.id.btnAutoDetect).setEnabled(false);
|
||||||
|
} else {
|
||||||
|
progressbar = dialogView.findViewById(R.id.spinnerAutoDetect);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
dialogView.findViewById(R.id.btnAutoDetect).setOnClickListener(v -> {
|
||||||
|
progressbar.setVisibility(ViewGroup.VISIBLE);
|
||||||
|
extractTitleFromFirstPage();
|
||||||
|
});
|
||||||
|
|
||||||
|
AlertDialog.Builder builder = new AlertDialog.Builder(getContext());
|
||||||
|
builder.setCancelable(false);
|
||||||
|
builder.setView(dialogView);
|
||||||
|
|
||||||
|
dialog = builder.create();
|
||||||
|
dialog.setCanceledOnTouchOutside(false);
|
||||||
|
return dialog;
|
||||||
|
}
|
||||||
|
|
||||||
|
private File createTemporaryFileForRecognition(Uri noteSheetUri) {
|
||||||
|
try {
|
||||||
|
// Hole den InputStream des Bildes
|
||||||
|
InputStream inputStream = getContext().getContentResolver().openInputStream(noteSheetUri);
|
||||||
|
if (inputStream != null) {
|
||||||
|
// Erstelle eine temporäre Datei
|
||||||
|
File tempFile = File.createTempFile("temp_note_sheet", ".jpg", getContext().getCacheDir());
|
||||||
|
|
||||||
|
// Inhalt des InputStreams in die temporäre Datei schreiben
|
||||||
|
try (OutputStream outputStream = new FileOutputStream(tempFile)) {
|
||||||
|
byte[] buffer = new byte[8192];
|
||||||
|
int bytesRead;
|
||||||
|
while ((bytesRead = inputStream.read(buffer)) != -1) {
|
||||||
|
outputStream.write(buffer, 0, bytesRead);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Schließe den InputStream
|
||||||
|
inputStream.close();
|
||||||
|
|
||||||
|
return tempFile;
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e("SongEditorDialog", "Fehler beim Erstellen der temporären Datei: " + e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
private void extractTitleFromFirstPage() {
|
||||||
|
if(noteSheetFiles != null && noteSheetFiles.length > 0) {
|
||||||
|
Uri firstPageUri = noteSheetFiles[0];
|
||||||
|
|
||||||
|
// Temporäre Datei erstellen
|
||||||
|
File tempFile = createTemporaryFileForRecognition(firstPageUri);
|
||||||
|
if(tempFile != null) {
|
||||||
|
songSyncService.recognizeTitle(tempFile.getAbsolutePath(), new SongSyncService.RecognizedSongCallback() {
|
||||||
|
@Override
|
||||||
|
public void onRecognizedSong(AIRecognizedSong aiRecognizedSong) {
|
||||||
|
progressbar.setVisibility(ViewGroup.GONE);
|
||||||
|
((EditText) dialog.findViewById(R.id.etTitle)).setText(aiRecognizedSong.getTitle());
|
||||||
|
((EditText) dialog.findViewById(R.id.etComposer)).setText(aiRecognizedSong.getComposer());
|
||||||
|
((EditText) dialog.findViewById(R.id.etYear)).setText(String.valueOf(aiRecognizedSong.getYear()));
|
||||||
|
((EditText) dialog.findViewById(R.id.etGenre)).setText(aiRecognizedSong.getDescription());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onStart() {
|
||||||
|
super.onStart();
|
||||||
|
if(editedSong != null) {
|
||||||
|
((EditText) dialog.findViewById(R.id.etTitle)).setText(editedSong.getTitle());
|
||||||
|
((EditText) dialog.findViewById(R.id.etComposer)).setText(editedSong.getComposer());
|
||||||
|
((EditText) dialog.findViewById(R.id.etGenre)).setText(editedSong.getGenre());
|
||||||
|
((EditText) dialog.findViewById(R.id.etYear)).setText(String.valueOf(editedSong.getYear()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onCancel() {
|
||||||
|
dialog.dismiss();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onSave() {
|
||||||
|
String title = ((EditText) dialog.findViewById(R.id.etTitle)).getText().toString();
|
||||||
|
String composer = ((EditText) dialog.findViewById(R.id.etComposer)).getText().toString();
|
||||||
|
|
||||||
|
String year_string = ((EditText) dialog.findViewById(R.id.etYear)).getText().toString();
|
||||||
|
int releaseYear = year_string.isBlank()? 0 : Integer.parseInt(year_string);
|
||||||
|
String genre = ((EditText) dialog.findViewById(R.id.etGenre)).getText().toString();
|
||||||
|
|
||||||
|
if(editedSong != null) {
|
||||||
|
editedSong.setTitle(title);
|
||||||
|
editedSong.setComposer(composer);
|
||||||
|
editedSong.setYear(releaseYear);
|
||||||
|
editedSong.setGenre(genre);
|
||||||
|
editedSong.setSyncStatus(SyncStatus.MODIFIED);
|
||||||
|
homeViewModel.updateSong(editedSong);
|
||||||
|
} else {
|
||||||
|
Song song = new Song(title, composer, genre, releaseYear);
|
||||||
|
|
||||||
|
Context context = this.getContext();
|
||||||
|
List<NoteSheet> noteSheetList = new ArrayList<>();
|
||||||
|
for(Uri uri : noteSheetFiles) {
|
||||||
|
Tupel<String, String> result = NoteSheetsUtil.saveImageInternally(context.getContentResolver(), uri, context.getFilesDir());
|
||||||
|
noteSheetList.add(new NoteSheet(result.getValue00(), result.getValue01()));
|
||||||
|
}
|
||||||
|
homeViewModel.addSong(song, noteSheetList);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
dialog.dismiss();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setNoteSheetFiles(Uri[] noteSheetFiles) {
|
||||||
|
this.noteSheetFiles = noteSheetFiles;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setHomeViewModel(HomeViewModel homeViewModel) {
|
||||||
|
this.homeViewModel = homeViewModel;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEditedSong(Song editedSong) {
|
||||||
|
this.editedSong = editedSong;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,117 @@
|
|||||||
|
package com.stormtales.notevault.utils;
|
||||||
|
|
||||||
|
import android.content.ContentResolver;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.media.ExifInterface;
|
||||||
|
import android.net.Uri;
|
||||||
|
import okhttp3.ResponseBody;
|
||||||
|
|
||||||
|
import java.io.*;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.security.MessageDigest;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.text.ParseException;
|
||||||
|
import java.text.SimpleDateFormat;
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
public class NoteSheetsUtil {
|
||||||
|
private static long getImageTimestamp(Context context, Uri imageUri) {
|
||||||
|
try {
|
||||||
|
InputStream inputStream = context.getContentResolver().openInputStream(imageUri);
|
||||||
|
ExifInterface exif = new ExifInterface(inputStream);
|
||||||
|
|
||||||
|
String dateTime = exif.getAttribute(ExifInterface.TAG_DATETIME);
|
||||||
|
if(dateTime != null) {
|
||||||
|
SimpleDateFormat format = new SimpleDateFormat("yyyy:MM:dd HH:mm:ss", Locale.getDefault());
|
||||||
|
Date date = format.parse(dateTime);
|
||||||
|
return date != null ? date.getTime() : 0;
|
||||||
|
} else {
|
||||||
|
File file = new File(imageUri.getPath());
|
||||||
|
return file.lastModified();
|
||||||
|
}
|
||||||
|
} catch (IOException | ParseException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void sortNoteSheetsByTimestamp(Context context, Uri[] uris) {
|
||||||
|
ArrayList<UriTimestamp> uriTimestamps = new ArrayList<>();
|
||||||
|
|
||||||
|
for(Uri uri : uris) {
|
||||||
|
long timestamp = getImageTimestamp(context, uri);
|
||||||
|
uriTimestamps.add(new UriTimestamp(uri, timestamp));
|
||||||
|
}
|
||||||
|
|
||||||
|
Collections.sort(uriTimestamps, Comparator.comparingLong(UriTimestamp::getTimestamp));
|
||||||
|
|
||||||
|
for(int i=0; i<uriTimestamps.size(); i++) {
|
||||||
|
uris[i] = uriTimestamps.get(i).getUri();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Tupel<String, String> saveImageInternally(ContentResolver contentResolver, Uri uri, File filesDir) {
|
||||||
|
try (InputStream inputStream = contentResolver.openInputStream(uri)) {
|
||||||
|
// Datei erstellen
|
||||||
|
File imageFile = new File(filesDir, "saved_image_" + System.currentTimeMillis() + ".jpg");
|
||||||
|
try (OutputStream outputStream = Files.newOutputStream(imageFile.toPath())) {
|
||||||
|
// SHA-256 Hash-Funktion initialisieren
|
||||||
|
MessageDigest digest = MessageDigest.getInstance("SHA-256");
|
||||||
|
|
||||||
|
// Daten blockweise lesen und gleichzeitig schreiben und hashen
|
||||||
|
byte[] buffer = new byte[4096];
|
||||||
|
int bytesRead;
|
||||||
|
while ((bytesRead = inputStream.read(buffer)) != -1) {
|
||||||
|
outputStream.write(buffer, 0, bytesRead);
|
||||||
|
digest.update(buffer, 0, bytesRead); // Hash aktualisieren
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hash berechnen
|
||||||
|
byte[] hashBytes = digest.digest();
|
||||||
|
StringBuilder hashString = new StringBuilder();
|
||||||
|
for (byte b : hashBytes) {
|
||||||
|
hashString.append(String.format("%02x", b)); // Bytes in Hexadezimal umwandeln
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hashwert zurückgeben (kann auch zusammen mit dem Pfad gespeichert werden)
|
||||||
|
return new Tupel<>(imageFile.getAbsolutePath(), hashString.toString());
|
||||||
|
} catch (NoSuchAlgorithmException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Tupel<String, String> saveImageFromServer(ResponseBody body, File filesDir) {
|
||||||
|
File imageFile = new File(filesDir, "saved_image_" + System.currentTimeMillis() + ".jpg");
|
||||||
|
try (InputStream inputStream = body.byteStream();
|
||||||
|
FileOutputStream outputStream = new FileOutputStream(imageFile)) {
|
||||||
|
MessageDigest digest = MessageDigest.getInstance("SHA-256");
|
||||||
|
|
||||||
|
// Daten blockweise lesen und gleichzeitig schreiben und hashen
|
||||||
|
byte[] buffer = new byte[4096];
|
||||||
|
int bytesRead;
|
||||||
|
while ((bytesRead = inputStream.read(buffer)) != -1) {
|
||||||
|
outputStream.write(buffer, 0, bytesRead);
|
||||||
|
digest.update(buffer, 0, bytesRead); // Hash aktualisieren
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hash berechnen
|
||||||
|
byte[] hashBytes = digest.digest();
|
||||||
|
StringBuilder hashString = new StringBuilder();
|
||||||
|
for (byte b : hashBytes) {
|
||||||
|
hashString.append(String.format("%02x", b)); // Bytes in Hexadezimal umwandeln
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hashwert zurückgeben (kann auch zusammen mit dem Pfad gespeichert werden)
|
||||||
|
return new Tupel<>(imageFile.getAbsolutePath(), hashString.toString());
|
||||||
|
} catch (IOException | NoSuchAlgorithmException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void deleteNoteSheet(String filePath) throws IOException {
|
||||||
|
Files.delete(new File(filePath).toPath());
|
||||||
|
}
|
||||||
|
}
|
33
app/src/main/java/com/stormtales/notevault/utils/Tupel.java
Normal file
33
app/src/main/java/com/stormtales/notevault/utils/Tupel.java
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
package com.stormtales.notevault.utils;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
public class Tupel<A,B> {
|
||||||
|
private final A value00;
|
||||||
|
private final B value01;
|
||||||
|
|
||||||
|
public Tupel(A value00, B value01) {
|
||||||
|
this.value00 = value00;
|
||||||
|
this.value01 = value01;
|
||||||
|
}
|
||||||
|
|
||||||
|
public A getValue00() {
|
||||||
|
return value00;
|
||||||
|
}
|
||||||
|
|
||||||
|
public B getValue01() {
|
||||||
|
return value01;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (o == null || getClass() != o.getClass()) return false;
|
||||||
|
Tupel<?, ?> tupel = (Tupel<?, ?>) o;
|
||||||
|
return Objects.equals(value00, tupel.value00) && Objects.equals(value01, tupel.value01);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hash(value00, value01);
|
||||||
|
}
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
package core.notevault.util;
|
package com.stormtales.notevault.utils;
|
||||||
|
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
|
|
@ -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;
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
@ -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);
|
|
||||||
}
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,5 +0,0 @@
|
|||||||
package core.notevault.data.sync;
|
|
||||||
|
|
||||||
public enum SyncDataObjectType {
|
|
||||||
MUSIC_NOTE
|
|
||||||
}
|
|
@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
@ -1,7 +0,0 @@
|
|||||||
package core.notevault.sync.auth;
|
|
||||||
|
|
||||||
public interface LoginCallback {
|
|
||||||
|
|
||||||
void onSuccess();
|
|
||||||
void onError(String error);
|
|
||||||
}
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
@ -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);
|
|
||||||
}
|
|
@ -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();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,5 +0,0 @@
|
|||||||
package core.notevault.sync.synchronisation.songs.notesheets;
|
|
||||||
|
|
||||||
public class UploadResponse {
|
|
||||||
private String serverID;
|
|
||||||
}
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -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";
|
|
||||||
}
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user