nextNoteVault #23
@ -1,46 +0,0 @@
 | 
			
		||||
+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
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										8
									
								
								.idea/.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										8
									
								
								.idea/.gitignore
									
									
									
									
										vendored
									
									
								
							@ -1,8 +0,0 @@
 | 
			
		||||
# Default ignored files
 | 
			
		||||
/shelf/
 | 
			
		||||
/workspace.xml
 | 
			
		||||
# Editor-based HTTP Client requests
 | 
			
		||||
/httpRequests/
 | 
			
		||||
# Datasource local storage ignored files
 | 
			
		||||
/dataSources/
 | 
			
		||||
/dataSources.local.xml
 | 
			
		||||
@ -1 +0,0 @@
 | 
			
		||||
NoteVault
 | 
			
		||||
@ -1,6 +0,0 @@
 | 
			
		||||
<?xml version="1.0" encoding="UTF-8"?>
 | 
			
		||||
<project version="4">
 | 
			
		||||
  <component name="CompilerConfiguration">
 | 
			
		||||
    <bytecodeTargetLevel target="21" />
 | 
			
		||||
  </component>
 | 
			
		||||
</project>
 | 
			
		||||
@ -1,23 +0,0 @@
 | 
			
		||||
<?xml version="1.0" encoding="UTF-8"?>
 | 
			
		||||
<project version="4">
 | 
			
		||||
  <component name="DataSourceManagerImpl" format="xml" multifile-model="true">
 | 
			
		||||
    <data-source source="LOCAL" name="music_database" uuid="eb23f694-6586-450b-8f6f-a75731d36b96">
 | 
			
		||||
      <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>
 | 
			
		||||
      <libraries>
 | 
			
		||||
        <library>
 | 
			
		||||
          <url>file://$APPLICATION_CONFIG_DIR$/jdbc-drivers/Xerial SQLiteJDBC/3.45.1/org/xerial/sqlite-jdbc/3.45.1.0/sqlite-jdbc-3.45.1.0.jar</url>
 | 
			
		||||
        </library>
 | 
			
		||||
        <library>
 | 
			
		||||
          <url>file://$APPLICATION_CONFIG_DIR$/jdbc-drivers/Xerial SQLiteJDBC/3.45.1/org/slf4j/slf4j-api/1.7.36/slf4j-api-1.7.36.jar</url>
 | 
			
		||||
        </library>
 | 
			
		||||
      </libraries>
 | 
			
		||||
    </data-source>
 | 
			
		||||
  </component>
 | 
			
		||||
</project>
 | 
			
		||||
@ -1,18 +0,0 @@
 | 
			
		||||
<?xml version="1.0" encoding="UTF-8"?>
 | 
			
		||||
<project version="4">
 | 
			
		||||
  <component name="deploymentTargetSelector">
 | 
			
		||||
    <selectionStates>
 | 
			
		||||
      <SelectionState runConfigName="app">
 | 
			
		||||
        <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>
 | 
			
		||||
    </selectionStates>
 | 
			
		||||
  </component>
 | 
			
		||||
</project>
 | 
			
		||||
@ -1,18 +0,0 @@
 | 
			
		||||
<?xml version="1.0" encoding="UTF-8"?>
 | 
			
		||||
<project version="4">
 | 
			
		||||
  <component name="GradleMigrationSettings" migrationVersion="1" />
 | 
			
		||||
  <component name="GradleSettings">
 | 
			
		||||
    <option name="linkedExternalProjectsSettings">
 | 
			
		||||
      <GradleProjectSettings>
 | 
			
		||||
        <option name="externalProjectPath" value="$PROJECT_DIR$" />
 | 
			
		||||
        <option name="gradleJvm" value="#GRADLE_LOCAL_JAVA_HOME" />
 | 
			
		||||
        <option name="modules">
 | 
			
		||||
          <set>
 | 
			
		||||
            <option value="$PROJECT_DIR$" />
 | 
			
		||||
            <option value="$PROJECT_DIR$/app" />
 | 
			
		||||
          </set>
 | 
			
		||||
        </option>
 | 
			
		||||
      </GradleProjectSettings>
 | 
			
		||||
    </option>
 | 
			
		||||
  </component>
 | 
			
		||||
</project>
 | 
			
		||||
@ -1,12 +0,0 @@
 | 
			
		||||
<project version="4">
 | 
			
		||||
  <component name="ExternalStorageConfigurationManager" enabled="true" />
 | 
			
		||||
  <component name="FrameworkDetectionExcludesConfiguration">
 | 
			
		||||
    <file type="web" url="file://$PROJECT_DIR$" />
 | 
			
		||||
  </component>
 | 
			
		||||
  <component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="21" project-jdk-type="JavaSDK">
 | 
			
		||||
    <output url="file://$PROJECT_DIR$/build/classes" />
 | 
			
		||||
  </component>
 | 
			
		||||
  <component name="ProjectType">
 | 
			
		||||
    <option name="id" value="Android" />
 | 
			
		||||
  </component>
 | 
			
		||||
</project>
 | 
			
		||||
@ -1,17 +0,0 @@
 | 
			
		||||
<?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,6 +0,0 @@
 | 
			
		||||
<?xml version="1.0" encoding="UTF-8"?>
 | 
			
		||||
<project version="4">
 | 
			
		||||
  <component name="VcsDirectoryMappings">
 | 
			
		||||
    <mapping directory="" vcs="Git" />
 | 
			
		||||
  </component>
 | 
			
		||||
</project>
 | 
			
		||||
@ -1,14 +1,16 @@
 | 
			
		||||
