Initialize Rewrite

This commit is contained in:
Sebastian Böckelmann 2025-04-12 18:17:17 +02:00
parent 390051842f
commit 2cd0b3034d
118 changed files with 250 additions and 4747 deletions

View File

@ -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
View File

@ -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

View File

@ -1 +0,0 @@
NoteVault

View File

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CompilerConfiguration">
<bytecodeTargetLevel target="21" />
</component>
</project>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

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

View File

@ -1,14 +1,16 @@
plugins { plugins {
alias(libs.plugins.android.application) alias(libs.plugins.android.application)
alias(libs.plugins.kotlin.android)
alias(libs.plugins.kotlin.compose)
} }
android { android {
namespace = "com.stormtales.notevault" namespace = "come.stormborntales.notevault"
compileSdk = 34 compileSdk = 34
defaultConfig { defaultConfig {
applicationId = "com.stormtales.notevault" applicationId = "come.stormborntales.notevault"
minSdk = 29 minSdk = 24
targetSdk = 34 targetSdk = 34
versionCode = 1 versionCode = 1
versionName = "1.0" versionName = "1.0"
@ -26,28 +28,31 @@ android {
sourceCompatibility = JavaVersion.VERSION_11 sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11 targetCompatibility = JavaVersion.VERSION_11
} }
kotlinOptions {
jvmTarget = "11"
}
buildFeatures { buildFeatures {
viewBinding = true compose = true
} }
} }
dependencies { dependencies {
implementation(libs.appcompat) implementation(libs.androidx.core.ktx)
implementation(libs.material) implementation(libs.androidx.lifecycle.runtime.ktx)
implementation(libs.constraintlayout) implementation(libs.androidx.activity.compose)
implementation(libs.lifecycle.livedata.ktx) implementation(platform(libs.androidx.compose.bom))
implementation(libs.lifecycle.viewmodel.ktx) implementation(libs.androidx.ui)
implementation(libs.navigation.fragment) implementation(libs.androidx.ui.graphics)
implementation(libs.navigation.ui) implementation(libs.androidx.ui.tooling.preview)
implementation(libs.room.runtime) implementation(libs.androidx.material3)
implementation(libs.annotation) implementation(libs.androidx.navigation.compose)
implementation(libs.androidx.lifecycle.viewmodel.compose)
testImplementation(libs.junit) testImplementation(libs.junit)
androidTestImplementation(libs.ext.junit) androidTestImplementation(libs.androidx.junit)
androidTestImplementation(libs.espresso.core) androidTestImplementation(libs.androidx.espresso.core)
annotationProcessor(libs.room.compiler) androidTestImplementation(platform(libs.androidx.compose.bom))
implementation("com.github.chrisbanes:PhotoView:2.3.0") androidTestImplementation(libs.androidx.ui.test.junit4)
implementation("com.squareup.retrofit2:retrofit:2.9.0") debugImplementation(libs.androidx.ui.tooling)
implementation("com.squareup.retrofit2:converter-gson:2.9.0") debugImplementation(libs.androidx.ui.test.manifest)
implementation("com.squareup.okhttp3:logging-interceptor:4.9.1")
} }

View File

@ -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());
}
}

View File

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

View File

@ -1,12 +1,11 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"> xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.INTERNET" />
<application <application
android:allowBackup="true" android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules" android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules" android:fullBackupContent="@xml/backup_rules"
android:networkSecurityConfig="@xml/network_security_config"
android:icon="@mipmap/ic_launcher" android:icon="@mipmap/ic_launcher"
android:label="@string/app_name" android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round" android:roundIcon="@mipmap/ic_launcher_round"
@ -17,18 +16,13 @@
android:name=".MainActivity" android:name=".MainActivity"
android:exported="true" android:exported="true"
android:label="@string/app_name" android:label="@string/app_name"
android:theme="@style/Theme.NoteVault.NoActionBar"> android:theme="@style/Theme.NoteVault">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN"/> <action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/> <category android:name="android.intent.category.LAUNCHER"/>
</intent-filter> </intent-filter>
</activity> </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> </application>
</manifest> </manifest>

View File

@ -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");
}
}

View File

@ -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;
}
}

View File

@ -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);
}

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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;
}

View File

@ -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;
}
}

View File

@ -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);
});
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

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

View File

@ -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();
}
}

View File

@ -1,6 +0,0 @@
package com.stormtales.notevault.network;
public interface APICallback {
void onSuccess();
void onError(String error);
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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);
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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();
}
}

View File

@ -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);
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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();
}
}

View File

@ -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;
}
}

View File

@ -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);
});
}
}

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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();
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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);
}
}
}

View File

@ -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();
}
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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());
}
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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")
}
}

View File

@ -0,0 +1,11 @@
package come.stormborntales.notevault.ui.theme
import androidx.compose.ui.graphics.Color
val Purple80 = Color(0xFFD0BCFF)
val PurpleGrey80 = Color(0xFFCCC2DC)
val Pink80 = Color(0xFFEFB8C8)
val Purple40 = Color(0xFF6650a4)
val PurpleGrey40 = Color(0xFF625b71)
val Pink40 = Color(0xFF7D5260)

View File

@ -0,0 +1,58 @@
package come.stormborntales.notevault.ui.theme
import android.app.Activity
import android.os.Build
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.darkColorScheme
import androidx.compose.material3.dynamicDarkColorScheme
import androidx.compose.material3.dynamicLightColorScheme
import androidx.compose.material3.lightColorScheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.platform.LocalContext
private val DarkColorScheme = darkColorScheme(
primary = Purple80,
secondary = PurpleGrey80,
tertiary = Pink80
)
private val LightColorScheme = lightColorScheme(
primary = Purple40,
secondary = PurpleGrey40,
tertiary = Pink40
/* Other default colors to override
background = Color(0xFFFFFBFE),
surface = Color(0xFFFFFBFE),
onPrimary = Color.White,
onSecondary = Color.White,
onTertiary = Color.White,
onBackground = Color(0xFF1C1B1F),
onSurface = Color(0xFF1C1B1F),
*/
)
@Composable
fun NoteVaultTheme(
darkTheme: Boolean = isSystemInDarkTheme(),
// Dynamic color is available on Android 12+
dynamicColor: Boolean = true,
content: @Composable () -> Unit
) {
val colorScheme = when {
dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
val context = LocalContext.current
if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
}
darkTheme -> DarkColorScheme
else -> LightColorScheme
}
MaterialTheme(
colorScheme = colorScheme,
typography = Typography,
content = content
)
}

View File

@ -0,0 +1,34 @@
package come.stormborntales.notevault.ui.theme
import androidx.compose.material3.Typography
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.sp
// Set of Material typography styles to start with
val Typography = Typography(
bodyLarge = TextStyle(
fontFamily = FontFamily.Default,
fontWeight = FontWeight.Normal,
fontSize = 16.sp,
lineHeight = 24.sp,
letterSpacing = 0.5.sp
)
/* Other default text styles to override
titleLarge = TextStyle(
fontFamily = FontFamily.Default,
fontWeight = FontWeight.Normal,
fontSize = 22.sp,
lineHeight = 28.sp,
letterSpacing = 0.sp
),
labelSmall = TextStyle(
fontFamily = FontFamily.Default,
fontWeight = FontWeight.Medium,
fontSize = 11.sp,
lineHeight = 16.sp,
letterSpacing = 0.5.sp
)
*/
)

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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