plugins {
 | 
			
		||||
    alias(libs.plugins.android.application)
 | 
			
		||||
    alias(libs.plugins.kotlin.android)
 | 
			
		||||
    alias(libs.plugins.kotlin.compose)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
android {
 | 
			
		||||
    namespace = "com.stormtales.notevault"
 | 
			
		||||
    namespace = "come.stormborntales.notevault"
 | 
			
		||||
    compileSdk = 34
 | 
			
		||||
 | 
			
		||||
    defaultConfig {
 | 
			
		||||
        applicationId = "com.stormtales.notevault"
 | 
			
		||||
        minSdk = 29
 | 
			
		||||
        applicationId = "come.stormborntales.notevault"
 | 
			
		||||
        minSdk = 24
 | 
			
		||||
        targetSdk = 34
 | 
			
		||||
        versionCode = 1
 | 
			
		||||
        versionName = "1.0"
 | 
			
		||||
@ -26,28 +28,31 @@ android {
 | 
			
		||||
        sourceCompatibility = JavaVersion.VERSION_11
 | 
			
		||||
        targetCompatibility = JavaVersion.VERSION_11
 | 
			
		||||
    }
 | 
			
		||||
    kotlinOptions {
 | 
			
		||||
        jvmTarget = "11"
 | 
			
		||||
    }
 | 
			
		||||
    buildFeatures {
 | 
			
		||||
        viewBinding = true
 | 
			
		||||
        compose = true
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
dependencies {
 | 
			
		||||
 | 
			
		||||
    implementation(libs.appcompat)
 | 
			
		||||
    implementation(libs.material)
 | 
			
		||||
    implementation(libs.constraintlayout)
 | 
			
		||||
    implementation(libs.lifecycle.livedata.ktx)
 | 
			
		||||
    implementation(libs.lifecycle.viewmodel.ktx)
 | 
			
		||||
    implementation(libs.navigation.fragment)
 | 
			
		||||
    implementation(libs.navigation.ui)
 | 
			
		||||
    implementation(libs.room.runtime)
 | 
			
		||||
    implementation(libs.annotation)
 | 
			
		||||
    implementation(libs.androidx.core.ktx)
 | 
			
		||||
    implementation(libs.androidx.lifecycle.runtime.ktx)
 | 
			
		||||
    implementation(libs.androidx.activity.compose)
 | 
			
		||||
    implementation(platform(libs.androidx.compose.bom))
 | 
			
		||||
    implementation(libs.androidx.ui)
 | 
			
		||||
    implementation(libs.androidx.ui.graphics)
 | 
			
		||||
    implementation(libs.androidx.ui.tooling.preview)
 | 
			
		||||
    implementation(libs.androidx.material3)
 | 
			
		||||
    implementation(libs.androidx.navigation.compose)
 | 
			
		||||
    implementation(libs.androidx.lifecycle.viewmodel.compose)
 | 
			
		||||
    testImplementation(libs.junit)
 | 
			
		||||
    androidTestImplementation(libs.ext.junit)
 | 
			
		||||
    androidTestImplementation(libs.espresso.core)
 | 
			
		||||
    annotationProcessor(libs.room.compiler)
 | 
			
		||||
    implementation("com.github.chrisbanes:PhotoView:2.3.0")
 | 
			
		||||
    implementation("com.squareup.retrofit2:retrofit:2.9.0")
 | 
			
		||||
    implementation("com.squareup.retrofit2:converter-gson:2.9.0")
 | 
			
		||||
    implementation("com.squareup.okhttp3:logging-interceptor:4.9.1")
 | 
			
		||||
    androidTestImplementation(libs.androidx.junit)
 | 
			
		||||
    androidTestImplementation(libs.androidx.espresso.core)
 | 
			
		||||
    androidTestImplementation(platform(libs.androidx.compose.bom))
 | 
			
		||||
    androidTestImplementation(libs.androidx.ui.test.junit4)
 | 
			
		||||
    debugImplementation(libs.androidx.ui.tooling)
 | 
			
		||||
    debugImplementation(libs.androidx.ui.test.manifest)
 | 
			
		||||
}
 | 
			
		||||
@ -1,25 +0,0 @@
 | 
			
		||||
package com.stormtales.notevault;
 | 
			
		||||
 | 
			
		||||
import android.content.Context;
 | 
			
		||||
import androidx.test.platform.app.InstrumentationRegistry;
 | 
			
		||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
 | 
			
		||||
 | 
			
		||||
import org.junit.Test;
 | 
			
		||||
import org.junit.runner.RunWith;
 | 
			
		||||
 | 
			
		||||
import static org.junit.Assert.*;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Instrumented test, which will execute on an Android device.
 | 
			
		||||
 *
 | 
			
		||||
 * @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
 | 
			
		||||
 */
 | 
			
		||||
@RunWith(AndroidJUnit4.class)
 | 
			
		||||
public class ExampleInstrumentedTest {
 | 
			
		||||
    @Test
 | 
			
		||||
    public void useAppContext() {
 | 
			
		||||
        // Context of the app under test.
 | 
			
		||||
        Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
 | 
			
		||||
        assertEquals("com.stormtales.notevault", appContext.getPackageName());
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,24 @@
 | 
			
		||||
package come.stormborntales.notevault
 | 
			
		||||
 | 
			
		||||
import androidx.test.platform.app.InstrumentationRegistry
 | 
			
		||||
import androidx.test.ext.junit.runners.AndroidJUnit4
 | 
			
		||||
 | 
			
		||||
import org.junit.Test
 | 
			
		||||
import org.junit.runner.RunWith
 | 
			
		||||
 | 
			
		||||
import org.junit.Assert.*
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Instrumented test, which will execute on an Android device.
 | 
			
		||||
 *
 | 
			
		||||
 * See [testing documentation](http://d.android.com/tools/testing).
 | 
			
		||||
 */
 | 
			
		||||
@RunWith(AndroidJUnit4::class)
 | 
			
		||||
class ExampleInstrumentedTest {
 | 
			
		||||
    @Test
 | 
			
		||||
    fun useAppContext() {
 | 
			
		||||
        // Context of the app under test.
 | 
			
		||||
        val appContext = InstrumentationRegistry.getInstrumentation().targetContext
 | 
			
		||||
        assertEquals("come.stormborntales.notevault", appContext.packageName)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -1,12 +1,11 @@
 | 
			
		||||
<?xml version="1.0" encoding="utf-8"?>
 | 
			
		||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
 | 
			
		||||
          xmlns:tools="http://schemas.android.com/tools">
 | 
			
		||||
    <uses-permission android:name="android.permission.INTERNET" />
 | 
			
		||||
 | 
			
		||||
    <application
 | 
			
		||||
            android:allowBackup="true"
 | 
			
		||||
            android:dataExtractionRules="@xml/data_extraction_rules"
 | 
			
		||||
            android:fullBackupContent="@xml/backup_rules"
 | 
			
		||||
            android:networkSecurityConfig="@xml/network_security_config"
 | 
			
		||||
            android:icon="@mipmap/ic_launcher"
 | 
			
		||||
            android:label="@string/app_name"
 | 
			
		||||
            android:roundIcon="@mipmap/ic_launcher_round"
 | 
			
		||||
@ -17,18 +16,13 @@
 | 
			
		||||
                android:name=".MainActivity"
 | 
			
		||||
                android:exported="true"
 | 
			
		||||
                android:label="@string/app_name"
 | 
			
		||||
                android:theme="@style/Theme.NoteVault.NoActionBar">
 | 
			
		||||
                android:theme="@style/Theme.NoteVault">
 | 
			
		||||
            <intent-filter>
 | 
			
		||||
                <action android:name="android.intent.action.MAIN"/>
 | 
			
		||||
 | 
			
		||||
                <category android:name="android.intent.category.LAUNCHER"/>
 | 
			
		||||
            </intent-filter>
 | 
			
		||||
        </activity>
 | 
			
		||||
 | 
			
		||||
        <activity android:name=".ui.sheetdisplay.NoteSheetDisplayActivity"
 | 
			
		||||
                  android:theme="@style/Theme.AppCompat.NoActionBar">
 | 
			
		||||
            <!-- Intent Filter hinzufügen, wenn Activity vom Launcher oder externen Apps aufgerufen werden soll -->
 | 
			
		||||
        </activity>
 | 
			
		||||
    </application>
 | 
			
		||||
 | 
			
		||||
</manifest>
 | 
			
		||||
@ -1,90 +0,0 @@
 | 
			
		||||
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");
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -1,33 +0,0 @@
 | 
			
		||||
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;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -1,87 +0,0 @@
 | 
			
		||||
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);
 | 
			
		||||
}
 | 
			
		||||
@ -1,101 +0,0 @@
 | 
			
		||||
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;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -1,131 +0,0 @@
 | 
			
		||||
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);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -1,15 +0,0 @@
 | 
			
		||||
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;
 | 
			
		||||
}
 | 
			
		||||
@ -1,23 +0,0 @@
 | 
			
		||||
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;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -1,86 +0,0 @@
 | 
			
		||||
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);
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -1,303 +0,0 @@
 | 
			
		||||
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,20 +0,0 @@
 | 
			
		||||
package com.stormtales.notevault.data.sync;
 | 
			
		||||
 | 
			
		||||
import androidx.room.TypeConverter;
 | 
			
		||||
 | 
			
		||||
import java.time.LocalDateTime;
 | 
			
		||||
import java.time.format.DateTimeFormatter;
 | 
			
		||||
 | 
			
		||||
public class DateConverter {
 | 
			
		||||
    // Konvertiere LocalDateTime in String
 | 
			
		||||
    @TypeConverter
 | 
			
		||||
    public static String fromLocalDateTime(LocalDateTime localDateTime) {
 | 
			
		||||
        return localDateTime == null ? null : localDateTime.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Konvertiere String zurück in LocalDateTime
 | 
			
		||||
    @TypeConverter
 | 
			
		||||
    public static LocalDateTime toLocalDateTime(String dateTimeString) {
 | 
			
		||||
        return dateTimeString == null ? null : LocalDateTime.parse(dateTimeString, DateTimeFormatter.ISO_LOCAL_DATE_TIME);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -1,9 +0,0 @@
 | 
			
		||||
package com.stormtales.notevault.data.sync;
 | 
			
		||||
 | 
			
		||||
public enum SyncStatus {
 | 
			
		||||
    CREATED,
 | 
			
		||||
    DELETED,
 | 
			
		||||
    MODIFIED,
 | 
			
		||||
    REMOTE_MODIFIED,
 | 
			
		||||
    SYNCED;
 | 
			
		||||
}
 | 
			
		||||
@ -1,15 +0,0 @@
 | 
			
		||||
package com.stormtales.notevault.data.sync;
 | 
			
		||||
 | 
			
		||||
import androidx.room.TypeConverter;
 | 
			
		||||
 | 
			
		||||
public class SyncStatusConverter {
 | 
			
		||||
    @TypeConverter
 | 
			
		||||
    public static SyncStatus fromInt(int value) {
 | 
			
		||||
        return SyncStatus.values()[value];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @TypeConverter
 | 
			
		||||
    public static int toInt(SyncStatus syncStatus) {
 | 
			
		||||
        return syncStatus.ordinal();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -1,6 +0,0 @@
 | 
			
		||||
package com.stormtales.notevault.network;
 | 
			
		||||
 | 
			
		||||
public interface APICallback {
 | 
			
		||||
    void onSuccess();
 | 
			
		||||
    void onError(String error);
 | 
			
		||||
}
 | 
			
		||||
@ -1,36 +0,0 @@
 | 
			
		||||
package com.stormtales.notevault.network;
 | 
			
		||||
 | 
			
		||||
import android.content.Context;
 | 
			
		||||
import android.content.SharedPreferences;
 | 
			
		||||
import com.stormtales.notevault.network.auth.AuthInterceptor;
 | 
			
		||||
import com.stormtales.notevault.network.auth.TokenManager;
 | 
			
		||||
import okhttp3.OkHttpClient;
 | 
			
		||||
import okhttp3.logging.HttpLoggingInterceptor;
 | 
			
		||||
import retrofit2.Retrofit;
 | 
			
		||||
import retrofit2.converter.gson.GsonConverterFactory;
 | 
			
		||||
 | 
			
		||||
public class NetworkModule {
 | 
			
		||||
    private static final String BASE_URL = "https://notevault.fawkes100.de/";
 | 
			
		||||
    private static Retrofit retrofit;
 | 
			
		||||
 | 
			
		||||
    public static Retrofit getRetrofitInstance(Context context) {
 | 
			
		||||
        if (retrofit == null) {
 | 
			
		||||
            HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor();
 | 
			
		||||
            loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
 | 
			
		||||
 | 
			
		||||
            TokenManager tokenManager = new TokenManager(context);
 | 
			
		||||
            OkHttpClient client = new OkHttpClient.Builder()
 | 
			
		||||
                    .addInterceptor(new AuthInterceptor(tokenManager))
 | 
			
		||||
                    .addInterceptor(loggingInterceptor)
 | 
			
		||||
                    .build();
 | 
			
		||||
 | 
			
		||||
            retrofit = new Retrofit.Builder()
 | 
			
		||||
                    .baseUrl(BASE_URL)
 | 
			
		||||
                    .client(client)
 | 
			
		||||
                    .addConverterFactory(GsonConverterFactory.create())
 | 
			
		||||
                    .build();
 | 
			
		||||
        }
 | 
			
		||||
        return retrofit;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -1,14 +0,0 @@
 | 
			
		||||
package com.stormtales.notevault.network;
 | 
			
		||||
 | 
			
		||||
public class StatusResponse {
 | 
			
		||||
 | 
			
		||||
    private String status;
 | 
			
		||||
 | 
			
		||||
    public String getStatus() {
 | 
			
		||||
        return status;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void setStatus(String status) {
 | 
			
		||||
        this.status = status;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -1,17 +0,0 @@
 | 
			
		||||
package com.stormtales.notevault.network.auth;
 | 
			
		||||
 | 
			
		||||
import com.stormtales.notevault.network.StatusResponse;
 | 
			
		||||
import retrofit2.Call;
 | 
			
		||||
import retrofit2.http.Body;
 | 
			
		||||
import retrofit2.http.Headers;
 | 
			
		||||
import retrofit2.http.POST;
 | 
			
		||||
 | 
			
		||||
public interface AuthAPI {
 | 
			
		||||
 | 
			
		||||
    @POST("/login/")
 | 
			
		||||
    Call<LoginResponse> login(@Body LoginRequest loginRequest);
 | 
			
		||||
 | 
			
		||||
    @POST("/register/")
 | 
			
		||||
    @Headers("Content-Type: application/json")
 | 
			
		||||
    Call<StatusResponse> registration(@Body RegisterRequest registerRequest);
 | 
			
		||||
}
 | 
			
		||||
@ -1,29 +0,0 @@
 | 
			
		||||
package com.stormtales.notevault.network.auth;
 | 
			
		||||
 | 
			
		||||
import okhttp3.Interceptor;
 | 
			
		||||
import okhttp3.Request;
 | 
			
		||||
import okhttp3.Response;
 | 
			
		||||
 | 
			
		||||
import java.io.IOException;
 | 
			
		||||
 | 
			
		||||
public class AuthInterceptor implements Interceptor {
 | 
			
		||||
    private TokenManager tokenManager;
 | 
			
		||||
 | 
			
		||||
    public AuthInterceptor(TokenManager tokenManager) {
 | 
			
		||||
        this.tokenManager = tokenManager;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public Response intercept(Chain chain) throws IOException {
 | 
			
		||||
        Request originalRequest = chain.request();
 | 
			
		||||
 | 
			
		||||
        String token = tokenManager.getToken();
 | 
			
		||||
        if (token == null) {
 | 
			
		||||
            return chain.proceed(originalRequest);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        Request authenticatedRequest = originalRequest.newBuilder()
 | 
			
		||||
                .header("Authorization", "Bearer " + token).build();
 | 
			
		||||
        return chain.proceed(authenticatedRequest);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -1,87 +0,0 @@
 | 
			
		||||
package com.stormtales.notevault.network.auth;
 | 
			
		||||
 | 
			
		||||
import android.content.Context;
 | 
			
		||||
import android.text.TextUtils;
 | 
			
		||||
import com.stormtales.notevault.network.APICallback;
 | 
			
		||||
import com.stormtales.notevault.network.NetworkModule;
 | 
			
		||||
import com.stormtales.notevault.network.StatusResponse;
 | 
			
		||||
import retrofit2.Call;
 | 
			
		||||
import retrofit2.Callback;
 | 
			
		||||
import retrofit2.Response;
 | 
			
		||||
 | 
			
		||||
public class AuthService {
 | 
			
		||||
 | 
			
		||||
    private final AuthAPI authAPI;
 | 
			
		||||
    private final TokenManager tokenManager;
 | 
			
		||||
 | 
			
		||||
    public AuthService(Context context) {
 | 
			
		||||
        this.authAPI = NetworkModule.getRetrofitInstance(context).create(AuthAPI.class);
 | 
			
		||||
        this.tokenManager = new TokenManager(context);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void performLogin(String email, String password, LoginCallback callback) {
 | 
			
		||||
        LoginRequest loginRequest = new LoginRequest(email, password);
 | 
			
		||||
 | 
			
		||||
        authAPI.login(loginRequest).enqueue(new Callback<LoginResponse>() {
 | 
			
		||||
            @Override
 | 
			
		||||
            public void onResponse(Call<LoginResponse> call, Response<LoginResponse> response) {
 | 
			
		||||
                if (response.isSuccessful() && response.body() != null) {
 | 
			
		||||
                    String token = response.body().getToken();
 | 
			
		||||
                    saveToken(token);
 | 
			
		||||
                    // Erfolgsrückmeldung an den Callback senden
 | 
			
		||||
                    callback.onSuccess(response.body());
 | 
			
		||||
                } else {
 | 
			
		||||
                    // Fehlermeldung an den Callback senden
 | 
			
		||||
                    callback.onError("Login fehlgeschlagen. Überprüfe Benutzername und Passwort.");
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            @Override
 | 
			
		||||
            public void onFailure(Call<LoginResponse> call, Throwable t) {
 | 
			
		||||
                // Netzwerkfehler an den Callback senden
 | 
			
		||||
                callback.onError("Netzwerkfehler: " + t.getMessage());
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void performRegistration(String email, String username, String password, APICallback callback) {
 | 
			
		||||
        RegisterRequest registerRequest = new RegisterRequest(username, password, email);
 | 
			
		||||
        authAPI.registration(registerRequest).enqueue(new Callback<StatusResponse>() {
 | 
			
		||||
 | 
			
		||||
            @Override
 | 
			
		||||
            public void onResponse(Call<StatusResponse> call, Response<StatusResponse> response) {
 | 
			
		||||
                if(response.isSuccessful() && response.body() != null) {
 | 
			
		||||
                    callback.onSuccess();
 | 
			
		||||
                } else {
 | 
			
		||||
                    callback.onError("Registration fehlgeschlagen. Überprüfe Benutzername und Passwort.");
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            @Override
 | 
			
		||||
            public void onFailure(Call<StatusResponse> call, Throwable throwable) {
 | 
			
		||||
                callback.onError("Netzwerkfehler: " + throwable.getMessage());
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void saveToken(String token) {
 | 
			
		||||
        tokenManager.saveToken(token);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public String getToken() {
 | 
			
		||||
        return tokenManager.getToken();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void logout() {
 | 
			
		||||
        tokenManager.clearToken();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public boolean isLoggedIn() {
 | 
			
		||||
        return !TextUtils.isEmpty(tokenManager.getToken());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public interface LoginCallback {
 | 
			
		||||
        void onSuccess(LoginResponse loginResponse);
 | 
			
		||||
        void onError(String error);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -1,11 +0,0 @@
 | 
			
		||||
package com.stormtales.notevault.network.auth;
 | 
			
		||||
 | 
			
		||||
public class LoginRequest {
 | 
			
		||||
    private String email;
 | 
			
		||||
    private String password;
 | 
			
		||||
 | 
			
		||||
    public LoginRequest(String email, String password) {
 | 
			
		||||
        this.email = email;
 | 
			
		||||
        this.password = password;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -1,22 +0,0 @@
 | 
			
		||||
package com.stormtales.notevault.network.auth;
 | 
			
		||||
 | 
			
		||||
public class LoginResponse {
 | 
			
		||||
    private String token;
 | 
			
		||||
    private String username;
 | 
			
		||||
 | 
			
		||||
    public String getToken() {
 | 
			
		||||
        return token;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void setToken(String token) {
 | 
			
		||||
        this.token = token;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public String getUsername() {
 | 
			
		||||
        return username;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void setUsername(String username) {
 | 
			
		||||
        this.username = username;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -1,14 +0,0 @@
 | 
			
		||||
package com.stormtales.notevault.network.auth;
 | 
			
		||||
 | 
			
		||||
public class RegisterRequest {
 | 
			
		||||
    private String username;
 | 
			
		||||
    private String email;
 | 
			
		||||
    private String password;
 | 
			
		||||
 | 
			
		||||
    public RegisterRequest(String username, String password, String email) {
 | 
			
		||||
        this.username = username;
 | 
			
		||||
        this.password = password;
 | 
			
		||||
        this.email = email;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -1,31 +0,0 @@
 | 
			
		||||
package com.stormtales.notevault.network.auth;
 | 
			
		||||
 | 
			
		||||
import android.content.Context;
 | 
			
		||||
import android.content.SharedPreferences;
 | 
			
		||||
 | 
			
		||||
public class TokenManager {
 | 
			
		||||
    private static final String PREF_NAME = "app_preferences";
 | 
			
		||||
    private static final String KEY_TOKEN = "jwt_token";
 | 
			
		||||
 | 
			
		||||
    private SharedPreferences sharedPreferences;
 | 
			
		||||
 | 
			
		||||
    public TokenManager(Context context) {
 | 
			
		||||
        sharedPreferences = context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void saveToken(String token) {
 | 
			
		||||
        SharedPreferences.Editor editor = sharedPreferences.edit();
 | 
			
		||||
        editor.putString(KEY_TOKEN, token);
 | 
			
		||||
        editor.apply();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public String getToken() {
 | 
			
		||||
        return sharedPreferences.getString(KEY_TOKEN, null);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void clearToken() {
 | 
			
		||||
        SharedPreferences.Editor editor = sharedPreferences.edit();
 | 
			
		||||
        editor.remove(KEY_TOKEN);
 | 
			
		||||
        editor.apply();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -1,45 +0,0 @@
 | 
			
		||||
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);
 | 
			
		||||
}
 | 
			
		||||
@ -1,154 +0,0 @@
 | 
			
		||||
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);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -1,246 +0,0 @@
 | 
			
		||||
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);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -1,40 +0,0 @@
 | 
			
		||||
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;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -1,15 +0,0 @@
 | 
			
		||||
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;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -1,15 +0,0 @@
 | 
			
		||||
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,22 +0,0 @@
 | 
			
		||||
package com.stormtales.notevault.network.sync.models;
 | 
			
		||||
 | 
			
		||||
public class CreateResponse {
 | 
			
		||||
    private int localID;
 | 
			
		||||
    private String serverID;
 | 
			
		||||
 | 
			
		||||
    public int getLocalID() {
 | 
			
		||||
        return localID;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void setLocalID(int localID) {
 | 
			
		||||
        this.localID = localID;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public String getServerID() {
 | 
			
		||||
        return serverID;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void setServerID(String serverID) {
 | 
			
		||||
        this.serverID = serverID;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -1,15 +0,0 @@
 | 
			
		||||
package com.stormtales.notevault.network.sync.models;
 | 
			
		||||
 | 
			
		||||
import java.util.List;
 | 
			
		||||
 | 
			
		||||
public class FetchResponse {
 | 
			
		||||
    private List<String> serverIDs;
 | 
			
		||||
 | 
			
		||||
    public List<String> getServerIDs() {
 | 
			
		||||
        return serverIDs;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void setServerIDs(List<String> serverIDs) {
 | 
			
		||||
        this.serverIDs = serverIDs;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -1,27 +0,0 @@
 | 
			
		||||
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;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -1,31 +0,0 @@
 | 
			
		||||
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,19 +0,0 @@
 | 
			
		||||
package com.stormtales.notevault.network.sync.models;
 | 
			
		||||
 | 
			
		||||
import java.util.List;
 | 
			
		||||
 | 
			
		||||
public class SongBatchDeleteRequest {
 | 
			
		||||
    private List<String> songs;
 | 
			
		||||
 | 
			
		||||
    public SongBatchDeleteRequest(List<String> songs) {
 | 
			
		||||
        this.songs = songs;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public List<String> getSongs() {
 | 
			
		||||
        return songs;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void setSongs(List<String> songs) {
 | 
			
		||||
        this.songs = songs;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -1,20 +0,0 @@
 | 
			
		||||
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;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -1,67 +0,0 @@
 | 
			
		||||
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,69 +0,0 @@
 | 
			
		||||
package com.stormtales.notevault.network.sync.models;
 | 
			
		||||
 | 
			
		||||
import java.util.List;
 | 
			
		||||
 | 
			
		||||
public class SongModel {
 | 
			
		||||
    private String serverID;
 | 
			
		||||
    private String title;
 | 
			
		||||
    private String composer;
 | 
			
		||||
    private String genre;
 | 
			
		||||
    private int year;
 | 
			
		||||
    private List<RemotelyModifiedNoteSheetModel> note_sheets;
 | 
			
		||||
    private boolean deleted;
 | 
			
		||||
 | 
			
		||||
    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<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;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -1,19 +0,0 @@
 | 
			
		||||
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;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -1,16 +0,0 @@
 | 
			
		||||
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;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -1,82 +0,0 @@
 | 
			
		||||
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;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -1,25 +0,0 @@
 | 
			
		||||
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;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -1,31 +0,0 @@
 | 
			
		||||
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;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -1,124 +0,0 @@
 | 
			
		||||
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();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -1,92 +0,0 @@
 | 
			
		||||
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;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -1,138 +0,0 @@
 | 
			
		||||
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);
 | 
			
		||||
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -1,88 +0,0 @@
 | 
			
		||||
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;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -1,99 +0,0 @@
 | 
			
		||||
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);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -1,156 +0,0 @@
 | 
			
		||||
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();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -1,37 +0,0 @@
 | 
			
		||||
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;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -1,116 +0,0 @@
 | 
			
		||||
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,51 +0,0 @@
 | 
			
		||||
package com.stormtales.notevault.ui.sheetdisplay;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
import android.content.Context;
 | 
			
		||||
import android.net.Uri;
 | 
			
		||||
import android.view.LayoutInflater;
 | 
			
		||||
import android.view.View;
 | 
			
		||||
import android.view.ViewGroup;
 | 
			
		||||
import androidx.annotation.NonNull;
 | 
			
		||||
import androidx.recyclerview.widget.RecyclerView;
 | 
			
		||||
import com.github.chrisbanes.photoview.PhotoView;
 | 
			
		||||
import com.stormtales.notevault.R;
 | 
			
		||||
 | 
			
		||||
import java.util.List;
 | 
			
		||||
 | 
			
		||||
public class ImagePagerAdapter extends RecyclerView.Adapter<ImagePagerAdapter.ImageViewHolder> {
 | 
			
		||||
    private final List<String> imageUris;
 | 
			
		||||
    private final Context context;
 | 
			
		||||
 | 
			
		||||
    public ImagePagerAdapter(Context context, List<String> imageUris) {
 | 
			
		||||
        this.context = context;
 | 
			
		||||
        this.imageUris = imageUris;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @NonNull
 | 
			
		||||
    @Override
 | 
			
		||||
    public ImageViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
 | 
			
		||||
        View view = LayoutInflater.from(context).inflate(R.layout.item_image, parent, false);
 | 
			
		||||
        return new ImageViewHolder(view);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void onBindViewHolder(@NonNull ImageViewHolder holder, int position) {
 | 
			
		||||
        Uri uri = Uri.parse(imageUris.get(position));
 | 
			
		||||
        holder.photoView.setImageURI(uri);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public int getItemCount() {
 | 
			
		||||
        return imageUris.size();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    static class ImageViewHolder extends RecyclerView.ViewHolder {
 | 
			
		||||
        PhotoView photoView;
 | 
			
		||||
 | 
			
		||||
        ImageViewHolder(View itemView) {
 | 
			
		||||
            super(itemView);
 | 
			
		||||
            photoView = itemView.findViewById(R.id.photoView);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -1,40 +0,0 @@
 | 
			
		||||
package com.stormtales.notevault.ui.sheetdisplay;
 | 
			
		||||
 | 
			
		||||
import android.content.Intent;
 | 
			
		||||
import android.os.Bundle;
 | 
			
		||||
import androidx.appcompat.app.AppCompatActivity;
 | 
			
		||||
import androidx.viewpager2.widget.ViewPager2;
 | 
			
		||||
import com.google.android.material.tabs.TabLayout;
 | 
			
		||||
import com.google.android.material.tabs.TabLayoutMediator;
 | 
			
		||||
import com.stormtales.notevault.R;
 | 
			
		||||
 | 
			
		||||
import java.util.Arrays;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
 | 
			
		||||
public class NoteSheetDisplayActivity extends AppCompatActivity {
 | 
			
		||||
    @Override
 | 
			
		||||
    protected void onCreate(Bundle savedInstanceState) {
 | 
			
		||||
        super.onCreate(savedInstanceState);
 | 
			
		||||
        setContentView(R.layout.activity_note_sheet_viewer); // Erstelle eine Layout-Datei
 | 
			
		||||
 | 
			
		||||
        ViewPager2 imageView = findViewById(R.id.viewPager); // Angenommen, du hast ein ImageView
 | 
			
		||||
        TabLayout tabLayout = findViewById(R.id.tabLayout);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        // Die URI aus dem Intent erhalten
 | 
			
		||||
        Intent intent = getIntent();
 | 
			
		||||
        String[] imageUris = intent.getStringArrayExtra("imageUris");
 | 
			
		||||
        if (imageUris != null) {
 | 
			
		||||
            List<String> uriList = Arrays.asList(imageUris);
 | 
			
		||||
            ImagePagerAdapter adapter = new ImagePagerAdapter(this, uriList);
 | 
			
		||||
            imageView.setAdapter(adapter); // Setze die URI in das ImageView
 | 
			
		||||
 | 
			
		||||
            new TabLayoutMediator(tabLayout, imageView,
 | 
			
		||||
                    (tab, position) -> tab.setText("Sheet " + (position + 1)) // Hier kannst du auch andere Labels verwenden
 | 
			
		||||
            ).attach();
 | 
			
		||||
        } else {
 | 
			
		||||
            throw new NullPointerException();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -1,35 +0,0 @@
 | 
			
		||||
package com.stormtales.notevault.ui.slideshow;
 | 
			
		||||
 | 
			
		||||
import android.os.Bundle;
 | 
			
		||||
import android.view.LayoutInflater;
 | 
			
		||||
import android.view.View;
 | 
			
		||||
import android.view.ViewGroup;
 | 
			
		||||
import android.widget.TextView;
 | 
			
		||||
import androidx.annotation.NonNull;
 | 
			
		||||
import androidx.fragment.app.Fragment;
 | 
			
		||||
import androidx.lifecycle.ViewModelProvider;
 | 
			
		||||
import com.stormtales.notevault.databinding.FragmentSlideshowBinding;
 | 
			
		||||
 | 
			
		||||
public class SlideshowFragment extends Fragment {
 | 
			
		||||
 | 
			
		||||
    private FragmentSlideshowBinding binding;
 | 
			
		||||
 | 
			
		||||
    public View onCreateView(@NonNull LayoutInflater inflater,
 | 
			
		||||
                             ViewGroup container, Bundle savedInstanceState) {
 | 
			
		||||
        SlideshowViewModel slideshowViewModel =
 | 
			
		||||
                new ViewModelProvider(this).get(SlideshowViewModel.class);
 | 
			
		||||
 | 
			
		||||
        binding = FragmentSlideshowBinding.inflate(inflater, container, false);
 | 
			
		||||
        View root = binding.getRoot();
 | 
			
		||||
 | 
			
		||||
        final TextView textView = binding.textSlideshow;
 | 
			
		||||
        slideshowViewModel.getText().observe(getViewLifecycleOwner(), textView::setText);
 | 
			
		||||
        return root;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void onDestroyView() {
 | 
			
		||||
        super.onDestroyView();
 | 
			
		||||
        binding = null;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -1,19 +0,0 @@
 | 
			
		||||
package com.stormtales.notevault.ui.slideshow;
 | 
			
		||||
 | 
			
		||||
import androidx.lifecycle.LiveData;
 | 
			
		||||
import androidx.lifecycle.MutableLiveData;
 | 
			
		||||
import androidx.lifecycle.ViewModel;
 | 
			
		||||
 | 
			
		||||
public class SlideshowViewModel extends ViewModel {
 | 
			
		||||
 | 
			
		||||
    private final MutableLiveData<String> mText;
 | 
			
		||||
 | 
			
		||||
    public SlideshowViewModel() {
 | 
			
		||||
        mText = new MutableLiveData<>();
 | 
			
		||||
        mText.setValue("This is slideshow fragment");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public LiveData<String> getText() {
 | 
			
		||||
        return mText;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -1,193 +0,0 @@
 | 
			
		||||
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;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -1,117 +0,0 @@
 | 
			
		||||
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());
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -1,33 +0,0 @@
 | 
			
		||||
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,21 +0,0 @@
 | 
			
		||||
package com.stormtales.notevault.utils;
 | 
			
		||||
 | 
			
		||||
import android.net.Uri;
 | 
			
		||||
 | 
			
		||||
public class UriTimestamp {
 | 
			
		||||
    private final Uri uri;
 | 
			
		||||
    private final long timestamp;
 | 
			
		||||
 | 
			
		||||
    public UriTimestamp(Uri uri, long timestamp) {
 | 
			
		||||
        this.uri = uri;
 | 
			
		||||
        this.timestamp = timestamp;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public Uri getUri() {
 | 
			
		||||
        return uri;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public long getTimestamp() {
 | 
			
		||||
        return timestamp;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,47 @@
 | 
			
		||||
package come.stormborntales.notevault
 | 
			
		||||
 | 
			
		||||
import android.os.Bundle
 | 
			
		||||
import androidx.activity.ComponentActivity
 | 
			
		||||
import androidx.activity.compose.setContent
 | 
			
		||||
import androidx.activity.enableEdgeToEdge
 | 
			
		||||
import androidx.compose.foundation.layout.fillMaxSize
 | 
			
		||||
import androidx.compose.foundation.layout.padding
 | 
			
		||||
import androidx.compose.material3.Scaffold
 | 
			
		||||
import androidx.compose.material3.Text
 | 
			
		||||
import androidx.compose.runtime.Composable
 | 
			
		||||
import androidx.compose.ui.Modifier
 | 
			
		||||
import androidx.compose.ui.tooling.preview.Preview
 | 
			
		||||
import come.stormborntales.notevault.ui.theme.NoteVaultTheme
 | 
			
		||||
 | 
			
		||||
class MainActivity : ComponentActivity() {
 | 
			
		||||
    override fun onCreate(savedInstanceState: Bundle?) {
 | 
			
		||||
        super.onCreate(savedInstanceState)
 | 
			
		||||
        enableEdgeToEdge()
 | 
			
		||||
        setContent {
 | 
			
		||||
            NoteVaultTheme {
 | 
			
		||||
                Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
 | 
			
		||||
                    Greeting(
 | 
			
		||||
                        name = "Android",
 | 
			
		||||
                        modifier = Modifier.padding(innerPadding)
 | 
			
		||||
                    )
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@Composable
 | 
			
		||||
fun Greeting(name: String, modifier: Modifier = Modifier) {
 | 
			
		||||
    Text(
 | 
			
		||||
        text = "Hello $name!",
 | 
			
		||||
        modifier = modifier
 | 
			
		||||
    )
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@Preview(showBackground = true)
 | 
			
		||||
@Composable
 | 
			
		||||
fun GreetingPreview() {
 | 
			
		||||
    NoteVaultTheme {
 | 
			
		||||
        Greeting("Android")
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,11 @@
 | 
			
		||||
package come.stormborntales.notevault.ui.theme
 | 
			
		||||
 | 
			
		||||
import androidx.compose.ui.graphics.Color
 | 
			
		||||
 | 
			
		||||
val Purple80 = Color(0xFFD0BCFF)
 | 
			
		||||
val PurpleGrey80 = Color(0xFFCCC2DC)
 | 
			
		||||
val Pink80 = Color(0xFFEFB8C8)
 | 
			
		||||
 | 
			
		||||
val Purple40 = Color(0xFF6650a4)
 | 
			
		||||
val PurpleGrey40 = Color(0xFF625b71)
 | 
			
		||||
val Pink40 = Color(0xFF7D5260)
 | 
			
		||||
@ -0,0 +1,58 @@
 | 
			
		||||
package come.stormborntales.notevault.ui.theme
 | 
			
		||||
 | 
			
		||||
import android.app.Activity
 | 
			
		||||
import android.os.Build
 | 
			
		||||
import androidx.compose.foundation.isSystemInDarkTheme
 | 
			
		||||
import androidx.compose.material3.MaterialTheme
 | 
			
		||||
import androidx.compose.material3.darkColorScheme
 | 
			
		||||
import androidx.compose.material3.dynamicDarkColorScheme
 | 
			
		||||
import androidx.compose.material3.dynamicLightColorScheme
 | 
			
		||||
import androidx.compose.material3.lightColorScheme
 | 
			
		||||
import androidx.compose.runtime.Composable
 | 
			
		||||
import androidx.compose.ui.platform.LocalContext
 | 
			
		||||
 | 
			
		||||
private val DarkColorScheme = darkColorScheme(
 | 
			
		||||
    primary = Purple80,
 | 
			
		||||
    secondary = PurpleGrey80,
 | 
			
		||||
    tertiary = Pink80
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
private val LightColorScheme = lightColorScheme(
 | 
			
		||||
    primary = Purple40,
 | 
			
		||||
    secondary = PurpleGrey40,
 | 
			
		||||
    tertiary = Pink40
 | 
			
		||||
 | 
			
		||||
    /* Other default colors to override
 | 
			
		||||
    background = Color(0xFFFFFBFE),
 | 
			
		||||
    surface = Color(0xFFFFFBFE),
 | 
			
		||||
    onPrimary = Color.White,
 | 
			
		||||
    onSecondary = Color.White,
 | 
			
		||||
    onTertiary = Color.White,
 | 
			
		||||
    onBackground = Color(0xFF1C1B1F),
 | 
			
		||||
    onSurface = Color(0xFF1C1B1F),
 | 
			
		||||
    */
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
@Composable
 | 
			
		||||
fun NoteVaultTheme(
 | 
			
		||||
    darkTheme: Boolean = isSystemInDarkTheme(),
 | 
			
		||||
    // Dynamic color is available on Android 12+
 | 
			
		||||
    dynamicColor: Boolean = true,
 | 
			
		||||
    content: @Composable () -> Unit
 | 
			
		||||
) {
 | 
			
		||||
    val colorScheme = when {
 | 
			
		||||
        dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
 | 
			
		||||
            val context = LocalContext.current
 | 
			
		||||
            if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        darkTheme -> DarkColorScheme
 | 
			
		||||
        else -> LightColorScheme
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    MaterialTheme(
 | 
			
		||||
        colorScheme = colorScheme,
 | 
			
		||||
        typography = Typography,
 | 
			
		||||
        content = content
 | 
			
		||||
    )
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,34 @@
 | 
			
		||||
package come.stormborntales.notevault.ui.theme
 | 
			
		||||
 | 
			
		||||
import androidx.compose.material3.Typography
 | 
			
		||||
import androidx.compose.ui.text.TextStyle
 | 
			
		||||
import androidx.compose.ui.text.font.FontFamily
 | 
			
		||||
import androidx.compose.ui.text.font.FontWeight
 | 
			
		||||
import androidx.compose.ui.unit.sp
 | 
			
		||||
 | 
			
		||||
// Set of Material typography styles to start with
 | 
			
		||||
val Typography = Typography(
 | 
			
		||||
    bodyLarge = TextStyle(
 | 
			
		||||
        fontFamily = FontFamily.Default,
 | 
			
		||||
        fontWeight = FontWeight.Normal,
 | 
			
		||||
        fontSize = 16.sp,
 | 
			
		||||
        lineHeight = 24.sp,
 | 
			
		||||
        letterSpacing = 0.5.sp
 | 
			
		||||
    )
 | 
			
		||||
    /* Other default text styles to override
 | 
			
		||||
    titleLarge = TextStyle(
 | 
			
		||||
        fontFamily = FontFamily.Default,
 | 
			
		||||
        fontWeight = FontWeight.Normal,
 | 
			
		||||
        fontSize = 22.sp,
 | 
			
		||||
        lineHeight = 28.sp,
 | 
			
		||||
        letterSpacing = 0.sp
 | 
			
		||||
    ),
 | 
			
		||||
    labelSmall = TextStyle(
 | 
			
		||||
        fontFamily = FontFamily.Default,
 | 
			
		||||
        fontWeight = FontWeight.Medium,
 | 
			
		||||
        fontSize = 11.sp,
 | 
			
		||||
        lineHeight = 16.sp,
 | 
			
		||||
        letterSpacing = 0.5.sp
 | 
			
		||||
    )
 | 
			
		||||
    */
 | 
			
		||||
)
 | 
			
		||||
@ -1,9 +0,0 @@
 | 
			
		||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
 | 
			
		||||
    android:width="24dp"
 | 
			
		||||
    android:height="24dp"
 | 
			
		||||
    android:viewportWidth="960"
 | 
			
		||||
    android:viewportHeight="960">
 | 
			
		||||
  <path
 | 
			
		||||
      android:pathData="M280,840q-33,0 -56.5,-23.5T200,760v-520h-40v-80h200v-40h240v40h200v80h-40v520q0,33 -23.5,56.5T680,840L280,840ZM680,240L280,240v520h400v-520ZM360,680h80v-360h-80v360ZM520,680h80v-360h-80v360ZM280,240v520,-520Z"
 | 
			
		||||
      android:fillColor="#e8eaed"/>
 | 
			
		||||
</vector>
 | 
			
		||||
@ -1,6 +0,0 @@
 | 
			
		||||
<shape xmlns:android="http://schemas.android.com/apk/res/android">
 | 
			
		||||
    <solid android:color="@android:color/white" />
 | 
			
		||||
    <corners android:radius="16dp" />
 | 
			
		||||
    <padding android:left="16dp" android:top="16dp" android:right="16dp" android:bottom="16dp" />
 | 
			
		||||
    <elevation android:height="4dp" />
 | 
			
		||||
</shape>
 | 
			
		||||
@ -1,9 +0,0 @@
 | 
			
		||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
 | 
			
		||||
    android:width="24dp"
 | 
			
		||||
    android:height="24dp"
 | 
			
		||||
    android:viewportWidth="960"
 | 
			
		||||
    android:viewportHeight="960">
 | 
			
		||||
  <path
 | 
			
		||||
      android:pathData="M200,760h57l391,-391 -57,-57 -391,391v57ZM120,840v-170l528,-527q12,-11 26.5,-17t30.5,-6q16,0 31,6t26,18l55,56q12,11 17.5,26t5.5,30q0,16 -5.5,30.5T817,313L290,840L120,840ZM760,256 L704,200 760,256ZM619,341 L591,312 648,369 619,341Z"
 | 
			
		||||
      android:fillColor="#e8eaed"/>
 | 
			
		||||
</vector>
 | 
			
		||||
@ -1,12 +0,0 @@
 | 
			
		||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
 | 
			
		||||
        android:width="24dp"
 | 
			
		||||
        android:height="24dp"
 | 
			
		||||
        android:viewportWidth="24.0"
 | 
			
		||||
        android:viewportHeight="24.0">
 | 
			
		||||
    <path
 | 
			
		||||
            android:fillColor="#FF000000"
 | 
			
		||||
            android:pathData="M12,12m-3.2,0a3.2,3.2 0,1 1,6.4 0a3.2,3.2 0,1 1,-6.4 0"/>
 | 
			
		||||
    <path
 | 
			
		||||
            android:fillColor="#FF000000"
 | 
			
		||||
            android:pathData="M9,2L7.17,4H4c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2V6c0,-1.1 -0.9,-2 -2,-2h-3.17L15,2H9zm3,15c-2.76,0 -5,-2.24 -5,-5s2.24,-5 5,-5 5,2.24 5,5 -2.24,5 -5,5z"/>
 | 
			
		||||
</vector>
 | 
			
		||||
@ -1,9 +0,0 @@
 | 
			
		||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
 | 
			
		||||
        android:width="24dp"
 | 
			
		||||
        android:height="24dp"
 | 
			
		||||
        android:viewportWidth="24.0"
 | 
			
		||||
        android:viewportHeight="24.0">
 | 
			
		||||
    <path
 | 
			
		||||
            android:fillColor="#FF000000"
 | 
			
		||||
            android:pathData="M22,16V4c0,-1.1 -0.9,-2 -2,-2H8c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2zm-11,-4l2.03,2.71L16,11l4,5H8l3,-4zM2,6v14c0,1.1 0.9,2 2,2h14v-2H4V6H2z"/>
 | 
			
		||||
</vector>
 | 
			
		||||
@ -1,9 +0,0 @@
 | 
			
		||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
 | 
			
		||||
        android:width="24dp"
 | 
			
		||||
        android:height="24dp"
 | 
			
		||||
        android:viewportWidth="24.0"
 | 
			
		||||
        android:viewportHeight="24.0">
 | 
			
		||||
    <path
 | 
			
		||||
            android:fillColor="#FF000000"
 | 
			
		||||
            android:pathData="M4,6H2v14c0,1.1 0.9,2 2,2h14v-2H4V6zm16,-4H8c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2V4c0,-1.1 -0.9,-2 -2,-2zm-8,12.5v-9l6,4.5 -6,4.5z"/>
 | 
			
		||||
</vector>
 | 
			
		||||
@ -1,9 +0,0 @@
 | 
			
		||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
 | 
			
		||||
    android:width="24dp"
 | 
			
		||||
    android:height="24dp"
 | 
			
		||||
    android:viewportWidth="960"
 | 
			
		||||
    android:viewportHeight="960">
 | 
			
		||||
  <path
 | 
			
		||||
      android:pathData="M480,840v-80h280v-560L480,200v-80h280q33,0 56.5,23.5T840,200v560q0,33 -23.5,56.5T760,840L480,840ZM400,680 L345,622 447,520L120,520v-80h327L345,338l55,-58 200,200 -200,200Z"
 | 
			
		||||
      android:fillColor="#e8eaed"/>
 | 
			
		||||
</vector>
 | 
			
		||||
@ -1,9 +0,0 @@
 | 
			
		||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
 | 
			
		||||
    android:width="24dp"
 | 
			
		||||
    android:height="24dp"
 | 
			
		||||
    android:viewportWidth="960"
 | 
			
		||||
    android:viewportHeight="960">
 | 
			
		||||
  <path
 | 
			
		||||
      android:pathData="M200,840q-33,0 -56.5,-23.5T120,760v-560q0,-33 23.5,-56.5T200,120h280v80L200,200v560h280v80L200,840ZM640,680 L585,622 687,520L360,520v-80h327L585,338l55,-58 200,200 -200,200Z"
 | 
			
		||||
      android:fillColor="#e8eaed"/>
 | 
			
		||||
</vector>
 | 
			
		||||
@ -1,9 +0,0 @@
 | 
			
		||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
 | 
			
		||||
       android:shape="rectangle">
 | 
			
		||||
    <gradient
 | 
			
		||||
            android:angle="135"
 | 
			
		||||
            android:centerColor="#009688"
 | 
			
		||||
            android:endColor="#00695C"
 | 
			
		||||
            android:startColor="#4DB6AC"
 | 
			
		||||
            android:type="linear"/>
 | 
			
		||||
</shape>
 | 
			
		||||
@ -1,26 +0,0 @@
 | 
			
		||||
<?xml version="1.0" encoding="utf-8"?>
 | 
			
		||||
<androidx.drawerlayout.widget.DrawerLayout
 | 
			
		||||
        xmlns:android="http://schemas.android.com/apk/res/android"
 | 
			
		||||
        xmlns:app="http://schemas.android.com/apk/res-auto"
 | 
			
		||||
        xmlns:tools="http://schemas.android.com/tools"
 | 
			
		||||
        android:id="@+id/drawer_layout"
 | 
			
		||||
        android:layout_width="match_parent"
 | 
			
		||||
        android:layout_height="match_parent"
 | 
			
		||||
        android:fitsSystemWindows="true"
 | 
			
		||||
        tools:openDrawer="start">
 | 
			
		||||
 | 
			
		||||
    <include
 | 
			
		||||
            android:id="@+id/app_bar_main"
 | 
			
		||||
            layout="@layout/app_bar_main"
 | 
			
		||||
            android:layout_width="match_parent"
 | 
			
		||||
            android:layout_height="match_parent"/>
 | 
			
		||||
 | 
			
		||||
    <com.google.android.material.navigation.NavigationView
 | 
			
		||||
            android:id="@+id/nav_view"
 | 
			
		||||
            android:layout_width="wrap_content"
 | 
			
		||||
            android:layout_height="match_parent"
 | 
			
		||||
            android:layout_gravity="start"
 | 
			
		||||
            android:fitsSystemWindows="true"
 | 
			
		||||
            app:headerLayout="@layout/nav_header_main"
 | 
			
		||||
            app:menu="@menu/activity_main_drawer"/>
 | 
			
		||||
</androidx.drawerlayout.widget.DrawerLayout>
 | 
			
		||||
@ -1,17 +0,0 @@
 | 
			
		||||
<?xml version="1.0" encoding="utf-8"?>
 | 
			
		||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
 | 
			
		||||
                android:layout_width="match_parent"
 | 
			
		||||
                android:layout_height="match_parent">
 | 
			
		||||
    <!-- TabLayout für die Swipe-Indikatoren -->
 | 
			
		||||
    <com.google.android.material.tabs.TabLayout
 | 
			
		||||
            android:id="@+id/tabLayout"
 | 
			
		||||
            android:layout_width="match_parent"
 | 
			
		||||
            android:layout_height="wrap_content"
 | 
			
		||||
            android:layout_alignParentTop="true"
 | 
			
		||||
            android:background="?android:attr/windowBackground"/>
 | 
			
		||||
 | 
			
		||||
    <androidx.viewpager2.widget.ViewPager2
 | 
			
		||||
            android:id="@+id/viewPager"
 | 
			
		||||
            android:layout_width="match_parent"
 | 
			
		||||
            android:layout_height="match_parent" />
 | 
			
		||||
</RelativeLayout>
 | 
			
		||||
@ -1,26 +0,0 @@
 | 
			
		||||
<?xml version="1.0" encoding="utf-8"?>
 | 
			
		||||
<androidx.coordinatorlayout.widget.CoordinatorLayout
 | 
			
		||||
        xmlns:android="http://schemas.android.com/apk/res/android"
 | 
			
		||||
        xmlns:app="http://schemas.android.com/apk/res-auto"
 | 
			
		||||
        xmlns:tools="http://schemas.android.com/tools"
 | 
			
		||||
        android:layout_width="match_parent"
 | 
			
		||||
        android:layout_height="match_parent"
 | 
			
		||||
        tools:context=".MainActivity">
 | 
			
		||||
 | 
			
		||||
    <com.google.android.material.appbar.AppBarLayout
 | 
			
		||||
            android:layout_height="wrap_content"
 | 
			
		||||
            android:layout_width="match_parent"
 | 
			
		||||
            android:theme="@style/Theme.NoteVault.AppBarOverlay">
 | 
			
		||||
 | 
			
		||||
        <androidx.appcompat.widget.Toolbar
 | 
			
		||||
                android:id="@+id/toolbar"
 | 
			
		||||
                android:layout_width="match_parent"
 | 
			
		||||
                android:background="?attr/colorPrimary"
 | 
			
		||||
                app:popupTheme="@style/Theme.NoteVault.PopupOverlay"
 | 
			
		||||
                android:layout_height="?attr/actionBarSize"/>
 | 
			
		||||
 | 
			
		||||
    </com.google.android.material.appbar.AppBarLayout>
 | 
			
		||||
 | 
			
		||||
    <include layout="@layout/content_main"/>
 | 
			
		||||
 | 
			
		||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
 | 
			
		||||
@ -1,22 +0,0 @@
 | 
			
		||||
<?xml version="1.0" encoding="utf-8"?>
 | 
			
		||||
<androidx.constraintlayout.widget.ConstraintLayout
 | 
			
		||||
        xmlns:android="http://schemas.android.com/apk/res/android"
 | 
			
		||||
        xmlns:app="http://schemas.android.com/apk/res-auto"
 | 
			
		||||
        xmlns:tools="http://schemas.android.com/tools"
 | 
			
		||||
        android:layout_width="match_parent"
 | 
			
		||||
        android:layout_height="match_parent"
 | 
			
		||||
        app:layout_behavior="@string/appbar_scrolling_view_behavior"
 | 
			
		||||
        tools:showIn="@layout/app_bar_main">
 | 
			
		||||
 | 
			
		||||
    <fragment
 | 
			
		||||
            android:id="@+id/nav_host_fragment_content_main"
 | 
			
		||||
            android:name="androidx.navigation.fragment.NavHostFragment"
 | 
			
		||||
            android:layout_width="match_parent"
 | 
			
		||||
            android:layout_height="match_parent"
 | 
			
		||||
            app:layout_constraintLeft_toLeftOf="parent"
 | 
			
		||||
            app:layout_constraintRight_toRightOf="parent"
 | 
			
		||||
            app:layout_constraintTop_toTopOf="parent"
 | 
			
		||||
            app:defaultNavHost="true"
 | 
			
		||||
            app:navGraph="@navigation/mobile_navigation"
 | 
			
		||||
    />
 | 
			
		||||
</androidx.constraintlayout.widget.ConstraintLayout>
 | 
			
		||||
@ -1,194 +0,0 @@
 | 
			
		||||
<?xml version="1.0" encoding="utf-8"?>
 | 
			
		||||
<androidx.constraintlayout.widget.ConstraintLayout
 | 
			
		||||
        xmlns:android="http://schemas.android.com/apk/res/android"
 | 
			
		||||
        xmlns:app="http://schemas.android.com/apk/res-auto"
 | 
			
		||||
        xmlns:tools="http://schemas.android.com/tools"
 | 
			
		||||
        android:layout_width="match_parent"
 | 
			
		||||
        android:layout_height="match_parent"
 | 
			
		||||
        tools:context=".ui.gallery.GalleryFragment">
 | 
			
		||||
 | 
			
		||||
    <LinearLayout
 | 
			
		||||
            android:orientation="vertical"
 | 
			
		||||
            android:layout_width="match_parent"
 | 
			
		||||
            android:layout_height="match_parent"
 | 
			
		||||
            android:padding="16dp">
 | 
			
		||||
 | 
			
		||||
        <!-- Created Songs Sync Section -->
 | 
			
		||||
        <androidx.constraintlayout.widget.ConstraintLayout
 | 
			
		||||
                android:layout_width="match_parent"
 | 
			
		||||
                android:layout_height="wrap_content"
 | 
			
		||||
                android:layout_marginBottom="16dp">
 | 
			
		||||
 | 
			
		||||
            <!-- Label -->
 | 
			
		||||
            <TextView
 | 
			
		||||
                    android:id="@+id/sync_created_songs"
 | 
			
		||||
                    android:layout_width="wrap_content"
 | 
			
		||||
                    android:layout_height="wrap_content"
 | 
			
		||||
                    android:text="Sync Client Side Created Songs"
 | 
			
		||||
                    android:textSize="16sp"
 | 
			
		||||
                    android:textColor="@android:color/black"
 | 
			
		||||
                    app:layout_constraintStart_toStartOf="parent"
 | 
			
		||||
                    app:layout_constraintTop_toTopOf="parent"
 | 
			
		||||
                    app:layout_constraintBottom_toBottomOf="parent"
 | 
			
		||||
                    android:layout_marginEnd="8dp" />
 | 
			
		||||
 | 
			
		||||
            <!-- Progress Bar -->
 | 
			
		||||
            <ProgressBar
 | 
			
		||||
                    android:id="@+id/progress_sync_created_songs"
 | 
			
		||||
                    style="?android:attr/progressBarStyleHorizontal"
 | 
			
		||||
                    android:layout_width="0dp"
 | 
			
		||||
                    android:layout_height="44dp"
 | 
			
		||||
                    android:indeterminate="true"
 | 
			
		||||
                    android:layout_marginEnd="16dp"
 | 
			
		||||
                    android:layout_marginStart="16dp"
 | 
			
		||||
                    app:layout_constraintStart_toEndOf="@id/sync_created_songs"
 | 
			
		||||
                    app:layout_constraintEnd_toStartOf="@id/sync_created_btn"
 | 
			
		||||
                    app:layout_constraintTop_toTopOf="@id/sync_created_songs"
 | 
			
		||||
                    app:layout_constraintBottom_toBottomOf="@id/sync_created_songs" />
 | 
			
		||||
 | 
			
		||||
            <!-- Sync Button -->
 | 
			
		||||
            <Button
 | 
			
		||||
                    android:id="@+id/sync_created_btn"
 | 
			
		||||
                    android:layout_width="wrap_content"
 | 
			
		||||
                    android:layout_height="wrap_content"
 | 
			
		||||
                    android:text="Sync"
 | 
			
		||||
                    app:layout_constraintEnd_toEndOf="parent"
 | 
			
		||||
                    app:layout_constraintTop_toTopOf="@id/sync_created_songs"
 | 
			
		||||
                    app:layout_constraintBottom_toBottomOf="@id/sync_created_songs"
 | 
			
		||||
                    android:layout_marginEnd="16dp" />
 | 
			
		||||
 | 
			
		||||
        </androidx.constraintlayout.widget.ConstraintLayout>
 | 
			
		||||
 | 
			
		||||
        <!-- Modified Songs Sync Section -->
 | 
			
		||||
        <androidx.constraintlayout.widget.ConstraintLayout
 | 
			
		||||
                android:layout_width="match_parent"
 | 
			
		||||
                android:layout_height="wrap_content">
 | 
			
		||||
 | 
			
		||||
            <!-- Label -->
 | 
			
		||||
            <TextView
 | 
			
		||||
                    android:id="@+id/sync_modified_songs"
 | 
			
		||||
                    android:layout_width="wrap_content"
 | 
			
		||||
                    android:layout_height="wrap_content"
 | 
			
		||||
                    android:text="Sync Client Side Modified Songs"
 | 
			
		||||
                    android:textSize="16sp"
 | 
			
		||||
                    android:textColor="@android:color/black"
 | 
			
		||||
                    app:layout_constraintStart_toStartOf="parent"
 | 
			
		||||
                    app:layout_constraintTop_toTopOf="parent"
 | 
			
		||||
                    app:layout_constraintBottom_toBottomOf="parent"
 | 
			
		||||
                    android:layout_marginEnd="8dp" />
 | 
			
		||||
 | 
			
		||||
            <!-- Progress Bar -->
 | 
			
		||||
            <ProgressBar
 | 
			
		||||
                    android:id="@+id/progress_sync_modified_songs"
 | 
			
		||||
                    style="?android:attr/progressBarStyleHorizontal"
 | 
			
		||||
                    android:layout_width="0dp"
 | 
			
		||||
                    android:layout_height="44dp"
 | 
			
		||||
                    android:indeterminate="true"
 | 
			
		||||
                    android:layout_marginEnd="16dp"
 | 
			
		||||
                    android:layout_marginStart="16dp"
 | 
			
		||||
                    app:layout_constraintStart_toEndOf="@id/sync_modified_songs"
 | 
			
		||||
                    app:layout_constraintEnd_toStartOf="@id/sync_modified_btn"
 | 
			
		||||
                    app:layout_constraintTop_toTopOf="@id/sync_modified_songs"
 | 
			
		||||
                    app:layout_constraintBottom_toBottomOf="@id/sync_modified_songs" />
 | 
			
		||||
 | 
			
		||||
            <!-- Sync Button -->
 | 
			
		||||
            <Button
 | 
			
		||||
                    android:id="@+id/sync_modified_btn"
 | 
			
		||||
                    android:layout_width="wrap_content"
 | 
			
		||||
                    android:layout_height="wrap_content"
 | 
			
		||||
                    android:text="Sync"
 | 
			
		||||
                    app:layout_constraintEnd_toEndOf="parent"
 | 
			
		||||
                    app:layout_constraintTop_toTopOf="@id/sync_modified_songs"
 | 
			
		||||
                    app:layout_constraintBottom_toBottomOf="@id/sync_modified_songs"
 | 
			
		||||
                    android:layout_marginEnd="16dp" />
 | 
			
		||||
 | 
			
		||||
        </androidx.constraintlayout.widget.ConstraintLayout>
 | 
			
		||||
        <!-- Deleted Songs Sync Section -->
 | 
			
		||||
        <androidx.constraintlayout.widget.ConstraintLayout
 | 
			
		||||
                android:layout_width="match_parent"
 | 
			
		||||
                android:layout_height="wrap_content">
 | 
			
		||||
 | 
			
		||||
            <!-- Label -->
 | 
			
		||||
            <TextView
 | 
			
		||||
                    android:id="@+id/sync_deleted_songs"
 | 
			
		||||
                    android:layout_width="wrap_content"
 | 
			
		||||
                    android:layout_height="wrap_content"
 | 
			
		||||
                    android:text="Sync Client Side Deleted Songs"
 | 
			
		||||
                    android:textSize="16sp"
 | 
			
		||||
                    android:textColor="@android:color/black"
 | 
			
		||||
                    app:layout_constraintStart_toStartOf="parent"
 | 
			
		||||
                    app:layout_constraintTop_toTopOf="parent"
 | 
			
		||||
                    app:layout_constraintBottom_toBottomOf="parent"
 | 
			
		||||
                    android:layout_marginEnd="8dp" />
 | 
			
		||||
 | 
			
		||||
            <!-- Progress Bar -->
 | 
			
		||||
            <ProgressBar
 | 
			
		||||
                    android:id="@+id/progress_sync_deleted_songs"
 | 
			
		||||
                    style="?android:attr/progressBarStyleHorizontal"
 | 
			
		||||
                    android:layout_width="0dp"
 | 
			
		||||
                    android:layout_height="44dp"
 | 
			
		||||
                    android:indeterminate="true"
 | 
			
		||||
                    android:layout_marginEnd="16dp"
 | 
			
		||||
                    android:layout_marginStart="16dp"
 | 
			
		||||
                    app:layout_constraintStart_toEndOf="@id/sync_deleted_songs"
 | 
			
		||||
                    app:layout_constraintEnd_toStartOf="@id/sync_deleted_btn"
 | 
			
		||||
                    app:layout_constraintTop_toTopOf="@id/sync_deleted_songs"
 | 
			
		||||
                    app:layout_constraintBottom_toBottomOf="@id/sync_deleted_songs" />
 | 
			
		||||
 | 
			
		||||
            <!-- Sync Button -->
 | 
			
		||||
            <Button
 | 
			
		||||
                    android:id="@+id/sync_deleted_btn"
 | 
			
		||||
                    android:layout_width="wrap_content"
 | 
			
		||||
                    android:layout_height="wrap_content"
 | 
			
		||||
                    android:text="Sync"
 | 
			
		||||
                    app:layout_constraintEnd_toEndOf="parent"
 | 
			
		||||
                    app:layout_constraintTop_toTopOf="@id/sync_deleted_songs"
 | 
			
		||||
                    app:layout_constraintBottom_toBottomOf="@id/sync_deleted_songs"
 | 
			
		||||
                    android:layout_marginEnd="16dp" />
 | 
			
		||||
 | 
			
		||||
        </androidx.constraintlayout.widget.ConstraintLayout>
 | 
			
		||||
        <androidx.constraintlayout.widget.ConstraintLayout
 | 
			
		||||
                android:layout_width="match_parent"
 | 
			
		||||
                android:layout_height="wrap_content">
 | 
			
		||||
 | 
			
		||||
            <!-- Label -->
 | 
			
		||||
            <TextView
 | 
			
		||||
                    android:id="@+id/fetch_songs"
 | 
			
		||||
                    android:layout_width="wrap_content"
 | 
			
		||||
                    android:layout_height="wrap_content"
 | 
			
		||||
                    android:text="Fetch Remote Modified Songs"
 | 
			
		||||
                    android:textSize="16sp"
 | 
			
		||||
                    android:textColor="@android:color/black"
 | 
			
		||||
                    app:layout_constraintStart_toStartOf="parent"
 | 
			
		||||
                    app:layout_constraintTop_toTopOf="parent"
 | 
			
		||||
                    app:layout_constraintBottom_toBottomOf="parent"
 | 
			
		||||
                    android:layout_marginEnd="8dp" />
 | 
			
		||||
 | 
			
		||||
            <!-- Progress Bar -->
 | 
			
		||||
            <ProgressBar
 | 
			
		||||
                    android:id="@+id/progress_fetch_songs"
 | 
			
		||||
                    style="?android:attr/progressBarStyleHorizontal"
 | 
			
		||||
                    android:layout_width="0dp"
 | 
			
		||||
                    android:layout_height="44dp"
 | 
			
		||||
                    android:indeterminate="true"
 | 
			
		||||
                    android:layout_marginEnd="16dp"
 | 
			
		||||
                    android:layout_marginStart="16dp"
 | 
			
		||||
                    app:layout_constraintStart_toEndOf="@id/fetch_songs"
 | 
			
		||||
                    app:layout_constraintEnd_toStartOf="@id/fetch_songs_btn"
 | 
			
		||||
                    app:layout_constraintTop_toTopOf="@id/fetch_songs"
 | 
			
		||||
                    app:layout_constraintBottom_toBottomOf="@id/fetch_songs"/>
 | 
			
		||||
 | 
			
		||||
            <!-- Sync Button -->
 | 
			
		||||
            <Button
 | 
			
		||||
                    android:id="@+id/fetch_songs_btn"
 | 
			
		||||
                    android:layout_width="wrap_content"
 | 
			
		||||
                    android:layout_height="wrap_content"
 | 
			
		||||
                    android:text="Sync"
 | 
			
		||||
                    app:layout_constraintEnd_toEndOf="parent"
 | 
			
		||||
                    app:layout_constraintTop_toTopOf="@id/fetch_songs"
 | 
			
		||||
                    app:layout_constraintBottom_toBottomOf="@id/fetch_songs"
 | 
			
		||||
                    android:layout_marginEnd="16dp" />
 | 
			
		||||
 | 
			
		||||
        </androidx.constraintlayout.widget.ConstraintLayout>
 | 
			
		||||
    </LinearLayout>
 | 
			
		||||
</androidx.constraintlayout.widget.ConstraintLayout>
 | 
			
		||||
@ -1,37 +0,0 @@
 | 
			
		||||
<?xml version="1.0" encoding="utf-8"?>
 | 
			
		||||
<androidx.constraintlayout.widget.ConstraintLayout
 | 
			
		||||
        xmlns:android="http://schemas.android.com/apk/res/android"
 | 
			
		||||
        xmlns:app="http://schemas.android.com/apk/res-auto"
 | 
			
		||||
        xmlns:tools="http://schemas.android.com/tools"
 | 
			
		||||
        android:layout_width="match_parent"
 | 
			
		||||
        android:layout_height="match_parent"
 | 
			
		||||
        tools:context=".ui.home.HomeFragment">
 | 
			
		||||
 | 
			
		||||
    <com.google.android.material.floatingactionbutton.FloatingActionButton
 | 
			
		||||
            android:src="@android:drawable/ic_input_add"
 | 
			
		||||
            android:layout_width="wrap_content"
 | 
			
		||||
            android:layout_height="wrap_content"
 | 
			
		||||
            android:clickable="true" android:id="@+id/addSongBtn"
 | 
			
		||||
            app:layout_constraintBottom_toBottomOf="parent"
 | 
			
		||||
            android:layout_marginBottom="16dp" android:layout_marginEnd="16dp"
 | 
			
		||||
            app:layout_constraintEnd_toEndOf="parent" android:accessibilityPaneTitle="Add a Song"/>
 | 
			
		||||
    <TextView
 | 
			
		||||
            android:id="@+id/text_home"
 | 
			
		||||
            android:layout_width="match_parent"
 | 
			
		||||
            android:layout_height="wrap_content"
 | 
			
		||||
            android:layout_marginStart="8dp"
 | 
			
		||||
            android:layout_marginTop="8dp"
 | 
			
		||||
            android:layout_marginEnd="8dp"
 | 
			
		||||
            android:textAlignment="center"
 | 
			
		||||
            android:textSize="20sp"
 | 
			
		||||
            app:layout_constraintEnd_toEndOf="parent"
 | 
			
		||||
            app:layout_constraintStart_toStartOf="parent"
 | 
			
		||||
            app:layout_constraintTop_toTopOf="parent"
 | 
			
		||||
            app:layout_constraintBottom_toBottomOf="parent"/>
 | 
			
		||||
    <androidx.recyclerview.widget.RecyclerView
 | 
			
		||||
            android:id="@+id/recycler_view_songs"
 | 
			
		||||
            android:layout_width="match_parent"
 | 
			
		||||
            android:layout_height="match_parent"
 | 
			
		||||
            android:padding="8dp"
 | 
			
		||||
            app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" />
 | 
			
		||||
</androidx.constraintlayout.widget.ConstraintLayout>
 | 
			
		||||
@ -1,79 +0,0 @@
 | 
			
		||||
<?xml version="1.0" encoding="utf-8"?>
 | 
			
		||||
<androidx.constraintlayout.widget.ConstraintLayout
 | 
			
		||||
        xmlns:android="http://schemas.android.com/apk/res/android"
 | 
			
		||||
        xmlns:app="http://schemas.android.com/apk/res-auto"
 | 
			
		||||
        xmlns:tools="http://schemas.android.com/tools"
 | 
			
		||||
        android:id="@+id/container"
 | 
			
		||||
        android:layout_width="match_parent"
 | 
			
		||||
        android:layout_height="match_parent"
 | 
			
		||||
        android:paddingBottom="@dimen/fragment_vertical_margin"
 | 
			
		||||
        android:paddingLeft="@dimen/fragment_horizontal_margin"
 | 
			
		||||
        android:paddingRight="@dimen/fragment_horizontal_margin"
 | 
			
		||||
        android:paddingTop="@dimen/fragment_vertical_margin"
 | 
			
		||||
        tools:context=".ui.login.LoginFragment">
 | 
			
		||||
 | 
			
		||||
    <EditText
 | 
			
		||||
            android:id="@+id/username"
 | 
			
		||||
            android:layout_width="0dp"
 | 
			
		||||
            android:layout_height="wrap_content"
 | 
			
		||||
            android:layout_marginStart="24dp"
 | 
			
		||||
            android:layout_marginTop="96dp"
 | 
			
		||||
            android:layout_marginEnd="24dp"
 | 
			
		||||
            android:autofillHints="@string/prompt_email"
 | 
			
		||||
            android:hint="@string/prompt_email"
 | 
			
		||||
            android:inputType="textEmailAddress"
 | 
			
		||||
            android:selectAllOnFocus="true"
 | 
			
		||||
            app:layout_constraintEnd_toEndOf="parent"
 | 
			
		||||
            app:layout_constraintStart_toStartOf="parent"
 | 
			
		||||
            app:layout_constraintTop_toTopOf="parent"/>
 | 
			
		||||
 | 
			
		||||
    <EditText
 | 
			
		||||
            android:id="@+id/password"
 | 
			
		||||
            android:layout_width="0dp"
 | 
			
		||||
            android:layout_height="wrap_content"
 | 
			
		||||
            android:layout_marginStart="24dp"
 | 
			
		||||
            android:layout_marginTop="8dp"
 | 
			
		||||
            android:layout_marginEnd="24dp"
 | 
			
		||||
            android:autofillHints="@string/prompt_password"
 | 
			
		||||
            android:hint="@string/prompt_password"
 | 
			
		||||
            android:imeActionLabel="@string/action_sign_in_short"
 | 
			
		||||
            android:imeOptions="actionDone"
 | 
			
		||||
            android:inputType="textPassword"
 | 
			
		||||
            android:selectAllOnFocus="true"
 | 
			
		||||
            app:layout_constraintEnd_toEndOf="parent"
 | 
			
		||||
            app:layout_constraintStart_toStartOf="parent"
 | 
			
		||||
            app:layout_constraintTop_toBottomOf="@+id/username"/>
 | 
			
		||||
 | 
			
		||||
    <Button
 | 
			
		||||
            android:id="@+id/login"
 | 
			
		||||
            android:enabled="false"
 | 
			
		||||
            android:layout_width="wrap_content"
 | 
			
		||||
            android:layout_height="wrap_content"
 | 
			
		||||
            android:layout_gravity="start"
 | 
			
		||||
            android:layout_marginStart="48dp"
 | 
			
		||||
            android:layout_marginTop="16dp"
 | 
			
		||||
            android:layout_marginEnd="48dp"
 | 
			
		||||
            android:layout_marginBottom="64dp"
 | 
			
		||||
            android:text="@string/action_sign_in"
 | 
			
		||||
            app:layout_constraintBottom_toBottomOf="parent"
 | 
			
		||||
            app:layout_constraintEnd_toEndOf="parent"
 | 
			
		||||
            app:layout_constraintStart_toStartOf="parent"
 | 
			
		||||
            app:layout_constraintTop_toBottomOf="@+id/password"
 | 
			
		||||
            app:layout_constraintVertical_bias="0.2"/>
 | 
			
		||||
 | 
			
		||||
    <ProgressBar
 | 
			
		||||
            android:id="@+id/loading"
 | 
			
		||||
            android:visibility="gone"
 | 
			
		||||
            android:layout_width="wrap_content"
 | 
			
		||||
            android:layout_height="wrap_content"
 | 
			
		||||
            android:layout_gravity="center"
 | 
			
		||||
            android:layout_marginStart="32dp"
 | 
			
		||||
            android:layout_marginTop="64dp"
 | 
			
		||||
            android:layout_marginEnd="32dp"
 | 
			
		||||
            android:layout_marginBottom="64dp"
 | 
			
		||||
            app:layout_constraintBottom_toBottomOf="parent"
 | 
			
		||||
            app:layout_constraintEnd_toEndOf="@+id/password"
 | 
			
		||||
            app:layout_constraintStart_toStartOf="@+id/password"
 | 
			
		||||
            app:layout_constraintTop_toTopOf="parent"
 | 
			
		||||
            app:layout_constraintVertical_bias="0.3"/>
 | 
			
		||||
</androidx.constraintlayout.widget.ConstraintLayout>
 | 
			
		||||
@ -1,84 +0,0 @@
 | 
			
		||||
<?xml version="1.0" encoding="utf-8"?>
 | 
			
		||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
 | 
			
		||||
              android:layout_width="match_parent"
 | 
			
		||||
              android:layout_height="wrap_content" xmlns:app="http://schemas.android.com/apk/res-auto"
 | 
			
		||||
              android:orientation="vertical"
 | 
			
		||||
              android:padding="16dp"
 | 
			
		||||
              android:background="@drawable/dialog_background"
 | 
			
		||||
              android:layout_gravity="center_horizontal">
 | 
			
		||||
 | 
			
		||||
    <!-- Title -->
 | 
			
		||||
    <TextView
 | 
			
		||||
            android:id="@+id/textViewTitle"
 | 
			
		||||
            android:layout_width="match_parent"
 | 
			
		||||
            android:layout_height="wrap_content"
 | 
			
		||||
            android:text="Login"
 | 
			
		||||
            android:textSize="18sp"
 | 
			
		||||
            android:textStyle="bold"
 | 
			
		||||
            android:gravity="center"
 | 
			
		||||
            android:paddingBottom="16dp" />
 | 
			
		||||
 | 
			
		||||
    <!-- Username Field (Visible only in Registration mode) -->
 | 
			
		||||
    <EditText
 | 
			
		||||
            android:id="@+id/editTextUsername"
 | 
			
		||||
            android:layout_width="match_parent"
 | 
			
		||||
            android:layout_height="wrap_content"
 | 
			
		||||
            android:hint="Username"
 | 
			
		||||
            android:visibility="gone" />
 | 
			
		||||
 | 
			
		||||
    <!-- Email Field -->
 | 
			
		||||
    <EditText
 | 
			
		||||
            android:id="@+id/editTextEmail"
 | 
			
		||||
            android:layout_width="match_parent"
 | 
			
		||||
            android:layout_height="wrap_content"
 | 
			
		||||
            android:hint="Email"
 | 
			
		||||
            android:inputType="textEmailAddress" />
 | 
			
		||||
 | 
			
		||||
    <!-- Password Field -->
 | 
			
		||||
    <EditText
 | 
			
		||||
            android:id="@+id/editTextPassword"
 | 
			
		||||
            android:layout_width="match_parent"
 | 
			
		||||
            android:layout_height="wrap_content"
 | 
			
		||||
            android:hint="Password"
 | 
			
		||||
            android:inputType="textPassword" />
 | 
			
		||||
 | 
			
		||||
    <!-- Action Button -->
 | 
			
		||||
    <Button
 | 
			
		||||
            android:id="@+id/buttonAction"
 | 
			
		||||
            android:layout_width="match_parent"
 | 
			
		||||
            android:layout_height="wrap_content"
 | 
			
		||||
            android:text="Login"
 | 
			
		||||
            android:backgroundTint="?attr/colorPrimary"
 | 
			
		||||
            android:textColor="@android:color/white"
 | 
			
		||||
            android:layout_marginTop="16dp" />
 | 
			
		||||
 | 
			
		||||
    <!-- Toggle Login/Registration -->
 | 
			
		||||
    <TextView
 | 
			
		||||
            android:id="@+id/textViewSwitch"
 | 
			
		||||
            android:layout_width="match_parent"
 | 
			
		||||
            android:layout_height="wrap_content"
 | 
			
		||||
            android:text="Don't have an account? Register"
 | 
			
		||||
            android:gravity="center"
 | 
			
		||||
            android:textColor="?attr/colorPrimary"
 | 
			
		||||
            android:layout_marginTop="8dp"
 | 
			
		||||
            android:padding="8dp"
 | 
			
		||||
            android:clickable="true"
 | 
			
		||||
            android:focusable="true" />
 | 
			
		||||
 | 
			
		||||
    <ProgressBar
 | 
			
		||||
            android:id="@+id/loading"
 | 
			
		||||
            android:visibility="gone"
 | 
			
		||||
            android:layout_width="wrap_content"
 | 
			
		||||
            android:layout_height="wrap_content"
 | 
			
		||||
            android:layout_gravity="center"
 | 
			
		||||
            android:layout_marginStart="32dp"
 | 
			
		||||
            android:layout_marginTop="64dp"
 | 
			
		||||
            android:layout_marginEnd="32dp"
 | 
			
		||||
            android:layout_marginBottom="64dp"
 | 
			
		||||
            app:layout_constraintBottom_toBottomOf="parent"
 | 
			
		||||
            app:layout_constraintEnd_toEndOf="@+id/password"
 | 
			
		||||
            app:layout_constraintStart_toStartOf="@+id/password"
 | 
			
		||||
            app:layout_constraintTop_toTopOf="parent"
 | 
			
		||||
            app:layout_constraintVertical_bias="0.3"/>
 | 
			
		||||
 | 
			
		||||
</LinearLayout>
 | 
			
		||||
@ -1,23 +0,0 @@
 | 
			
		||||
<?xml version="1.0" encoding="utf-8"?>
 | 
			
		||||
<androidx.constraintlayout.widget.ConstraintLayout
 | 
			
		||||
        xmlns:android="http://schemas.android.com/apk/res/android"
 | 
			
		||||
        xmlns:app="http://schemas.android.com/apk/res-auto"
 | 
			
		||||
        xmlns:tools="http://schemas.android.com/tools"
 | 
			
		||||
        android:layout_width="match_parent"
 | 
			
		||||
        android:layout_height="match_parent"
 | 
			
		||||
        tools:context=".ui.slideshow.SlideshowFragment">
 | 
			
		||||
 | 
			
		||||
    <TextView
 | 
			
		||||
            android:id="@+id/text_slideshow"
 | 
			
		||||
            android:layout_width="match_parent"
 | 
			
		||||
            android:layout_height="wrap_content"
 | 
			
		||||
            android:layout_marginStart="8dp"
 | 
			
		||||
            android:layout_marginTop="8dp"
 | 
			
		||||
            android:layout_marginEnd="8dp"
 | 
			
		||||
            android:textAlignment="center"
 | 
			
		||||
            android:textSize="20sp"
 | 
			
		||||
            app:layout_constraintEnd_toEndOf="parent"
 | 
			
		||||
            app:layout_constraintStart_toStartOf="parent"
 | 
			
		||||
            app:layout_constraintTop_toTopOf="parent"
 | 
			
		||||
            app:layout_constraintBottom_toBottomOf="parent"/>
 | 
			
		||||
</androidx.constraintlayout.widget.ConstraintLayout>
 | 
			
		||||
@ -1,146 +0,0 @@
 | 
			
		||||
<?xml version="1.0" encoding="utf-8"?>
 | 
			
		||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
 | 
			
		||||
              android:layout_width="match_parent"
 | 
			
		||||
              android:layout_height="wrap_content"
 | 
			
		||||
              android:orientation="vertical"
 | 
			
		||||
              android:padding="24dp"
 | 
			
		||||
              android:background="?android:attr/windowBackground">
 | 
			
		||||
 | 
			
		||||
    <!-- Titel des Songs -->
 | 
			
		||||
    <TextView
 | 
			
		||||
            android:id="@+id/tvTitle"
 | 
			
		||||
            android:layout_width="match_parent"
 | 
			
		||||
            android:layout_height="wrap_content"
 | 
			
		||||
            android:text="Song Title"
 | 
			
		||||
            android:textSize="16sp"
 | 
			
		||||
            android:textColor="#000000"
 | 
			
		||||
            android:paddingBottom="8dp"
 | 
			
		||||
            android:fontFamily="sans-serif-medium"/>
 | 
			
		||||
 | 
			
		||||
    <EditText
 | 
			
		||||
            android:id="@+id/etTitle"
 | 
			
		||||
            android:layout_width="match_parent"
 | 
			
		||||
            android:layout_height="wrap_content"
 | 
			
		||||
            android:hint="Enter Song Title"
 | 
			
		||||
            android:inputType="text"
 | 
			
		||||
            android:padding="12dp"
 | 
			
		||||
            android:layout_marginBottom="16dp"
 | 
			
		||||
            android:backgroundTint="@color/light_blue_600"/>
 | 
			
		||||
 | 
			
		||||
    <!-- Komponist -->
 | 
			
		||||
    <TextView
 | 
			
		||||
            android:id="@+id/tvComposer"
 | 
			
		||||
            android:layout_width="match_parent"
 | 
			
		||||
            android:layout_height="wrap_content"
 | 
			
		||||
            android:text="Composer"
 | 
			
		||||
            android:textSize="16sp"
 | 
			
		||||
            android:textColor="#000000"
 | 
			
		||||
            android:paddingBottom="8dp"
 | 
			
		||||
            android:fontFamily="sans-serif-medium"/>
 | 
			
		||||
 | 
			
		||||
    <EditText
 | 
			
		||||
            android:id="@+id/etComposer"
 | 
			
		||||
            android:layout_width="match_parent"
 | 
			
		||||
            android:layout_height="wrap_content"
 | 
			
		||||
            android:hint="Enter Composer"
 | 
			
		||||
            android:inputType="text"
 | 
			
		||||
            android:padding="12dp"
 | 
			
		||||
            android:layout_marginBottom="16dp"
 | 
			
		||||
            android:backgroundTint="@color/light_blue_600"/>
 | 
			
		||||
 | 
			
		||||
    <!-- Erscheinungsjahr -->
 | 
			
		||||
    <TextView
 | 
			
		||||
            android:id="@+id/tvYear"
 | 
			
		||||
            android:layout_width="match_parent"
 | 
			
		||||
            android:layout_height="wrap_content"
 | 
			
		||||
            android:text="Release Year"
 | 
			
		||||
            android:textSize="16sp"
 | 
			
		||||
            android:textColor="#000000"
 | 
			
		||||
            android:paddingBottom="8dp"
 | 
			
		||||
            android:fontFamily="sans-serif-medium"/>
 | 
			
		||||
 | 
			
		||||
    <EditText
 | 
			
		||||
            android:id="@+id/etYear"
 | 
			
		||||
            android:layout_width="match_parent"
 | 
			
		||||
            android:layout_height="wrap_content"
 | 
			
		||||
            android:hint="Enter Release Year"
 | 
			
		||||
            android:inputType="number"
 | 
			
		||||
            android:padding="12dp"
 | 
			
		||||
            android:layout_marginBottom="16dp"
 | 
			
		||||
            android:backgroundTint="@color/light_blue_600"/>
 | 
			
		||||
 | 
			
		||||
    <!-- Genre -->
 | 
			
		||||
    <TextView
 | 
			
		||||
            android:id="@+id/tvGenre"
 | 
			
		||||
            android:layout_width="match_parent"
 | 
			
		||||
            android:layout_height="wrap_content"
 | 
			
		||||
            android:text="Genre"
 | 
			
		||||
            android:textSize="16sp"
 | 
			
		||||
            android:textColor="#000000"
 | 
			
		||||
            android:paddingBottom="8dp"
 | 
			
		||||
            android:fontFamily="sans-serif-medium"/>
 | 
			
		||||
 | 
			
		||||
    <EditText
 | 
			
		||||
            android:id="@+id/etGenre"
 | 
			
		||||
            android:layout_width="match_parent"
 | 
			
		||||
            android:layout_height="wrap_content"
 | 
			
		||||
            android:hint="Enter Genre"
 | 
			
		||||
            android:inputType="text"
 | 
			
		||||
            android:padding="12dp"
 | 
			
		||||
            android:layout_marginBottom="16dp"
 | 
			
		||||
            android:backgroundTint="@color/light_blue_600"/>
 | 
			
		||||
 | 
			
		||||
    <!-- Buttons -->
 | 
			
		||||
    <LinearLayout
 | 
			
		||||
            android:layout_width="match_parent"
 | 
			
		||||
            android:layout_height="wrap_content"
 | 
			
		||||
            android:orientation="horizontal"
 | 
			
		||||
            android:gravity="end">
 | 
			
		||||
        <!-- ProgressBar als Spinner -->
 | 
			
		||||
        <ProgressBar
 | 
			
		||||
                android:id="@+id/spinnerAutoDetect"
 | 
			
		||||
                android:layout_width="24dp"
 | 
			
		||||
                android:layout_height="24dp"
 | 
			
		||||
                android:layout_marginEnd="16dp"
 | 
			
		||||
                android:baselineAligned="false"
 | 
			
		||||
 | 
			
		||||
                android:visibility="gone"
 | 
			
		||||
                android:indeterminate="true" />
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        <TextView
 | 
			
		||||
                android:id="@+id/btnAutoDetect"
 | 
			
		||||
                android:layout_width="wrap_content"
 | 
			
		||||
                android:layout_height="wrap_content"
 | 
			
		||||
                android:text="Automatische Erkennung"
 | 
			
		||||
                android:textColor="@color/light_blue_600"
 | 
			
		||||
                android:paddingEnd="16dp"
 | 
			
		||||
                android:clickable="true"
 | 
			
		||||
                android:focusable="true"
 | 
			
		||||
                android:textSize="16sp"
 | 
			
		||||
                android:layout_gravity="center_vertical"
 | 
			
		||||
                android:fontFamily="sans-serif-medium" />
 | 
			
		||||
 | 
			
		||||
        <Button
 | 
			
		||||
                android:id="@+id/btnCancel"
 | 
			
		||||
                android:layout_width="wrap_content"
 | 
			
		||||
                android:layout_height="wrap_content"
 | 
			
		||||
                android:text="Cancel"
 | 
			
		||||
                android:layout_marginEnd="8dp"
 | 
			
		||||
                android:backgroundTint="@android:color/darker_gray"
 | 
			
		||||
                android:textColor="@android:color/white"
 | 
			
		||||
                android:paddingHorizontal="16dp"
 | 
			
		||||
                android:paddingVertical="8dp" />
 | 
			
		||||
 | 
			
		||||
        <Button
 | 
			
		||||
                android:id="@+id/btnSave"
 | 
			
		||||
                android:layout_width="wrap_content"
 | 
			
		||||
                android:layout_height="wrap_content"
 | 
			
		||||
                android:text="Save"
 | 
			
		||||
                android:backgroundTint="@color/light_blue_600"
 | 
			
		||||
                android:textColor="@android:color/white"
 | 
			
		||||
                android:paddingHorizontal="16dp"
 | 
			
		||||
                android:paddingVertical="8dp" />
 | 
			
		||||
    </LinearLayout>
 | 
			
		||||
 | 
			
		||||
</LinearLayout>
 | 
			
		||||
@ -1,11 +0,0 @@
 | 
			
		||||
<?xml version="1.0" encoding="utf-8"?>
 | 
			
		||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
 | 
			
		||||
             android:layout_width="match_parent"
 | 
			
		||||
             android:layout_height="match_parent">
 | 
			
		||||
 | 
			
		||||
    <com.github.chrisbanes.photoview.PhotoView
 | 
			
		||||
            android:id="@+id/photoView"
 | 
			
		||||
            android:layout_width="match_parent"
 | 
			
		||||
            android:layout_height="match_parent"
 | 
			
		||||
            android:scaleType="fitCenter" />
 | 
			
		||||
</FrameLayout>
 | 
			
		||||
@ -1,77 +0,0 @@
 | 
			
		||||
<?xml version="1.0" encoding="utf-8"?>
 | 
			
		||||
<LinearLayout
 | 
			
		||||
        xmlns:android="http://schemas.android.com/apk/res/android"
 | 
			
		||||
        android:layout_width="match_parent"
 | 
			
		||||
        android:layout_height="wrap_content" xmlns:app="http://schemas.android.com/apk/res-auto"
 | 
			
		||||
        android:orientation="horizontal"
 | 
			
		||||
        android:padding="8dp"
 | 
			
		||||
        android:gravity="center_vertical">
 | 
			
		||||
 | 
			
		||||
    <!-- Text Section -->
 | 
			
		||||
    <LinearLayout
 | 
			
		||||
            android:layout_width="0dp"
 | 
			
		||||
            android:layout_height="wrap_content"
 | 
			
		||||
            android:layout_weight="1"
 | 
			
		||||
            android:orientation="vertical">
 | 
			
		||||
 | 
			
		||||
        <!-- First Row: Year and Composition -->
 | 
			
		||||
        <LinearLayout
 | 
			
		||||
                android:layout_width="match_parent"
 | 
			
		||||
                android:layout_height="wrap_content"
 | 
			
		||||
                android:orientation="horizontal">
 | 
			
		||||
 | 
			
		||||
            <TextView
 | 
			
		||||
                    android:id="@+id/text_year"
 | 
			
		||||
                    android:layout_width="wrap_content"
 | 
			
		||||
                    android:layout_height="wrap_content"
 | 
			
		||||
                    android:text="Year"
 | 
			
		||||
                    android:textSize="14sp"
 | 
			
		||||
                    android:textColor="@android:color/darker_gray" />
 | 
			
		||||
 | 
			
		||||
            <TextView
 | 
			
		||||
                    android:id="@+id/text_composition"
 | 
			
		||||
                    android:layout_width="wrap_content"
 | 
			
		||||
                    android:layout_height="wrap_content"
 | 
			
		||||
                    android:text="Composition"
 | 
			
		||||
                    android:textSize="14sp"
 | 
			
		||||
                    android:layout_marginStart="8dp"
 | 
			
		||||
                    android:textColor="@android:color/darker_gray" />
 | 
			
		||||
        </LinearLayout>
 | 
			
		||||
 | 
			
		||||
        <!-- Second Row: Title -->
 | 
			
		||||
        <TextView
 | 
			
		||||
                android:id="@+id/text_title"
 | 
			
		||||
                android:layout_width="match_parent"
 | 
			
		||||
                android:layout_height="wrap_content"
 | 
			
		||||
                android:text="Title"
 | 
			
		||||
                android:textSize="16sp"
 | 
			
		||||
                android:textColor="@android:color/black"
 | 
			
		||||
                android:paddingTop="4dp"/>
 | 
			
		||||
    </LinearLayout>
 | 
			
		||||
 | 
			
		||||
    <!-- Rechte Seite mit den Buttons -->
 | 
			
		||||
    <LinearLayout
 | 
			
		||||
            android:layout_width="wrap_content"
 | 
			
		||||
            android:layout_height="wrap_content"
 | 
			
		||||
            android:orientation="horizontal">
 | 
			
		||||
 | 
			
		||||
        <ImageButton
 | 
			
		||||
                android:id="@+id/edit_song_button"
 | 
			
		||||
                android:layout_width="48dp"
 | 
			
		||||
                android:layout_height="48dp"
 | 
			
		||||
                android:src="@drawable/edit_24dp_e8eaed_fill0_wght400_grad0_opsz24"
 | 
			
		||||
                app:tint="@color/teal_700"
 | 
			
		||||
                android:contentDescription="Edit Song"
 | 
			
		||||
                android:background="?android:selectableItemBackgroundBorderless" />
 | 
			
		||||
 | 
			
		||||
        <ImageButton
 | 
			
		||||
                android:id="@+id/delete_song_button"
 | 
			
		||||
                android:layout_width="48dp"
 | 
			
		||||
                android:layout_height="48dp"
 | 
			
		||||
                android:src="@drawable/delete"
 | 
			
		||||
                app:tint="#FF0000"
 | 
			
		||||
                android:contentDescription="Delete Song"
 | 
			
		||||
                android:background="?android:selectableItemBackgroundBorderless"/>
 | 
			
		||||
    </LinearLayout>
 | 
			
		||||
 | 
			
		||||
</LinearLayout>
 | 
			
		||||
@ -1,36 +0,0 @@
 | 
			
		||||
<?xml version="1.0" encoding="utf-8"?>
 | 
			
		||||
<LinearLayout
 | 
			
		||||
        xmlns:android="http://schemas.android.com/apk/res/android"
 | 
			
		||||
        xmlns:app="http://schemas.android.com/apk/res-auto"
 | 
			
		||||
        android:layout_width="match_parent"
 | 
			
		||||
        android:layout_height="@dimen/nav_header_height"
 | 
			
		||||
        android:background="@drawable/side_nav_bar"
 | 
			
		||||
        android:paddingBottom="@dimen/activity_vertical_margin"
 | 
			
		||||
        android:paddingLeft="@dimen/activity_horizontal_margin"
 | 
			
		||||
        android:paddingRight="@dimen/activity_horizontal_margin"
 | 
			
		||||
        android:paddingTop="@dimen/activity_vertical_margin"
 | 
			
		||||
        android:theme="@style/ThemeOverlay.AppCompat.Dark"
 | 
			
		||||
        android:orientation="vertical"
 | 
			
		||||
        android:gravity="bottom">
 | 
			
		||||
 | 
			
		||||
    <ImageView
 | 
			
		||||
            android:layout_width="wrap_content"
 | 
			
		||||
            android:layout_height="wrap_content"
 | 
			
		||||
            android:paddingTop="@dimen/nav_header_vertical_spacing"
 | 
			
		||||
            app:srcCompat="@mipmap/ic_launcher_round"
 | 
			
		||||
            android:contentDescription="@string/nav_header_desc"
 | 
			
		||||
            android:id="@+id/imageView"/>
 | 
			
		||||
 | 
			
		||||
    <TextView
 | 
			
		||||
            android:layout_width="match_parent"
 | 
			
		||||
            android:layout_height="wrap_content"
 | 
			
		||||
            android:paddingTop="@dimen/nav_header_vertical_spacing"
 | 
			
		||||
            android:text="@string/nav_header_title"
 | 
			
		||||
            android:textAppearance="@style/TextAppearance.AppCompat.Body1"/>
 | 
			
		||||
 | 
			
		||||
    <TextView
 | 
			
		||||
            android:layout_width="wrap_content"
 | 
			
		||||
            android:layout_height="wrap_content"
 | 
			
		||||
            android:text="@string/nav_header_subtitle"
 | 
			
		||||
            android:id="@+id/textView"/>
 | 
			
		||||
</LinearLayout>
 | 
			
		||||
@ -1,20 +0,0 @@
 | 
			
		||||
<?xml version="1.0" encoding="utf-8"?>
 | 
			
		||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
 | 
			
		||||
      xmlns:tools="http://schemas.android.com/tools"
 | 
			
		||||
      tools:showIn="navigation_view">
 | 
			
		||||
 | 
			
		||||
    <group android:checkableBehavior="single">
 | 
			
		||||
        <item
 | 
			
		||||
                android:id="@+id/nav_home"
 | 
			
		||||
                android:icon="@drawable/ic_menu_camera"
 | 
			
		||||
                android:title="@string/menu_home"/>
 | 
			
		||||
        <item
 | 
			
		||||
                android:id="@+id/nav_gallery"
 | 
			
		||||
                android:icon="@drawable/ic_menu_gallery"
 | 
			
		||||
                android:title="@string/menu_gallery"/>
 | 
			
		||||
        <item
 | 
			
		||||
                android:id="@+id/nav_slideshow"
 | 
			
		||||
                android:icon="@drawable/ic_menu_slideshow"
 | 
			
		||||
                android:title="@string/menu_slideshow"/>
 | 
			
		||||
    </group>
 | 
			
		||||
</menu>
 | 
			
		||||
@ -1,8 +0,0 @@
 | 
			
		||||
<?xml version="1.0" encoding="utf-8"?>
 | 
			
		||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
 | 
			
		||||
      xmlns:app="http://schemas.android.com/apk/res-auto">
 | 
			
		||||
    <item android:id="@+id/auth_action"
 | 
			
		||||
          android:title="@string/login"
 | 
			
		||||
          android:icon="@drawable/login"
 | 
			
		||||
          app:showAsAction="always"/>
 | 
			
		||||
</menu>
 | 
			
		||||
Some files were not shown because too many files have changed in this diff Show More
		Loading…
	
		Reference in New Issue
	
	Block a user