Repo created

This commit is contained in:
Fr4nz D13trich 2025-11-23 08:51:57 +01:00
parent 61d62cabdf
commit ad3df69bdb
872 changed files with 65976 additions and 2 deletions

145
app/build.gradle Normal file
View file

@ -0,0 +1,145 @@
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
android {
compileSdk 35
namespace 'it.niedermann.nextcloud.deck'
defaultConfig {
applicationId "it.niedermann.nextcloud.deck"
minSdk 24
targetSdk 35
versionCode 1024005
versionName "1.24.5"
vectorDrawables.useSupportLibrary true
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
javaCompileOptions {
annotationProcessorOptions {
arguments = ["room.schemaLocation": "$projectDir/schemas".toString()]
}
}
}
buildFeatures {
viewBinding true
buildConfig true
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
compileOptions {
coreLibraryDesugaringEnabled true
sourceCompatibility JavaVersion.VERSION_17
targetCompatibility JavaVersion.VERSION_17
}
kotlinOptions {
jvmTarget = '17'
}
flavorDimensions = ["version"]
productFlavors {
fdroid {
dimension "version"
}
dev {
dimension "version"
applicationIdSuffix ".dev"
}
play {
dimension "version"
applicationIdSuffix ".play"
}
pfungstadt {
dimension "version"
applicationIdSuffix ".pfungstadt"
}
mdm {
dimension "version"
applicationIdSuffix ".mdm"
}
}
testOptions {
unitTests {
includeAndroidResources true
}
}
lint {
abortOnError false
disable 'MissingTranslation'
}
}
dependencies {
def cameraVersion = "1.4.1"
def lifecycleVersion = "2.8.7"
def roomVersion = "2.6.1"
def glideVersion = "4.16.0"
def nextcloudCommonsVersion = "2.3.5"
def androidCommonsVersion = "1.0.4"
implementation project(path: ':cross-tab-drag-and-drop')
implementation project(path: ':tab-layout-helper')
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.1.4'
// Android X
implementation 'androidx.appcompat:appcompat:1.7.0'
implementation "androidx.camera:camera-camera2:$cameraVersion"
implementation "androidx.camera:camera-lifecycle:$cameraVersion"
implementation "androidx.camera:camera-view:$cameraVersion"
implementation 'androidx.constraintlayout:constraintlayout:2.2.0'
implementation "androidx.lifecycle:lifecycle-runtime-ktx:$lifecycleVersion"
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycleVersion"
implementation 'androidx.preference:preference-ktx:1.2.1'
implementation "androidx.room:room-runtime:$roomVersion"
annotationProcessor "androidx.room:room-compiler:$roomVersion"
implementation 'androidx.core:core-splashscreen:1.0.1'
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
implementation 'androidx.work:work-runtime:2.10.0'
implementation "com.google.android.material:material:$rootProject.materialVersion"
// Glide
implementation "com.github.bumptech.glide:glide:$glideVersion"
annotationProcessor "com.github.bumptech.glide:compiler:$glideVersion"
// Image compression
implementation 'id.zelory:compressor:3.0.1'
// Single-Sign-On
implementation 'com.github.nextcloud:Android-SingleSignOn:1.3.2'
implementation 'com.github.nextcloud:android-common:0.24.0'
implementation "com.github.stefan-niedermann.nextcloud-commons:sso-glide:$nextcloudCommonsVersion"
implementation "com.github.stefan-niedermann.nextcloud-commons:exception:$nextcloudCommonsVersion"
implementation("com.github.stefan-niedermann.nextcloud-commons:markdown:$nextcloudCommonsVersion") {
exclude group: 'org.jetbrains', module: 'annotations-java5'
}
implementation "com.github.stefan-niedermann.android-commons:util:$androidCommonsVersion"
implementation "com.github.stefan-niedermann.android-commons:shared-preferences:$androidCommonsVersion"
implementation "com.github.stefan-niedermann.android-commons:reactive-livedata:$androidCommonsVersion"
// Custom Date / Time Picker for branding support
implementation 'com.wdullaer:materialdatetimepicker:4.2.3'
// Flexbox
implementation 'com.google.android.flexbox:flexbox:3.0.0'
// Custom Color Picker
implementation 'com.github.skydoves:colorpickerview:2.3.0'
// Gson
implementation 'com.google.code.gson:gson:2.11.0'
// Retrofit
implementation 'com.squareup.retrofit2:retrofit:2.11.0'
// Zoom Layout
implementation("com.otaliastudios:zoomlayout:1.9.0")
// Tests
testImplementation 'junit:junit:4.13.2'
testImplementation 'org.robolectric:robolectric:4.14.1'
testImplementation 'org.mockito:mockito-core:5.15.2'
testImplementation 'androidx.test:core:1.6.1'
testImplementation 'androidx.arch.core:core-testing:2.2.0'
}

View file

@ -0,0 +1,41 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="38.268356"
android:viewportHeight="38.268356">
<group android:translateX="-2.4874432"
android:translateY="-2.4874432">
<group
android:translateX="13.621622"
android:translateY="13.621622">
<path
android:pathData="M2,7L14,7A1,1 0,0 1,15 8L15,14A1,1 0,0 1,14 15L2,15A1,1 0,0 1,1 14L1,8A1,1 0,0 1,2 7z"
android:fillColor="#fff" />
<path
android:pathData="M2.5,5L13.5,5A0.5,0.5 0,0 1,14 5.5L14,5.5A0.5,0.5 0,0 1,13.5 6L2.5,6A0.5,0.5 0,0 1,2 5.5L2,5.5A0.5,0.5 0,0 1,2.5 5z"
android:fillColor="#fff" />
<path
android:pathData="M3.5,3L12.5,3A0.5,0.5 0,0 1,13 3.5L13,3.5A0.5,0.5 0,0 1,12.5 4L3.5,4A0.5,0.5 0,0 1,3 3.5L3,3.5A0.5,0.5 0,0 1,3.5 3z"
android:fillColor="#fff" />
<path
android:pathData="M4.5,1L11.5,1A0.5,0.5 0,0 1,12 1.5L12,1.5A0.5,0.5 0,0 1,11.5 2L4.5,2A0.5,0.5 0,0 1,4 1.5L4,1.5A0.5,0.5 0,0 1,4.5 1z"
android:fillColor="#fff" />
</group>
</group>
<group
android:translateX="14.3"
android:translateY="26.2"
android:scaleX=".1"
android:scaleY=".1">
<path
android:pathData="M11.908125 40h11.4c4.44 0 7.24 -1.04 9.2 -3.4 2.32 -2.72 3.56 -6.68 3.56 -11.2 0 -4.48 -1.24 -8.44 -3.56 -11.2 -1.96 -2.36 -4.72 -3.36 -9.2 -3.36h-11.4zm6 -5V15.84h5.4c4.52 0 6.76 3.16 6.76 9.6 0 6.4 -2.24 9.56 -6.76 9.56z"
android:fillColor="#fff"/>
<path
android:pathData="M46.894375 27.44h13.96v-5h-13.96v-6.6h15.08v-5h-21.08V40h21.8v-5h-15.8z"
android:fillColor="#fff"/>
<path
android:pathData="M80.333125 40l10 -29.16h-6.04l-6.36 21.96 -6.48 -21.96h-6.04l9.84 29.16z"
android:fillColor="#fff"/>
</group>
</vector>

View file

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<shortcuts xmlns:android="http://schemas.android.com/apk/res/android">
<shortcut
android:enabled="true"
android:icon="@drawable/ic_add_24dp"
android:shortcutId="it.niedermann.nextcloud.deck"
android:shortcutLongLabel="@string/add_card"
android:shortcutShortLabel="@string/simple_add">
<intent
android:action="android.intent.action.VIEW"
android:targetClass="it.niedermann.nextcloud.deck.ui.preparecreate.PrepareCreateActivity"
android:targetPackage="it.niedermann.nextcloud.deck.dev" />
<categories android:name="android.shortcut.conversation" />
</shortcut>
</shortcuts>

View file

@ -0,0 +1,264 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="32" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_CONTACTS" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-feature android:name="android.hardware.camera.any" android:required="false" />
<uses-sdk tools:overrideLibrary="androidx.camera.core, androidx.camera.camera2, androidx.camera.lifecycle, androidx.camera.view" />
<queries>
<package android:name="com.nextcloud.client" />
<package android:name="com.nextcloud.android.qa" />
<package android:name="com.nextcloud.android.beta" />
</queries>
<application
android:name="it.niedermann.nextcloud.deck.DeckApplication"
android:allowBackup="false"
android:fullBackupContent="false"
android:hardwareAccelerated="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:networkSecurityConfig="@xml/network_security_config"
android:supportsRtl="true"
android:theme="@style/AppTheme"
android:enableOnBackInvokedCallback="true"
tools:ignore="GoogleAppIndexingWarning"
tools:targetApi="tiramisu">
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.provider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/provider_paths" />
</provider>
<activity
android:name=".ui.main.MainActivity"
android:label="@string/app_name_short"
android:theme="@style/SplashTheme"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<meta-data
android:name="android.app.shortcuts"
android:resource="@xml/shortcuts" />
<meta-data
android:name="android.app.searchable"
android:resource="@xml/searchable" />
<meta-data
android:name="android.app.default_searchable"
android:value=".ui.MainActivity" />
</activity>
<activity
android:name=".ui.manageaccounts.ManageAccountsActivity"
android:label="@string/manage_accounts"
android:parentActivityName=".ui.main.MainActivity"
android:windowSoftInputMode="stateHidden" />
<activity
android:name=".ui.takephoto.TakePhotoActivity"
android:theme="@style/TakePhotoTheme"
android:windowSoftInputMode="stateHidden" />
<activity
android:name=".ui.sharetarget.ShareTargetActivity"
android:label="@string/share_add_to_card"
android:theme="@style/SplashTheme"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="*/*" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.SEND_MULTIPLE" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="*/*" />
</intent-filter>
</activity>
<activity
android:name=".ui.archivedboards.ArchivedBoardsActivity"
android:label="@string/archived_boards"
android:parentActivityName="it.niedermann.nextcloud.deck.ui.main.MainActivity" />
<activity
android:name=".ui.upcomingcards.UpcomingCardsActivity"
android:label="@string/widget_upcoming_title"
android:parentActivityName="it.niedermann.nextcloud.deck.ui.main.MainActivity" />
<activity
android:name=".ui.card.EditActivity"
android:label="@string/edit"
android:parentActivityName="it.niedermann.nextcloud.deck.ui.main.MainActivity" />
<activity
android:name=".ui.attachments.AttachmentsActivity"
android:label="@string/attachments"
android:parentActivityName="it.niedermann.nextcloud.deck.ui.card.EditActivity"
android:theme="@style/TransparentTheme" />
<activity
android:name=".ui.settings.SettingsActivity"
android:label="@string/simple_settings"
android:parentActivityName="it.niedermann.nextcloud.deck.ui.main.MainActivity" />
<activity
android:name=".ui.ImportAccountActivity"
android:label="@string/app_name" />
<activity
android:name=".ui.preparecreate.PrepareCreateActivity"
android:description="@string/add_a_new_card_using_the_button"
android:label="@string/add_card"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="text/*" />
</intent-filter>
</activity>
<activity
android:name=".ui.about.AboutActivity"
android:label="@string/about"
android:parentActivityName="it.niedermann.nextcloud.deck.ui.main.MainActivity" />
<activity
android:name=".ui.PushNotificationActivity"
android:label="@string/app_name"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
</intent-filter>
</activity>
<activity
android:name=".ui.exception.ExceptionActivity"
android:process=":error_activity" />
<!-- <receiver-->
<!-- android:name="it.niedermann.nextcloud.deck.ui.widget.filter.FilterWidget"-->
<!-- android:label="@string/widget_filter_title">-->
<!-- <intent-filter>-->
<!-- <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />-->
<!-- </intent-filter>-->
<!-- <meta-data-->
<!-- android:name="android.appwidget.provider"-->
<!-- android:resource="@xml/filter_widget_provider" />-->
<!-- </receiver>-->
<service
android:name=".ui.widget.upcoming.UpcomingWidgetService"
android:permission="android.permission.BIND_REMOTEVIEWS" />
<receiver
android:name="it.niedermann.nextcloud.deck.ui.widget.upcoming.UpcomingWidget"
android:label="@string/widget_upcoming_title"
android:exported="true">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
</intent-filter>
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/upcoming_widget_provider" />
</receiver>
<activity android:name=".ui.widget.stack.StackWidgetConfigurationActivity"
android:exported="true">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_CONFIGURE" />
</intent-filter>
</activity>
<receiver
android:name="it.niedermann.nextcloud.deck.ui.widget.stack.StackWidget"
android:label="@string/widget_stack_title"
android:exported="true">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
</intent-filter>
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/stack_widget_provider" />
</receiver>
<service
android:name=".ui.tiles.EditCardTileService"
android:description="@string/add_a_new_card_using_the_button"
android:icon="@drawable/ic_app_logo"
android:label="@string/add_card"
android:permission="android.permission.BIND_QUICK_SETTINGS_TILE"
android:exported="true">
<intent-filter>
<action android:name="android.service.quicksettings.action.QS_TILE" />
</intent-filter>
</service>
<service
android:name=".ui.widget.stack.StackWidgetService"
android:permission="android.permission.BIND_REMOTEVIEWS" />
<activity
android:name=".ui.widget.singlecard.SelectCardForWidgetActivity"
android:label="@string/share_add_to_card"
android:theme="@style/SplashTheme"
android:exported="true">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_CONFIGURE" />
</intent-filter>
</activity>
<service
android:name=".ui.widget.singlecard.SingleCardWidgetService"
android:permission="android.permission.BIND_REMOTEVIEWS" />
<receiver
android:name=".ui.widget.singlecard.SingleCardWidget"
android:label="@string/single_card"
android:exported="true">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
</intent-filter>
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/widget_single_card_info" />
</receiver>
<!-- Trigger Google Play services to install the backported photo picker module. -->
<!-- https://developer.android.com/training/data-storage/shared/photopicker#device-availability -->
<!--suppress AndroidDomInspection -->
<service android:name="com.google.android.gms.metadata.ModuleDependencies"
android:enabled="false" android:exported="false" tools:ignore="MissingClass">
<intent-filter>
<action android:name="com.google.android.gms.metadata.MODULE_DEPENDENCIES" />
</intent-filter>
<meta-data android:name="photopicker_activity:0:required" android:value="" />
</service>
</application>
</manifest>

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

View file

@ -0,0 +1,52 @@
package it.niedermann.nextcloud.deck;
import android.app.Application;
import android.os.StrictMode;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import it.niedermann.nextcloud.deck.repository.PreferencesRepository;
import it.niedermann.nextcloud.deck.util.CustomAppGlideModule;
public class DeckApplication extends Application {
private final ExecutorService executor = new ThreadPoolExecutor(0, 2, 0L, TimeUnit.SECONDS, new SynchronousQueue<>());
@Override
public void onCreate() {
final var repo = new PreferencesRepository(this);
if (BuildConfig.DEBUG) {
enableStrictModeLogging();
}
repo.getAppThemeSetting().thenAcceptAsync(repo::setAppTheme, executor);
repo.isDebugModeEnabled().thenAcceptAsync(DeckLog::enablePersistentLogs, executor);
super.onCreate();
}
@Override
public void onLowMemory() {
super.onLowMemory();
DeckLog.error("--- Low memory: Clear Glide cache ---");
CustomAppGlideModule.clearCache(this);
DeckLog.error("--- Low memory: Clear debug log ---");
DeckLog.clearDebugLog();
}
private void enableStrictModeLogging() {
StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
.detectAll()
.permitDiskReads()
.penaltyLog()
.build());
StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()
.detectAll()
.penaltyLog()
.build());
}
}

View file

@ -0,0 +1,148 @@
package it.niedermann.nextcloud.deck;
import android.content.Context;
import android.content.Intent;
import android.text.TextUtils;
import android.util.Log;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.content.FileProvider;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.time.Instant;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import it.niedermann.nextcloud.deck.util.MimeTypeUtil;
public class DeckLog {
private DeckLog() {
throw new UnsupportedOperationException("This class must not get instantiated");
}
private static final StringBuffer DEBUG_LOG = new StringBuffer();
private static boolean PERSIST_LOGS = false;
private static final String TAG = DeckLog.class.getSimpleName();
private static final DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
public static void enablePersistentLogs(boolean persistLogs) {
PERSIST_LOGS = persistLogs;
if (!persistLogs) {
clearDebugLog();
}
}
public static String getStacktraceAsString(Throwable e) {
final var sw = new StringWriter();
e.printStackTrace(new PrintWriter(sw));
return sw.toString();
}
public enum Severity {
VERBOSE, DEBUG, LOG, INFO, WARN, ERROR, WTF
}
public static void verbose(Object... message) {
log(Severity.VERBOSE, 4, message);
}
public static void log(Object... message) {
log(Severity.DEBUG, 4, message);
}
public static void info(Object... message) {
log(Severity.INFO, 4, message);
}
public static void warn(Object... message) {
log(Severity.WARN, 4, message);
}
public static void error(Object... message) {
log(Severity.ERROR, 4, message);
}
public static void wtf(Object... message) {
log(Severity.WTF, 4, message);
}
public static void log(@NonNull Severity severity, Object... message) {
log(severity, 3, message);
}
private static void log(@NonNull Severity severity, int stackTracePosition, Object... messages) {
if (!(PERSIST_LOGS || BuildConfig.DEBUG)) {
return;
}
final StackTraceElement caller = Thread.currentThread().getStackTrace()[stackTracePosition];
final String print = "(" + caller.getFileName() + ":" + caller.getLineNumber() + ") " + caller.getMethodName() + "() → " + TextUtils.join(" ", messages);
if (PERSIST_LOGS) {
DEBUG_LOG
.append(dtf.format(Instant.now().atZone(ZoneId.systemDefault())))
.append(" ")
.append(severity.name())
.append(" ")
.append(print)
.append("\n");
}
switch (severity) {
case DEBUG -> Log.d(TAG, print);
case INFO -> Log.i(TAG, print);
case WARN -> Log.w(TAG, print);
case ERROR -> Log.e(TAG, print);
case WTF -> Log.wtf(TAG, print);
default -> Log.v(TAG, print);
}
}
public static void logError(@Nullable Throwable e) {
if (!(PERSIST_LOGS || BuildConfig.DEBUG)) {
return;
}
if (e == null) {
error("Could not log error because given error was null");
return;
}
final StringWriter sw = new StringWriter();
e.printStackTrace(new PrintWriter(sw));
final String stacktrace = sw.toString();
final StackTraceElement caller = Thread.currentThread().getStackTrace()[3];
final String print = "(" + caller.getFileName() + ":" + caller.getLineNumber() + ") " + caller.getMethodName() + "() → " + stacktrace;
if (PERSIST_LOGS) {
DEBUG_LOG.append(print).append("\n");
}
Log.e(TAG, print);
}
@NonNull
public static String getDebugLog() {
return DEBUG_LOG.toString();
}
public static void clearDebugLog() {
DEBUG_LOG.setLength(0);
}
/**
* Writes the current log to a temporary file and starts a share intent.
*/
public static void shareLogAsFile(@NonNull Context context) throws IOException {
Toast.makeText(context, R.string.copying_logs_to_file, Toast.LENGTH_LONG).show();
final File logFile = new File(context.getCacheDir().getAbsolutePath() + "/log.txt");
final FileWriter writer = new FileWriter(logFile);
writer.write(DeckLog.getDebugLog());
writer.close();
context.startActivity(new Intent(Intent.ACTION_SEND)
.putExtra(Intent.EXTRA_TITLE, context.getString(R.string.log_file))
.putExtra(Intent.EXTRA_STREAM, FileProvider.getUriForFile(context, BuildConfig.APPLICATION_ID + ".provider", logFile))
.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
.setType(MimeTypeUtil.TEXT_PLAIN));
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,262 @@
package it.niedermann.nextcloud.deck.database;
import android.content.Context;
import androidx.annotation.NonNull;
import androidx.room.Database;
import androidx.room.Room;
import androidx.room.RoomDatabase;
import androidx.room.TypeConverters;
import androidx.sqlite.db.SupportSQLiteDatabase;
import it.niedermann.nextcloud.deck.DeckLog;
import it.niedermann.nextcloud.deck.database.converter.DateTypeConverter;
import it.niedermann.nextcloud.deck.database.converter.EnumConverter;
import it.niedermann.nextcloud.deck.database.dao.AccessControlDao;
import it.niedermann.nextcloud.deck.database.dao.AccountDao;
import it.niedermann.nextcloud.deck.database.dao.ActivityDao;
import it.niedermann.nextcloud.deck.database.dao.AttachmentDao;
import it.niedermann.nextcloud.deck.database.dao.BoardDao;
import it.niedermann.nextcloud.deck.database.dao.CardDao;
import it.niedermann.nextcloud.deck.database.dao.CommentDao;
import it.niedermann.nextcloud.deck.database.dao.JoinBoardWithLabelDao;
import it.niedermann.nextcloud.deck.database.dao.JoinBoardWithPermissionDao;
import it.niedermann.nextcloud.deck.database.dao.JoinBoardWithUserDao;
import it.niedermann.nextcloud.deck.database.dao.JoinCardWithLabelDao;
import it.niedermann.nextcloud.deck.database.dao.JoinCardWithUserDao;
import it.niedermann.nextcloud.deck.database.dao.LabelDao;
import it.niedermann.nextcloud.deck.database.dao.MentionDao;
import it.niedermann.nextcloud.deck.database.dao.PermissionDao;
import it.niedermann.nextcloud.deck.database.dao.StackDao;
import it.niedermann.nextcloud.deck.database.dao.UserDao;
import it.niedermann.nextcloud.deck.database.dao.UserInBoardDao;
import it.niedermann.nextcloud.deck.database.dao.UserInGroupDao;
import it.niedermann.nextcloud.deck.database.dao.projects.JoinCardWithOcsProjectDao;
import it.niedermann.nextcloud.deck.database.dao.projects.OcsProjectDao;
import it.niedermann.nextcloud.deck.database.dao.projects.OcsProjectResourceDao;
import it.niedermann.nextcloud.deck.database.dao.widgets.SingleCardWidgetModelDao;
import it.niedermann.nextcloud.deck.database.dao.widgets.filter.FilterWidgetAccountDao;
import it.niedermann.nextcloud.deck.database.dao.widgets.filter.FilterWidgetBoardDao;
import it.niedermann.nextcloud.deck.database.dao.widgets.filter.FilterWidgetDao;
import it.niedermann.nextcloud.deck.database.dao.widgets.filter.FilterWidgetLabelDao;
import it.niedermann.nextcloud.deck.database.dao.widgets.filter.FilterWidgetProjectDao;
import it.niedermann.nextcloud.deck.database.dao.widgets.filter.FilterWidgetSortDao;
import it.niedermann.nextcloud.deck.database.dao.widgets.filter.FilterWidgetStackDao;
import it.niedermann.nextcloud.deck.database.dao.widgets.filter.FilterWidgetUserDao;
import it.niedermann.nextcloud.deck.database.migration.Migration_10_11;
import it.niedermann.nextcloud.deck.database.migration.Migration_11_12;
import it.niedermann.nextcloud.deck.database.migration.Migration_12_13;
import it.niedermann.nextcloud.deck.database.migration.Migration_13_14;
import it.niedermann.nextcloud.deck.database.migration.Migration_14_15;
import it.niedermann.nextcloud.deck.database.migration.Migration_15_16;
import it.niedermann.nextcloud.deck.database.migration.Migration_16_17;
import it.niedermann.nextcloud.deck.database.migration.Migration_17_18;
import it.niedermann.nextcloud.deck.database.migration.Migration_18_19;
import it.niedermann.nextcloud.deck.database.migration.Migration_19_20;
import it.niedermann.nextcloud.deck.database.migration.Migration_20_21;
import it.niedermann.nextcloud.deck.database.migration.Migration_21_22;
import it.niedermann.nextcloud.deck.database.migration.Migration_22_23;
import it.niedermann.nextcloud.deck.database.migration.Migration_23_24;
import it.niedermann.nextcloud.deck.database.migration.Migration_24_25;
import it.niedermann.nextcloud.deck.database.migration.Migration_25_26;
import it.niedermann.nextcloud.deck.database.migration.Migration_26_27;
import it.niedermann.nextcloud.deck.database.migration.Migration_27_28;
import it.niedermann.nextcloud.deck.database.migration.Migration_28_29;
import it.niedermann.nextcloud.deck.database.migration.Migration_29_30;
import it.niedermann.nextcloud.deck.database.migration.Migration_30_31;
import it.niedermann.nextcloud.deck.database.migration.Migration_31_32;
import it.niedermann.nextcloud.deck.database.migration.Migration_32_33;
import it.niedermann.nextcloud.deck.database.migration.Migration_33_34;
import it.niedermann.nextcloud.deck.database.migration.Migration_8_9;
import it.niedermann.nextcloud.deck.database.migration.Migration_9_10;
import it.niedermann.nextcloud.deck.model.AccessControl;
import it.niedermann.nextcloud.deck.model.Account;
import it.niedermann.nextcloud.deck.model.Attachment;
import it.niedermann.nextcloud.deck.model.Board;
import it.niedermann.nextcloud.deck.model.Card;
import it.niedermann.nextcloud.deck.model.JoinBoardWithLabel;
import it.niedermann.nextcloud.deck.model.JoinBoardWithPermission;
import it.niedermann.nextcloud.deck.model.JoinBoardWithUser;
import it.niedermann.nextcloud.deck.model.JoinCardWithLabel;
import it.niedermann.nextcloud.deck.model.JoinCardWithUser;
import it.niedermann.nextcloud.deck.model.Label;
import it.niedermann.nextcloud.deck.model.Permission;
import it.niedermann.nextcloud.deck.model.Stack;
import it.niedermann.nextcloud.deck.model.User;
import it.niedermann.nextcloud.deck.model.ocs.Activity;
import it.niedermann.nextcloud.deck.model.ocs.comment.DeckComment;
import it.niedermann.nextcloud.deck.model.ocs.comment.Mention;
import it.niedermann.nextcloud.deck.model.ocs.projects.JoinCardWithProject;
import it.niedermann.nextcloud.deck.model.ocs.projects.OcsProject;
import it.niedermann.nextcloud.deck.model.ocs.projects.OcsProjectResource;
import it.niedermann.nextcloud.deck.model.relations.UserInBoard;
import it.niedermann.nextcloud.deck.model.relations.UserInGroup;
import it.niedermann.nextcloud.deck.model.widget.filter.FilterWidget;
import it.niedermann.nextcloud.deck.model.widget.filter.FilterWidgetAccount;
import it.niedermann.nextcloud.deck.model.widget.filter.FilterWidgetBoard;
import it.niedermann.nextcloud.deck.model.widget.filter.FilterWidgetLabel;
import it.niedermann.nextcloud.deck.model.widget.filter.FilterWidgetProject;
import it.niedermann.nextcloud.deck.model.widget.filter.FilterWidgetSort;
import it.niedermann.nextcloud.deck.model.widget.filter.FilterWidgetStack;
import it.niedermann.nextcloud.deck.model.widget.filter.FilterWidgetUser;
import it.niedermann.nextcloud.deck.model.widget.singlecard.SingleCardWidgetModel;
import it.niedermann.nextcloud.deck.remote.api.LastSyncUtil;
@Database(
entities = {
Account.class,
Attachment.class,
AccessControl.class,
Board.class,
Card.class,
JoinBoardWithLabel.class,
JoinBoardWithPermission.class,
JoinBoardWithUser.class,
JoinCardWithLabel.class,
JoinCardWithUser.class,
Label.class,
Permission.class,
Stack.class,
User.class,
Activity.class,
DeckComment.class,
Mention.class,
SingleCardWidgetModel.class,
OcsProject.class,
OcsProjectResource.class,
JoinCardWithProject.class,
UserInGroup.class,
UserInBoard.class,
FilterWidget.class,
FilterWidgetAccount.class,
FilterWidgetBoard.class,
FilterWidgetStack.class,
FilterWidgetLabel.class,
FilterWidgetUser.class,
FilterWidgetProject.class,
FilterWidgetSort.class,
},
exportSchema = false,
version = 34
)
@TypeConverters({DateTypeConverter.class, EnumConverter.class})
public abstract class DeckDatabase extends RoomDatabase {
private static final String DECK_DB_NAME = "NC_DECK_DB.db";
private static volatile DeckDatabase instance;
public static final RoomDatabase.Callback ON_CREATE_CALLBACK = new RoomDatabase.Callback() {
@Override
public void onCreate(@NonNull SupportSQLiteDatabase db) {
super.onCreate(db);
DeckLog.info("Database", DECK_DB_NAME, "created.");
LastSyncUtil.resetAll();
}
};
public static synchronized DeckDatabase getInstance(Context context) {
if (instance == null) {
instance = create(context);
}
return instance;
}
private static DeckDatabase create(final Context context) {
return Room.databaseBuilder(
context,
DeckDatabase.class,
DECK_DB_NAME)
.addMigrations(new Migration_8_9())
.addMigrations(new Migration_9_10())
.addMigrations(new Migration_10_11())
.addMigrations(new Migration_11_12())
.addMigrations(new Migration_12_13())
.addMigrations(new Migration_13_14())
.addMigrations(new Migration_14_15(context))
.addMigrations(new Migration_15_16())
.addMigrations(new Migration_16_17())
.addMigrations(new Migration_17_18())
.addMigrations(new Migration_18_19())
.addMigrations(new Migration_19_20())
.addMigrations(new Migration_20_21())
.addMigrations(new Migration_21_22(context))
.addMigrations(new Migration_22_23())
.addMigrations(new Migration_23_24(context))
.addMigrations(new Migration_24_25())
.addMigrations(new Migration_25_26())
.addMigrations(new Migration_26_27())
.addMigrations(new Migration_27_28())
.addMigrations(new Migration_28_29())
.addMigrations(new Migration_29_30(context))
.addMigrations(new Migration_30_31())
.addMigrations(new Migration_31_32(context))
.addMigrations(new Migration_32_33())
.addMigrations(new Migration_33_34())
.fallbackToDestructiveMigration()
.addCallback(ON_CREATE_CALLBACK)
.build();
}
public abstract AccountDao getAccountDao();
public abstract AccessControlDao getAccessControlDao();
public abstract BoardDao getBoardDao();
public abstract CardDao getCardDao();
public abstract JoinBoardWithLabelDao getJoinBoardWithLabelDao();
public abstract JoinBoardWithPermissionDao getJoinBoardWithPermissionDao();
public abstract JoinBoardWithUserDao getJoinBoardWithUserDao();
public abstract JoinCardWithLabelDao getJoinCardWithLabelDao();
public abstract JoinCardWithUserDao getJoinCardWithUserDao();
public abstract LabelDao getLabelDao();
public abstract ActivityDao getActivityDao();
public abstract PermissionDao getPermissionDao();
public abstract StackDao getStackDao();
public abstract UserDao getUserDao();
public abstract AttachmentDao getAttachmentDao();
public abstract CommentDao getCommentDao();
public abstract MentionDao getMentionDao();
public abstract SingleCardWidgetModelDao getSingleCardWidgetModelDao();
public abstract OcsProjectDao getOcsProjectDao();
public abstract OcsProjectResourceDao getOcsProjectResourceDao();
public abstract JoinCardWithOcsProjectDao getJoinCardWithOcsProjectDao();
public abstract UserInGroupDao getUserInGroupDao();
public abstract UserInBoardDao getUserInBoardDao();
public abstract FilterWidgetDao getFilterWidgetDao();
public abstract FilterWidgetAccountDao getFilterWidgetAccountDao();
public abstract FilterWidgetBoardDao getFilterWidgetBoardDao();
public abstract FilterWidgetStackDao getFilterWidgetStackDao();
public abstract FilterWidgetLabelDao getFilterWidgetLabelDao();
public abstract FilterWidgetUserDao getFilterWidgetUserDao();
public abstract FilterWidgetProjectDao getFilterWidgetProjectDao();
public abstract FilterWidgetSortDao getFilterWidgetSortDao();
}

View file

@ -0,0 +1,18 @@
package it.niedermann.nextcloud.deck.database.converter;
import androidx.room.TypeConverter;
import java.time.Instant;
public class DateTypeConverter {
@TypeConverter
public static Instant toInstant(Long value) {
return value == null ? null : Instant.ofEpochMilli(value);
}
@TypeConverter
public static Long fromInstant(Instant value) {
return value == null ? null : value.toEpochMilli();
}
}

View file

@ -0,0 +1,67 @@
package it.niedermann.nextcloud.deck.database.converter;
import androidx.annotation.Nullable;
import androidx.room.TypeConverter;
import it.niedermann.nextcloud.deck.DeckLog;
import it.niedermann.nextcloud.deck.model.enums.EAttachmentType;
import it.niedermann.nextcloud.deck.model.enums.EDueType;
import it.niedermann.nextcloud.deck.model.enums.ESortCriteria;
import it.niedermann.nextcloud.deck.model.widget.filter.EWidgetType;
public class EnumConverter {
// #### EWidgetType
@TypeConverter
public static EWidgetType toWidgetTypeEnum(Integer value) {
try {
return value == null ? null : EWidgetType.findById(value);
} catch (IllegalArgumentException e) {
DeckLog.error(EWidgetType.class.getSimpleName(), value, "not found. Falling back to generic", EWidgetType.FILTER_WIDGET);
return EWidgetType.FILTER_WIDGET;
}
}
@TypeConverter
public static Integer fromWidgetTypeEnum(EWidgetType value) {
return value == null ? null : value.getId();
}
// #### EDueType
@TypeConverter
@Nullable
public static EDueType toDueTypeEnum(@Nullable Integer value) {
return value == null ? null : EDueType.findById(value);
}
@TypeConverter
@Nullable
public static Integer fromDueTypeEnum(@Nullable EDueType value) {
return value == null ? null : value.getId();
}
// #### ESortCriteria
@TypeConverter
@Nullable
public static ESortCriteria toSortCriteriaEnum(@Nullable Integer value) {
return value == null ? null : ESortCriteria.findById(value);
}
@TypeConverter
@Nullable
public static Integer fromSortCriteriaEnum(@Nullable ESortCriteria value) {
return value == null ? null : value.getId();
}
// #### EAttachmentType
@TypeConverter
@Nullable
public static EAttachmentType toEAttachmentType(@Nullable String value) {
return value == null ? null : EAttachmentType.findByValue(value);
}
@TypeConverter
@Nullable
public static String fromEAttachmentType(@Nullable EAttachmentType value) {
return value == null ? null : value.getValue();
}
}

View file

@ -0,0 +1,35 @@
package it.niedermann.nextcloud.deck.database.dao;
import androidx.lifecycle.LiveData;
import androidx.room.Dao;
import androidx.room.Query;
import java.util.List;
import java.util.Set;
import it.niedermann.nextcloud.deck.model.AccessControl;
@Dao
public interface AccessControlDao extends GenericDao<AccessControl> {
@Query("SELECT * FROM AccessControl WHERE accountId = :accountId and id = :remoteId")
LiveData<AccessControl> getAccessControlByRemoteId(final long accountId, final long remoteId);
@Query("SELECT * FROM AccessControl WHERE accountId = :accountId and boardId = :localBoardId and status <> 3")
LiveData<List<AccessControl>> getAccessControlByLocalBoardId(final long accountId, final long localBoardId);
@Query("SELECT * FROM AccessControl WHERE accountId = :accountId and boardId = :localBoardId and status <> 3")
List<AccessControl> getAccessControlByLocalBoardIdDirectly(final long accountId, final long localBoardId);
@Query("SELECT * FROM AccessControl WHERE accountId = :accountId and id = :remoteId")
AccessControl getAccessControlByRemoteIdDirectly(final long accountId, final long remoteId);
@Query("SELECT * FROM AccessControl WHERE accountId = :accountId and boardId = :boardId and (status<>1 or id is null or lastModified <> lastModifiedLocal)")
List<AccessControl> getLocallyChangedAccessControl(long accountId, long boardId);
@Query("SELECT distinct boardId FROM AccessControl WHERE accountId = :accountId and (status<>1 or id is null or lastModified <> lastModifiedLocal)")
List<Long> getBoardIDsOfLocallyChangedAccessControl(long accountId);
@Query("DELETE FROM AccessControl WHERE boardId = :localBoardId and localId not in (:idsToKeep)")
void deleteAccessControlsForBoardWhereLocalIdsNotInDirectly(long localBoardId, Set<Long> idsToKeep);
}

View file

@ -0,0 +1,51 @@
package it.niedermann.nextcloud.deck.database.dao;
import androidx.lifecycle.LiveData;
import androidx.room.Dao;
import androidx.room.Query;
import java.util.List;
import it.niedermann.nextcloud.deck.model.Account;
@Dao
public interface AccountDao extends GenericDao<Account> {
@Query("SELECT count(*) FROM account")
int countAccountsDirectly();
@Query("SELECT count(*) FROM account")
LiveData<Integer> countAccounts();
@Query("DELETE from account where id = :id")
void deleteById(long id);
@Query("SELECT * from account where id = :id")
Account getAccountByIdDirectly(long id);
@Query("SELECT * from account where id = :id")
LiveData<Account> getAccountById(long id);
@Query("SELECT * from account where name = :name")
LiveData<Account> getAccountByName(String name);
@Query("SELECT * from account where name = :name")
Account getAccountByNameDirectly(String name);
@Query("SELECT * from account")
LiveData<List<Account>> getAllAccounts();
@Query("SELECT * from account")
List<Account> getAllAccountsDirectly();
@Query("SELECT * from account a where a.url like :hostLike and exists (select 1 from board b where b.id = :boardRemoteId and a.id = b.accountId)")
LiveData<List<Account>> readAccountsForHostWithReadAccessToBoard(String hostLike, long boardRemoteId);
@Query("SELECT * from account a where a.url like :hostLike and exists (select 1 from board b where b.id = :boardRemoteId and a.id = b.accountId)")
List<Account> readAccountsForHostWithReadAccessToBoardDirectly(String hostLike, long boardRemoteId);
@Query("SELECT a.color FROM account a where a.id = :accountId")
LiveData<Integer> getAccountColor(long accountId);
@Query("SELECT a.color FROM account a where a.id = :accountId")
Integer getAccountColorDirectly(long accountId);
}

View file

@ -0,0 +1,19 @@
package it.niedermann.nextcloud.deck.database.dao;
import androidx.lifecycle.LiveData;
import androidx.room.Dao;
import androidx.room.Query;
import java.util.List;
import it.niedermann.nextcloud.deck.model.ocs.Activity;
@Dao
public interface ActivityDao extends GenericDao<Activity> {
@Query("SELECT * FROM activity WHERE cardId = :localCardId order by lastModified desc")
LiveData<List<Activity>> getActivitiesForCard(final long localCardId);
@Query("SELECT * FROM activity WHERE accountId = :accountId and id = :remoteActivityId")
Activity getActivityByRemoteIdDirectly(long accountId, long remoteActivityId);
}

View file

@ -0,0 +1,34 @@
package it.niedermann.nextcloud.deck.database.dao;
import androidx.lifecycle.LiveData;
import androidx.room.Dao;
import androidx.room.Query;
import java.util.List;
import it.niedermann.nextcloud.deck.model.Attachment;
@Dao
public interface AttachmentDao extends GenericDao<Attachment> {
@Query("SELECT * FROM attachment where cardId = :cardId")
LiveData<List<Attachment>> getAttachmentsForCard(long cardId);
@Query("SELECT * FROM attachment where accountId = :accountId and id = :remoteId")
Attachment getAttachmentByRemoteIdDirectly(long accountId, Long remoteId);
@Query("SELECT * FROM attachment where accountId = :accountId and localId = :id")
Attachment getAttachmentByLocalIdDirectly(long accountId, Long id);
@Query("SELECT * FROM attachment WHERE accountId = :accountId and cardId = :localCardId and (status<>1 or id is null or lastModified <> lastModifiedLocal)")
List<Attachment> getLocallyChangedAttachmentsByLocalCardIdDirectly(long accountId, long localCardId);
@Query("SELECT * FROM attachment WHERE accountId = :accountId and (status<>1 or id is null or lastModified <> lastModifiedLocal)")
List<Attachment> getLocallyChangedAttachmentsDirectly(long accountId);
@Query("SELECT a.* FROM attachment a inner join card c on c.localId = a.cardId " +
"WHERE c.stackId = :localStackId and (a.status<>1 or a.id is null or a.lastModified <> a.lastModifiedLocal)")
List<Attachment> getLocallyChangedAttachmentsForStackDirectly(long localStackId);
@Query("SELECT * FROM attachment WHERE accountId = :accountId and cardId = :localCardId")
List<Attachment> getAttachmentsForLocalCardIdDirectly(long accountId, Long localCardId);
}

View file

@ -0,0 +1,93 @@
package it.niedermann.nextcloud.deck.database.dao;
import androidx.lifecycle.LiveData;
import androidx.room.Dao;
import androidx.room.Query;
import androidx.room.Transaction;
import java.util.List;
import it.niedermann.nextcloud.deck.model.Board;
import it.niedermann.nextcloud.deck.model.full.FullBoard;
@Dao
public interface BoardDao extends GenericDao<Board> {
@Transaction
@Query("SELECT * FROM board WHERE accountId = :accountId and archived = :archived and (deletedAt = 0 or deletedAt is null) and status <> 3 order by title asc")
LiveData<List<Board>> getNotDeletedBoards(long accountId, int archived);
@Transaction
@Query("SELECT * FROM board WHERE accountId = :accountId and archived = :archived and (deletedAt = 0 or deletedAt is null) and status <> 3 order by title asc")
List<Board> getNotDeletedBoardsDirectly(long accountId, int archived);
@Transaction
@Query("SELECT * FROM board WHERE accountId = :accountId and archived = :archived and (deletedAt = 0 or deletedAt is null) and status <> 3 order by title asc")
LiveData<List<FullBoard>> getNotDeletedFullBoards(long accountId, int archived);
@Query("SELECT * FROM board WHERE accountId = :accountId and id = :remoteId")
LiveData<Board> getBoardByRemoteId(final long accountId, final long remoteId);
@Query("SELECT * FROM board WHERE accountId = :accountId and id = :remoteId")
Board getBoardByRemoteIdDirectly(long accountId, long remoteId);
@Query("SELECT * FROM board WHERE localId = :localId")
Board getBoardByLocalIdDirectly(long localId);
@Transaction
@Query("SELECT * FROM board WHERE accountId = :accountId and id = :remoteId")
FullBoard getFullBoardByRemoteIdDirectly(long accountId, long remoteId);
@Transaction
@Query("SELECT * FROM board WHERE accountId = :accountId and localId = :localId")
FullBoard getFullBoardByLocalIdDirectly(long accountId, long localId);
@Transaction
@Query("SELECT * FROM board WHERE accountId = :accountId and (status<>1 or id is null or lastModified <> lastModifiedLocal)")
List<FullBoard> getLocallyChangedBoardsDirectly(long accountId);
@Transaction
@Query("SELECT * FROM board WHERE accountId = :accountId and localId = :localId")
LiveData<FullBoard> getFullBoardById(final long accountId, final long localId);
@Query("SELECT b.* FROM board b JOIN stack s ON s.boardId = b.localId JOIN card c ON s.localId = c.stackId where c.localId = :localCardId")
Board getBoardByLocalCardIdDirectly(long localCardId);
@Query("SELECT b.localId FROM board b JOIN stack s ON s.boardId = b.localId JOIN card c ON s.localId = c.stackId where c.localId = :localCardId")
Long getBoardLocalIdByLocalCardIdDirectly(long localCardId);
@Transaction
@Query("SELECT b.* FROM board b JOIN stack s ON s.boardId = b.localId JOIN card c ON c.localId = :localCardId and c.stackId = s.localId")
FullBoard getFullBoardByLocalCardIdDirectly(long localCardId);
@Transaction
@Query("SELECT * FROM board WHERE accountId = :accountId")
List<FullBoard> getAllFullBoards(long accountId);
@Query("SELECT * FROM board WHERE accountId = :accountId and archived = 0 and permissionEdit = 1 and (deletedAt = 0 or deletedAt is null) and status <> 3 order by title asc")
LiveData<List<Board>> getBoardsWithEditPermissionsForAccount(long accountId);
@Query("SELECT s.boardId " +
"FROM card c " +
"inner join stack s on s.localId = c.stackId " +
"WHERE c.id = :cardRemoteId and c.accountId = :accountId")
LiveData<Long> getLocalBoardIdByCardRemoteIdAndAccountId(long cardRemoteId, long accountId);
@Query("SELECT s.boardId " +
"FROM card c " +
"inner join stack s on s.localId = c.stackId " +
"WHERE c.id = :cardRemoteId and c.accountId = :accountId")
Long getBoardLocalIdByAccountAndCardRemoteIdDirectly(long accountId, long cardRemoteId);
@Query("SELECT count(*) FROM board WHERE accountId = :accountId and archived = 1 and (deletedAt = 0 or deletedAt is null) and status <> 3")
LiveData<Integer> countArchivedBoards(long accountId);
@Query("SELECT * FROM board WHERE accountId = :accountId and title = :title")
Board getBoardForAccountByNameDirectly(long accountId, String title);
@Query("SELECT b.color FROM board b where b.localId = :localBoardId and b.accountId = :accountId")
Integer getBoardColorByLocalIdDirectly(long accountId, long localBoardId);
@Query("SELECT b.color FROM board b where b.localId = :localBoardId and b.accountId = :accountId")
LiveData<Integer> getBoardColor(long accountId, long localBoardId);
}

View file

@ -0,0 +1,145 @@
package it.niedermann.nextcloud.deck.database.dao;
import androidx.lifecycle.LiveData;
import androidx.room.Dao;
import androidx.room.Query;
import androidx.room.RawQuery;
import androidx.room.Transaction;
import androidx.sqlite.db.SupportSQLiteQuery;
import java.util.List;
import it.niedermann.nextcloud.deck.model.Card;
import it.niedermann.nextcloud.deck.model.full.FullCard;
import it.niedermann.nextcloud.deck.model.full.FullCardWithProjects;
@Dao
public interface CardDao extends GenericDao<Card> {
String QUERY_UPCOMING_CARDS = "SELECT c.* FROM card c " +
"join stack s on s.localId = c.stackId " +
"join board b on b.localId = s.boardId " +
"WHERE b.archived = 0 and c.archived = 0 and b.status <> 3 and s.status <> 3 and c.status <> 3 " +
"and (c.deletedAt is null or c.deletedAt = 0) " +
"and (s.deletedAt is null or s.deletedAt = 0) " +
"and (b.deletedAt is null or b.deletedAt = 0) " +
"and (c.done is null or c.done = 0) " +
// Full Logic: (hasDueDate AND isIn_PRIVATE_Board) OR (isInSharedBoard AND (assignedToMe OR (hasDueDate AND noAssignees)))
"and (" +
"(c.dueDate is not null AND NOT exists(select 1 from AccessControl ac where ac.boardId = b.localId and ac.status <> 3))" + //(hasDueDate AND isInPrivateBoard)
"OR (" +
"exists(select 1 from AccessControl ac where ac.boardId = b.localId and ac.status <> 3) " + //OR (isInSharedBoard AND
"AND (" +
"(c.dueDate is not null AND not exists(select 1 from JoinCardWithUser j where j.cardId = c.localId)) " + // hasDueDate AND noAssignees OR
"OR exists(select 1 from JoinCardWithUser j where j.cardId = c.localId and j.userId in (select u.localId from user u where u.uid in (select a.userName from Account a)))" + //(assignedToMe
")" +
")" +
")" +
"ORDER BY c.dueDate asc";
@Query("SELECT * FROM card WHERE stackId = :localStackId order by `order`, createdAt asc")
LiveData<List<Card>> getCardsForStack(final long localStackId);
@Query("SELECT * FROM card WHERE accountId = :accountId and id = :remoteId")
LiveData<Card> getCardByRemoteId(final long accountId, final long remoteId);
@Transaction
@Query("SELECT * FROM card WHERE accountId = :accountId and id = :remoteId")
FullCard getFullCardByRemoteIdDirectly(final long accountId, final long remoteId);
@Query("SELECT * FROM card WHERE accountId = :accountId and localId = :localId")
Card getCardByLocalIdDirectly(final long accountId, final long localId);
@Transaction
@Query("SELECT * FROM card WHERE accountId = :accountId and localId = :localId")
FullCard getFullCardByLocalIdDirectly(final long accountId, final long localId);
@Transaction
// v not deleted!
@Query("SELECT * FROM card WHERE accountId = :accountId AND archived = 0 AND stackId = :localStackId and status<>3 order by `order`, createdAt asc")
LiveData<List<FullCard>> getFullCardsForStack(final long accountId, final long localStackId);
@Transaction
@RawQuery(observedEntities = Card.class)
LiveData<List<FullCard>> getFilteredFullCardsForStack(SupportSQLiteQuery query);
@Transaction
@RawQuery(observedEntities = Card.class)
List<FullCard> getFilteredFullCardsForStackDirectly(SupportSQLiteQuery query);
@Transaction
@Query("SELECT * FROM card WHERE accountId = :accountId AND stackId = :localStackId order by `order`, createdAt asc")
List<FullCard> getFullCardsForStackDirectly(final long accountId, final long localStackId);
@Transaction
@Query("SELECT * FROM card WHERE accountId = :accountId and localId = :localCardId")
LiveData<FullCard> getFullCardByLocalId(final long accountId, final long localCardId);
@Transaction
@Query("SELECT * FROM card WHERE accountId = :accountId and localId = :localCardId")
LiveData<FullCardWithProjects> getFullCardWithProjectsByLocalId(final long accountId, final long localCardId);
@Transaction
@Query("SELECT * FROM card WHERE accountId = :accountId and id = :remoteId")
LiveData<FullCard> getFullCardByRemoteId(final long accountId, final long remoteId);
@Query("SELECT * FROM card WHERE accountId = :accountId and id = :remoteId")
Card getCardByRemoteIdDirectly(long accountId, long remoteId);
@Transaction
@Query("SELECT * FROM card WHERE accountId = :accountId and (status<>1 or id is null or lastModified <> lastModifiedLocal)")
List<FullCard> getLocallyChangedCardsDirectly(long accountId);
@Transaction
@Query("SELECT * FROM card WHERE accountId = :accountId and stackId = :localStackId and (status<>1 or id is null or lastModified <> lastModifiedLocal)")
List<FullCard> getLocallyChangedCardsByLocalStackIdDirectly(long accountId, long localStackId);
@Query("SELECT * FROM card c WHERE accountId = :accountId and exists ( select 1 from DeckComment dc where dc.objectId = c.localId and dc.status<>1)")
List<Card> getCardsWithLocallyChangedCommentsDirectly(Long accountId);
@Query("SELECT * FROM card c WHERE stackId = :localStackId and exists ( select 1 from DeckComment dc where dc.objectId = c.localId and dc.status<>1)")
List<Card> getCardsWithLocallyChangedCommentsForStackDirectly(Long localStackId);
@Query("SELECT count(*) FROM card c WHERE accountId = :accountId and stackId = :localStackId and status <> 3")
int countCardsInStackDirectly(long accountId, long localStackId);
@Query("SELECT coalesce(MAX(`order`), -1) FROM card c WHERE stackId = :localStackId and status <> 3")
Integer getHighestOrderInStack(Long localStackId);
@Query("SELECT c.stackId FROM card c WHERE localId = :localCardId")
Long getLocalStackIdByLocalCardId(Long localCardId);
@Transaction
@Query("SELECT * FROM card c WHERE " +
"exists(select 1 from Stack s join Board b on s.boardId = b.localId where s.localId = c.stackId " +
"and b.archived = 0 " +
"and not exists(select 1 from AccessControl ac where ac.boardId = b.localId and status <> 3)) " +
"and dueDate is not null " +
"and (coalesce(:accountIds, null) is null or accountId in (:accountIds)) " +
"and status <> 3 " +
"and archived = 0")
List<FullCard> getFullCardsForNonSharedBoardsWithDueDateForUpcomingCardsWidgetDirectly(List<Long> accountIds);
@Transaction
@Query(QUERY_UPCOMING_CARDS)
LiveData<List<FullCard>> getUpcomingCards();
@Transaction
@Query(QUERY_UPCOMING_CARDS)
List<FullCard> getUpcomingCardsDirectly();
@Transaction
@Query("SELECT c.* FROM card c " +
"inner join Stack s on c.stackId = s.localId " +
"WHERE s.boardId = :localBoardId " +
"and (c.title like :term or c.description like :term) " +
"and c.accountId = :accountId " +
"and s.accountId = :accountId " +
"and c.status <> 3 " +
"and s.status <> 3 " +
"and c.archived = 0 " +
"order by s.`order`, c.`order`")
LiveData<List<FullCard>> searchCard(long accountId, long localBoardId, String term);
@Query("SELECT s.localId FROM card s")
List<Long> getAllIDs();
}

View file

@ -0,0 +1,47 @@
package it.niedermann.nextcloud.deck.database.dao;
import androidx.lifecycle.LiveData;
import androidx.room.Dao;
import androidx.room.Query;
import androidx.room.Transaction;
import java.util.List;
import it.niedermann.nextcloud.deck.model.ocs.comment.DeckComment;
import it.niedermann.nextcloud.deck.model.ocs.comment.full.FullDeckComment;
@Dao
public interface CommentDao extends GenericDao<DeckComment> {
@Query("SELECT * FROM DeckComment where accountId = :accountId and id = :remoteId")
DeckComment getCommentByRemoteIdDirectly(long accountId, Long remoteId);
@Query("SELECT * FROM DeckComment where accountId = :accountId and localId = :id")
DeckComment getCommentByLocalIdDirectly(long accountId, Long id);
@Query("SELECT * FROM DeckComment WHERE accountId = :accountId and objectId = :localCardId " +
"and (status<>1 or id is null or lastModified <> lastModifiedLocal) order by localId asc")
List<DeckComment> getLocallyChangedCommentsByLocalCardIdDirectly(long accountId, long localCardId);
@Query("SELECT * FROM DeckComment WHERE accountId = :accountId and (status<>1 or id is null or lastModified <> lastModifiedLocal)")
List<DeckComment> getLocallyChangedCommentsDirectly(long accountId);
@Query("SELECT * FROM DeckComment WHERE accountId = :accountId and objectId = :localCardId")
List<DeckComment> getCommentsForLocalCardIdDirectly(long accountId, Long localCardId);
@Query("SELECT * FROM DeckComment where objectId = :localCardId")
List<DeckComment> getCommentByLocalCardIdDirectly(Long localCardId);
@Query("SELECT * FROM DeckComment where objectId = :localCardId order by creationDateTime desc")
LiveData<List<DeckComment>> getCommentByLocalCardId(Long localCardId);
@Transaction
@Query("SELECT * FROM DeckComment where objectId = :localCardId order by creationDateTime desc, localId desc")
LiveData<List<FullDeckComment>> getFullCommentByLocalCardId(Long localCardId);
@Query("SELECT id FROM DeckComment where localId = :localId")
Long getRemoteCommentIdForLocalIdDirectly(Long localId);
@Query("SELECT localId FROM DeckComment where id = :remoteId and accountId = :accountId")
Long getLocalCommentIdForRemoteIdDirectly(long accountId, Long remoteId);
}

View file

@ -0,0 +1,23 @@
package it.niedermann.nextcloud.deck.database.dao;
import androidx.room.Delete;
import androidx.room.Insert;
import androidx.room.Update;
public interface GenericDao<T> {
@Insert
long insert(T entity);
@SuppressWarnings("unchecked")
@Insert
long[] insert(T... entity);
@SuppressWarnings("unchecked")
@Update
void update(T... entity);
@SuppressWarnings("unchecked")
@Delete
void delete(T... entity);
}

View file

@ -0,0 +1,12 @@
package it.niedermann.nextcloud.deck.database.dao;
import androidx.room.Dao;
import androidx.room.Query;
import it.niedermann.nextcloud.deck.model.JoinBoardWithLabel;
@Dao
public interface JoinBoardWithLabelDao extends GenericDao<JoinBoardWithLabel> {
@Query("DELETE FROM joinboardwithlabel WHERE boardId = :localId")
void deleteByBoardId(long localId);
}

View file

@ -0,0 +1,13 @@
package it.niedermann.nextcloud.deck.database.dao;
import androidx.room.Dao;
import androidx.room.Query;
import it.niedermann.nextcloud.deck.model.JoinBoardWithPermission;
@Dao
public interface JoinBoardWithPermissionDao extends GenericDao<JoinBoardWithPermission> {
@Query("DELETE FROM joinboardwithpermission WHERE boardId = :localId")
void deleteByBoardId(long localId);
}

View file

@ -0,0 +1,13 @@
package it.niedermann.nextcloud.deck.database.dao;
import androidx.room.Dao;
import androidx.room.Query;
import it.niedermann.nextcloud.deck.model.JoinBoardWithUser;
@Dao
public interface JoinBoardWithUserDao extends GenericDao<JoinBoardWithUser> {
@Query("DELETE FROM joinboardwithuser WHERE boardId = :localId")
void deleteByBoardId(long localId);
}

View file

@ -0,0 +1,52 @@
package it.niedermann.nextcloud.deck.database.dao;
import androidx.room.Dao;
import androidx.room.Query;
import java.util.List;
import it.niedermann.nextcloud.deck.model.JoinCardWithLabel;
@Dao
public interface JoinCardWithLabelDao extends GenericDao<JoinCardWithLabel> {
@Query("DELETE FROM joincardwithlabel WHERE cardId = :localCardId and status == 1") // only if UP_TO_DATE
void deleteByCardId(long localCardId);
@Query("DELETE FROM joincardwithlabel WHERE cardId = :localCardId and labelId = :labelId")
void deleteByCardIdAndLabelId(long localCardId, long labelId);
@Query("Update joincardwithlabel set status = :status WHERE cardId = :localCardId and labelId = :localLabelId")
void setDbStatus(long localCardId, long localLabelId, int status);
@Query("select labelId from joincardwithlabel WHERE cardId = :localCardId and labelId IN (:localLabelIds) and status <> 3") // not LOCAL_DELETED
List<Long> filterDeleted(long localCardId, List<Long> localLabelIds);
@Query("select * from joincardwithlabel WHERE cardId = :localCardId and labelId = :localLabelId")
JoinCardWithLabel getJoin(Long localLabelId, Long localCardId);
@Query("select l.id as labelId, c.id as cardId, j.status from joincardwithlabel j " +
"inner join card c on j.cardId = c.localId " +
"inner join label l on j.labelId = l.localId " +
"WHERE j.status <> 1") // not UP_TO_DATE
List<JoinCardWithLabel> getAllDeletedJoinsWithRemoteIDs();
@Query("select l.id as labelId, c.id as cardId, j.status from joincardwithlabel j " +
"inner join card c on j.cardId = c.localId " +
"inner join label l on j.labelId = l.localId " +
"WHERE j.cardId = :localCardId and j.labelId = :localLabelId") // not UP_TO_DATE
JoinCardWithLabel getRemoteIdsForJoin(long localCardId, long localLabelId);
@Query("select * from joincardwithlabel WHERE status <> 1") // not UP_TO_DATE
List<JoinCardWithLabel> getAllChangedJoins();
@Query("select j.* from joincardwithlabel j inner join card c on j.cardId = c.localId WHERE c.stackId = :localStackId and j.status <> 1") // not UP_TO_DATE
List<JoinCardWithLabel> getAllChangedJoinsForStack(Long localStackId);
@Query("delete from joincardwithlabel " +
"where cardId = (select c.localId from card c where c.accountId = :accountId and c.id = :remoteCardId) " +
"and labelId = (select l.localId from label l where l.accountId = :accountId and l.id = :remoteLabelId)")
void deleteJoinedLabelForCardPhysicallyByRemoteIDs(Long accountId, Long remoteCardId, Long remoteLabelId);
@Query("select count(*) from joincardwithlabel WHERE labelId = :localLabelId and status <> 3") // not locally deleted
int countCardsWithLabelDirectly(long localLabelId);
}

View file

@ -0,0 +1,49 @@
package it.niedermann.nextcloud.deck.database.dao;
import androidx.room.Dao;
import androidx.room.Query;
import java.util.List;
import it.niedermann.nextcloud.deck.model.JoinCardWithUser;
@Dao
public interface JoinCardWithUserDao extends GenericDao<JoinCardWithUser> {
@Query("DELETE FROM joincardwithuser WHERE cardId = :localId and status=1") // 1 = UP_TO_DATE
void deleteByCardId(long localId);
@Query("Update joincardwithuser set status = :status WHERE cardId = :localCardId and userId = :localUserId")
void setDbStatus(long localCardId, long localUserId, int status);
@Query("DELETE FROM joincardwithuser WHERE cardId = :localCardId and userId = :localUserId")
void deleteByCardIdAndUserIdPhysically(long localCardId, long localUserId);
@Query("DELETE FROM joincardwithuser " +
"WHERE cardid in (select c.localId from Card c join Stack s on c.stackId = s.localId and s.boardId = :localBoardId) " +
"and userId not in (select userId from UserInBoard where boardId = :localBoardId)")
void deleteJoinedUsersForCardsInBoardWithoutPermissionPhysically(long localBoardId);
@Query("select * FROM joincardwithuser WHERE cardId = :localCardId and userId = :localUserId")
JoinCardWithUser getJoin(Long localUserId, Long localCardId);
@Query("select u.localId as userId, c.id as cardId, j.status from joincardwithuser j " +
"inner join card c on j.cardId = c.localId " +
"inner join user u on j.userId = u.localId " +
"WHERE j.status <> 1") // not UP_TO_DATE
List<JoinCardWithUser> getChangedJoinsWithRemoteIDs();
@Query("select u.localId as userId, c.id as cardId, j.status from joincardwithuser j " +
"inner join card c on j.cardId = c.localId " +
"inner join user u on j.userId = u.localId " +
"WHERE c.stackId = :localStackId " +
"AND j.status <> 1") // not UP_TO_DATE
List<JoinCardWithUser> getChangedJoinsWithRemoteIDsForStack(Long localStackId);
@Query("delete from joincardwithuser " +
"where cardId = (select c.localId from card c where c.accountId = :accountId and c.id = :remoteCardId) " +
"and userId = (select u.localId from user u where u.accountId = :accountId and u.uid = :userUid)")
void deleteJoinedUserForCardPhysicallyByRemoteIDs(Long accountId, Long remoteCardId, String userUid);
@Query("select userId from joincardwithuser WHERE cardId = :localCardId and userId IN (:assignedUserIDs) and status <> 3") // not LOCAL_DELETED
List<Long> filterDeleted(long localCardId, List<Long> assignedUserIDs);
}

View file

@ -0,0 +1,57 @@
package it.niedermann.nextcloud.deck.database.dao;
import androidx.lifecycle.LiveData;
import androidx.room.Dao;
import androidx.room.Query;
import java.util.List;
import it.niedermann.nextcloud.deck.model.Label;
@Dao
public interface LabelDao extends GenericDao<Label> {
// @Query("SELECT * FROM label WHERE stackId = :localStackId")
// LiveData<List<Label>> getLabelsForStack(final long localStackId);
@Query("SELECT * FROM label WHERE accountId = :accountId and id = :remoteId")
LiveData<Label> getLabelByRemoteId(final long accountId, final long remoteId);
@Query("SELECT * FROM label WHERE localId = :localId")
LiveData<Label> getLabelByLocalId(final long localId);
@Query("SELECT * FROM label WHERE accountId = :accountId and id = :remoteId")
Label getLabelByRemoteIdDirectly(final long accountId, final long remoteId);
@Query("SELECT * FROM label WHERE localId IN (:labelIDs) and status <> 3 order by title asc") // not LOCAL_DELETED
List<Label> getLabelsByIdsDirectly(List<Long> labelIDs);
@Query("SELECT * FROM label WHERE localId = :localLabelID")
Label getLabelsByIdDirectly(final long localLabelID);
@Query("SELECT l.* FROM label l WHERE accountId = :accountId" +
" AND NOT EXISTS (" +
"select 1 from joincardwithlabel jl where jl.labelId = l.localId " +
"and jl.cardId = :notYetAssignedToLocalCardId AND status <> 3" + // not LOCAL_DELETED
") " +
" AND boardId = :boardId and title LIKE :searchTerm")
LiveData<List<Label>> searchNotYetAssignedLabelsByTitle(final long accountId, final long boardId, final long notYetAssignedToLocalCardId, String searchTerm);
@Query("SELECT * FROM label WHERE accountId = :accountId and (status<>1 or id is null or lastModified <> lastModifiedLocal)")
List<Label> getLocallyChangedLabelsDirectly(long accountId);
@Query("SELECT l.* " +
"FROM label l LEFT JOIN joincardwithlabel j ON j.labelId = l.localId " +
"WHERE l.accountId = :accountId AND l.boardId = :boardId " +
"AND NOT EXISTS (" +
"select 1 from joincardwithlabel jl where jl.labelId = l.localId " +
"and jl.cardId = :notAssignedToLocalCardId AND status <> 3" + // not LOCAL_DELETED
") " +
"GROUP BY l.localId ORDER BY count(*) DESC")
LiveData<List<Label>> findProposalsForLabelsToAssign(long accountId, long boardId, long notAssignedToLocalCardId);
@Query("select * from label WHERE boardId = :boardId and title = :title")
Label getLabelByBoardIdAndTitleDirectly(long boardId, String title);
}

View file

@ -0,0 +1,18 @@
package it.niedermann.nextcloud.deck.database.dao;
import androidx.room.Dao;
import androidx.room.Query;
import java.util.List;
import it.niedermann.nextcloud.deck.model.ocs.comment.Mention;
@Dao
public interface MentionDao extends GenericDao<Mention> {
@Query("delete from mention WHERE commentId = :commentID")
void clearMentionsForCommentId(long commentID);
@Query("select * from mention WHERE commentId = :commentID")
List<Mention> getMentionsForCommentIdDirectly(long commentID);
}

View file

@ -0,0 +1,10 @@
package it.niedermann.nextcloud.deck.database.dao;
import androidx.room.Dao;
import it.niedermann.nextcloud.deck.model.Permission;
@Dao
public interface PermissionDao extends GenericDao<Permission> {
}

View file

@ -0,0 +1,73 @@
package it.niedermann.nextcloud.deck.database.dao;
import androidx.lifecycle.LiveData;
import androidx.room.Dao;
import androidx.room.Query;
import androidx.room.Transaction;
import java.util.List;
import it.niedermann.nextcloud.deck.model.Stack;
import it.niedermann.nextcloud.deck.model.full.FullStack;
@Dao
public interface StackDao extends GenericDao<Stack> {
@Query("SELECT * FROM stack WHERE accountId = :accountId AND boardId = :localBoardId and status<>3 and (deletedAt is null or deletedAt = 0) order by `order` asc")
LiveData<List<Stack>> getStacksForBoard(final long accountId, final long localBoardId);
@Query("SELECT * FROM stack WHERE accountId = :accountId and boardId = :localBoardId and id = :remoteId")
LiveData<Stack> getStackByRemoteId(final long accountId, final long localBoardId, final long remoteId);
@Query("SELECT * FROM stack WHERE localId = :localStackId")
Stack getStackByLocalIdDirectly(final long localStackId);
@Transaction
@Query("SELECT * FROM stack WHERE localId = :localStackId")
FullStack getFullStackByLocalIdDirectly(final long localStackId);
@Transaction
@Query("SELECT * FROM stack WHERE accountId = :accountId and boardId = :localBoardId and id = :remoteId")
FullStack getFullStackByRemoteIdDirectly(final long accountId, final long localBoardId, final long remoteId);
@Transaction
@Query("SELECT * FROM stack WHERE accountId = :accountId and boardId = :localBoardId and id = :remoteId")
LiveData<FullStack> getFullStackByRemoteId(final long accountId, final long localBoardId, final long remoteId);
@Transaction
@Query("SELECT * FROM stack WHERE accountId = :accountId and localId = :localId")
LiveData<FullStack> getFullStack(long accountId, long localId);
@Query("SELECT localId FROM stack WHERE accountId = :accountId")
List<Long> getLocalStackIdsByAccountIdDirectly(long accountId);
@Query("SELECT localId FROM stack WHERE boardId = :localBoardId")
List<Long> getLocalStackIdsByLocalBoardIdDirectly(long localBoardId);
@Transaction
@Query("SELECT * FROM stack WHERE accountId = :accountId and boardId = :localBoardId and (status<>1 or id is null or lastModified <> lastModifiedLocal)")
List<FullStack> getLocallyChangedStacksForBoardDirectly(long accountId, long localBoardId);
@Transaction
@Query("SELECT * FROM stack WHERE accountId = :accountId and (status<>1 or id is null or lastModified <> lastModifiedLocal)")
List<FullStack> getLocallyChangedStacksDirectly(long accountId);
@Transaction
@Query("SELECT * FROM stack WHERE accountId = :accountId AND boardId = :localBoardId and status<>3 and (deletedAt is null or deletedAt = 0) order by `order` asc")
List<FullStack> getFullStacksForBoardDirectly(long accountId, long localBoardId);
@Query("SELECT localId FROM stack s WHERE accountId = :accountId and id = :stackId")
Long getLocalStackIdByRemoteStackIdDirectly(long accountId, Long stackId);
@Query("SELECT coalesce(MAX(`order`), -1) FROM stack s WHERE boardId = :localBoardId")
Integer getHighestStackOrderInBoard(long localBoardId);
@Query("SELECT exists(select 1 from Stack s join Board b on s.boardId = b.localId where s.localId = :localStackId and exists(select 1 from AccessControl ac where ac.boardId = b.localId and status <> 3))")
boolean isStackOnSharedBoardDirectly(Long localStackId);
@Query("SELECT s.localId FROM stack s join Board b on s.boardId = b.localId where b.archived <> 0 and b.accountId in (:accountIds)")
List<Long> getLocalStackIdsInArchivedBoardsByAccountIdsDirectly(List<Long> accountIds);
@Query("SELECT s.localId FROM stack s")
List<Long> getAllIDs();
}

View file

@ -0,0 +1,112 @@
package it.niedermann.nextcloud.deck.database.dao;
import androidx.lifecycle.LiveData;
import androidx.room.Dao;
import androidx.room.Query;
import java.util.List;
import it.niedermann.nextcloud.deck.model.User;
@Dao
public interface UserDao extends GenericDao<User> {
@Query("SELECT * FROM user WHERE accountId = :accountId")
LiveData<List<User>> getUsersForAccount(final long accountId);
@Query("SELECT * FROM user WHERE accountId = :accountId and localId = :localId")
LiveData<User> getUserByLocalId(final long accountId, final long localId);
@Query("SELECT * FROM user WHERE accountId = :accountId and uid = :uid")
LiveData<User> getUserByUid(final long accountId, final String uid);
@Query("SELECT u.displayname FROM user u WHERE accountId = :accountId and uid = :uid")
String getUserNameByUidDirectly(final long accountId, final String uid);
@Query("SELECT u.* FROM user u WHERE accountId = :accountId " +
" AND NOT EXISTS (" +
" select 1 from joincardwithuser ju" +
" where ju.userId = u.localId" +
" and ju.cardId = :notYetAssignedToLocalCardId AND status <> 3" + // not LOCAL_DELETED
" )" +
" AND ( " +
" EXISTS (" +
" select 1 from userinboard where boardId = :boardId AND userId = u.localId" +
" )" +
" OR" +
" EXISTS (" +
" select 1 from accesscontrol" + // v GROUP!
" where (userId = u.localId OR (type = 1 and exists(select 1 from UserInGroup uig where uig.memberId = u.localId and uig.groupId = userId))) " +
" and boardId = :boardId and status <> 3" +
" )" +
" OR" +
" EXISTS (" +
" select 1 from board where localId = :boardId AND ownerId = u.localId" +
" )" +
")" +
"and ( uid LIKE :searchTerm or displayname LIKE :searchTerm or primaryKey LIKE :searchTerm )")
LiveData<List<User>> searchUserByUidOrDisplayName(final long accountId, final long boardId, final long notYetAssignedToLocalCardId, final String searchTerm);
@Query("SELECT u.* FROM user u WHERE accountId = :accountId " +
" AND NOT EXISTS (" +
" select 1 from accesscontrol ju" +
" where ju.userId = u.localId and ju.boardId = :boardId and status <> 3" + // not LOCAL_DELETED
" ) " +
"and ( uid LIKE :searchTerm or displayname LIKE :searchTerm or primaryKey LIKE :searchTerm ) " +
"and u.localId <> (select b.ownerId from board b where localId = :boardId)" +
"ORDER BY u.displayname")
LiveData<List<User>> searchUserByUidOrDisplayNameForACL(final long accountId, final long boardId, final String searchTerm);
@Query("SELECT * FROM user WHERE accountId = :accountId and uid = :uid")
User getUserByUidDirectly(final long accountId, final String uid);
@Query("SELECT * FROM user WHERE localId IN (:assignedUserIDs) and status <> 3") // not LOCAL_DELETED
List<User> getUsersByIdDirectly(List<Long> assignedUserIDs);
@Query("SELECT * FROM user WHERE localId = :localUserId")
User getUserByLocalIdDirectly(long localUserId);
@Query(" SELECT u.* FROM user u" +
" WHERE u.accountId = :accountId" +
" AND NOT EXISTS (" +
" select 1 from joincardwithuser ju" +
" where ju.userId = u.localId" +
" and ju.cardId = :notAssignedToLocalCardId AND status <> 3" + // not LOCAL_DELETED
" )" +
" AND ( " +
" EXISTS (" +
" select 1 from userinboard where boardId = :boardId AND userId = u.localId" +
" )" +
" OR" +
" EXISTS (" +
" select 1 from accesscontrol" + // v GROUP!
" where (userId = u.localId OR (type = 1 and exists(select 1 from UserInGroup uig where uig.memberId = u.localId and uig.groupId = userId))) " +
" and boardId = :boardId and status <> 3" +
" )" +
" OR" +
" EXISTS (" +
" select 1 from board where localId = :boardId AND ownerId = u.localId" +
" )" +
")" +
" ORDER BY (" +
" select count(*) from joincardwithuser j" +
" where userId = u.localId and cardId in (select c.localId from card c inner join stack s on s.localId = c.stackId where s.boardId = :boardId)" +
") DESC" +
" LIMIT :topX")
LiveData<List<User>> findProposalsForUsersToAssign(long accountId, long boardId, long notAssignedToLocalCardId, int topX);
@Query("SELECT u.* FROM user u WHERE accountId = :accountId " +
" AND NOT EXISTS (" +
" select 1 from accesscontrol ju" +
" where ju.userId = u.localId and ju.boardId = :boardId and status <> 3" + // not LOCAL_DELETED
" ) " +
"and u.localId <> (select b.ownerId from board b where localId = :boardId)" +
"ORDER BY u.displayname " +
"LIMIT :topX")
LiveData<List<User>> findProposalsForUsersToAssignForACL(long accountId, long boardId, int topX);
@Query("SELECT * FROM user WHERE localId IN (:userIDs) and status <> 3") // not LOCAL_DELETED
List<User> getUsersByIdsDirectly(List<Long> userIDs);
}

View file

@ -0,0 +1,12 @@
package it.niedermann.nextcloud.deck.database.dao;
import androidx.room.Dao;
import androidx.room.Query;
import it.niedermann.nextcloud.deck.model.relations.UserInBoard;
@Dao
public interface UserInBoardDao extends GenericDao<UserInBoard> {
@Query("DELETE FROM userinboard WHERE boardId = :localId")
void deleteByBoardId(long localId);
}

View file

@ -0,0 +1,18 @@
package it.niedermann.nextcloud.deck.database.dao;
import androidx.room.Dao;
import androidx.room.Query;
import androidx.room.RawQuery;
import androidx.sqlite.db.SupportSQLiteQuery;
import it.niedermann.nextcloud.deck.model.ocs.user.UserForAssignment;
import it.niedermann.nextcloud.deck.model.relations.UserInGroup;
@Dao
public interface UserInGroupDao extends GenericDao<UserInGroup> {
@Query("DELETE FROM useringroup WHERE groupId = :localId")
void deleteByGroupId(long localId);
@RawQuery
UserForAssignment getUserForAssignment(SupportSQLiteQuery query);
}

View file

@ -0,0 +1,21 @@
package it.niedermann.nextcloud.deck.database.dao.projects;
import androidx.room.Dao;
import androidx.room.Query;
import java.util.List;
import it.niedermann.nextcloud.deck.database.dao.GenericDao;
import it.niedermann.nextcloud.deck.model.ocs.projects.JoinCardWithProject;
@Dao
public interface JoinCardWithOcsProjectDao extends GenericDao<JoinCardWithProject> {
@Query("select * from JoinCardWithProject where projectId = :localProjectId and cardId = :localCardId")
JoinCardWithProject getAssignmentByCardIdAndProjectIdDirectly(Long localCardId, Long localProjectId);
@Query("delete from JoinCardWithProject where cardId = :localCardId and projectId NOT in (select p.localId from OcsProject p where p.accountId = :accountId and p.id in (:remoteProjectIDs))")
void deleteProjectResourcesByCardIdExceptGivenProjectIdsDirectly(long accountId, Long localCardId, List<Long> remoteProjectIDs);
@Query("delete from JoinCardWithProject where cardId = :localCardId")
void deleteProjectResourcesByCardIdDirectly(Long localCardId);
}

View file

@ -0,0 +1,13 @@
package it.niedermann.nextcloud.deck.database.dao.projects;
import androidx.room.Dao;
import androidx.room.Query;
import it.niedermann.nextcloud.deck.database.dao.GenericDao;
import it.niedermann.nextcloud.deck.model.ocs.projects.OcsProject;
@Dao
public interface OcsProjectDao extends GenericDao<OcsProject> {
@Query("select * from OcsProject where accountId = :accountId and id = :remoteId")
OcsProject getProjectByRemoteIdDirectly(long accountId, Long remoteId);
}

View file

@ -0,0 +1,25 @@
package it.niedermann.nextcloud.deck.database.dao.projects;
import androidx.lifecycle.LiveData;
import androidx.room.Dao;
import androidx.room.Query;
import java.util.List;
import it.niedermann.nextcloud.deck.database.dao.GenericDao;
import it.niedermann.nextcloud.deck.model.ocs.projects.OcsProjectResource;
@Dao
public interface OcsProjectResourceDao extends GenericDao<OcsProjectResource> {
@Query("delete from OcsProjectResource where projectId = :localProjectId")
void deleteByProjectId(Long localProjectId);
@Query("select * from OcsProjectResource where projectId = :localProjectId")
LiveData<List<OcsProjectResource>> getResourcesByLocalProjectId(Long localProjectId);
@Query("select count(id) from OcsProjectResource where projectId = :localProjectId")
int countProjectResourcesInProjectDirectly(Long localProjectId);
@Query("select count(id) from OcsProjectResource where projectId = :localProjectId")
LiveData<Integer> countProjectResourcesInProject(Long localProjectId);
}

View file

@ -0,0 +1,21 @@
package it.niedermann.nextcloud.deck.database.dao.widgets;
import androidx.room.Dao;
import androidx.room.Query;
import androidx.room.Transaction;
import it.niedermann.nextcloud.deck.database.dao.GenericDao;
import it.niedermann.nextcloud.deck.model.full.FullSingleCardWidgetModel;
import it.niedermann.nextcloud.deck.model.widget.singlecard.SingleCardWidgetModel;
@Dao
public interface SingleCardWidgetModelDao extends GenericDao<SingleCardWidgetModel> {
@Transaction
@Query("SELECT * FROM singlecardwidgetmodel WHERE widgetId = :widgetId")
FullSingleCardWidgetModel getFullCardByRemoteIdDirectly(final int widgetId);
@Transaction
@Query("SELECT EXISTS (SELECT 1 FROM singlecardwidgetmodel WHERE cardId = :cardLocalId)")
boolean containsCardLocalId(final Long cardLocalId);
}

View file

@ -0,0 +1,19 @@
package it.niedermann.nextcloud.deck.database.dao.widgets;
import androidx.room.Dao;
import androidx.room.Query;
import androidx.room.Transaction;
import it.niedermann.nextcloud.deck.database.dao.GenericDao;
import it.niedermann.nextcloud.deck.model.appwidgets.StackWidgetModel;
@Dao
public interface StackWidgetModelDao extends GenericDao<StackWidgetModel> {
@Query("SELECT * FROM stackwidgetmodel WHERE appwidgetid = :appWidgetId")
StackWidgetModel getStackWidgetByAppWidgetIdDirectly(final int appWidgetId);
@Transaction
@Query("SELECT EXISTS (SELECT 1 FROM stackwidgetmodel WHERE stackId in (:stackLocalIds))")
boolean containsStackLocalId(final long... stackLocalIds);
}

View file

@ -0,0 +1,18 @@
package it.niedermann.nextcloud.deck.database.dao.widgets.filter;
import androidx.room.Dao;
import androidx.room.Query;
import java.util.List;
import it.niedermann.nextcloud.deck.database.dao.GenericDao;
import it.niedermann.nextcloud.deck.model.widget.filter.FilterWidgetAccount;
@Dao
public interface FilterWidgetAccountDao extends GenericDao<FilterWidgetAccount> {
@Query("DELETE FROM FilterWidgetAccount WHERE filterWidgetId = :filterWidgetId")
void deleteByFilterWidgetId (Integer filterWidgetId);
@Query("select * FROM FilterWidgetAccount WHERE filterWidgetId = :filterWidgetId")
List<FilterWidgetAccount> getFilterWidgetAccountsByFilterWidgetIdDirectly(Integer filterWidgetId);
}

View file

@ -0,0 +1,15 @@
package it.niedermann.nextcloud.deck.database.dao.widgets.filter;
import androidx.room.Dao;
import androidx.room.Query;
import java.util.List;
import it.niedermann.nextcloud.deck.database.dao.GenericDao;
import it.niedermann.nextcloud.deck.model.widget.filter.FilterWidgetBoard;
@Dao
public interface FilterWidgetBoardDao extends GenericDao<FilterWidgetBoard> {
@Query("SELECT * FROM FilterWidgetBoard where filterAccountId = :filterWidgetAccountId")
List<FilterWidgetBoard> getFilterWidgetBoardsByFilterWidgetAccountIdDirectly(Long filterWidgetAccountId);
}

View file

@ -0,0 +1,44 @@
package it.niedermann.nextcloud.deck.database.dao.widgets.filter;
import androidx.room.Dao;
import androidx.room.Query;
import androidx.room.Transaction;
import java.util.List;
import it.niedermann.nextcloud.deck.database.dao.GenericDao;
import it.niedermann.nextcloud.deck.model.widget.filter.EWidgetType;
import it.niedermann.nextcloud.deck.model.widget.filter.FilterWidget;
@Dao
public interface FilterWidgetDao extends GenericDao<FilterWidget> {
@Query("DELETE FROM filterwidget WHERE id = :filterWidgetId")
void delete (Integer filterWidgetId);
@Query("SELECT * FROM FilterWidget where id = :filterWidgetId")
FilterWidget getFilterWidgetByIdDirectly(Integer filterWidgetId);
@Query("SELECT EXISTS (SELECT 1 FROM FilterWidget WHERE id = :filterWidgetId)")
boolean filterWidgetExists(int filterWidgetId);
@Query("SELECT id FROM FilterWidget WHERE widgetType = :type")
List<Integer> getFilterWidgetIdsByType(int type);
@Transaction
@Query("SELECT DISTINCT w.widgetType " +
"FROM FilterWidget w " +
"LEFT JOIN FilterWidgetAccount a ON w.id = a.filterWidgetId " +
"LEFT JOIN FilterWidgetBoard b ON a.id = b.filterAccountId " +
"LEFT JOIN FilterWidgetStack s ON b.id = s.filterBoardId " +
"LEFT JOIN FilterWidgetUser u ON a.id = u.filterAccountId " +
"LEFT JOIN FilterWidgetProject p ON a.id = p.filterAccountId " +
"LEFT JOIN FilterWidgetLabel l ON b.id = l.filterBoardId " +
"WHERE (:changedEntityType = 'ACCOUNT' AND (a.accountId = :localIdOfChangedEntity OR a.accountId IS NULL)) " +
"OR (:changedEntityType = 'BOARD' AND (b.boardId = :localIdOfChangedEntity OR b.boardId IS NULL)) " +
"OR (:changedEntityType = 'STACK' AND (s.stackId = :localIdOfChangedEntity OR s.stackId IS NULL)) " +
"OR (:changedEntityType = 'USER' AND (u.userId = :localIdOfChangedEntity OR u.userId IS NULL)) " +
"OR (:changedEntityType = 'PROJECT' AND (p.projectId = :localIdOfChangedEntity OR p.projectId IS NULL)) " +
"OR (:changedEntityType = 'LABEL' AND (l.labelId = :localIdOfChangedEntity OR l.labelId IS NULL)) "
)
List<EWidgetType> getChangedListTypesByEntity(String changedEntityType, Long localIdOfChangedEntity);
}

View file

@ -0,0 +1,15 @@
package it.niedermann.nextcloud.deck.database.dao.widgets.filter;
import androidx.room.Dao;
import androidx.room.Query;
import java.util.List;
import it.niedermann.nextcloud.deck.database.dao.GenericDao;
import it.niedermann.nextcloud.deck.model.widget.filter.FilterWidgetLabel;
@Dao
public interface FilterWidgetLabelDao extends GenericDao<FilterWidgetLabel> {
@Query("SELECT * FROM FilterWidgetLabel where filterBoardId = :filterWidgetBoardId")
List<FilterWidgetLabel> getFilterWidgetLabelsByFilterWidgetBoardIdDirectly(Long filterWidgetBoardId);
}

View file

@ -0,0 +1,15 @@
package it.niedermann.nextcloud.deck.database.dao.widgets.filter;
import androidx.room.Dao;
import androidx.room.Query;
import java.util.List;
import it.niedermann.nextcloud.deck.database.dao.GenericDao;
import it.niedermann.nextcloud.deck.model.widget.filter.FilterWidgetProject;
@Dao
public interface FilterWidgetProjectDao extends GenericDao<FilterWidgetProject> {
@Query("SELECT * FROM FilterWidgetProject where filterAccountId = :filterWidgetAccountId")
List<FilterWidgetProject> getFilterWidgetProjectsByFilterWidgetAccountIdDirectly(Long filterWidgetAccountId);
}

View file

@ -0,0 +1,18 @@
package it.niedermann.nextcloud.deck.database.dao.widgets.filter;
import androidx.room.Dao;
import androidx.room.Query;
import java.util.List;
import it.niedermann.nextcloud.deck.database.dao.GenericDao;
import it.niedermann.nextcloud.deck.model.widget.filter.FilterWidgetSort;
@Dao
public interface FilterWidgetSortDao extends GenericDao<FilterWidgetSort> {
@Query("DELETE FROM FilterWidgetSort WHERE filterWidgetId = :filterWidgetId")
void deleteByFilterWidgetId (Integer filterWidgetId);
@Query("select * FROM FilterWidgetSort WHERE filterWidgetId = :filterWidgetId order by ruleOrder asc")
List<FilterWidgetSort> getFilterWidgetSortByFilterWidgetIdDirectly(Integer filterWidgetId);
}

View file

@ -0,0 +1,15 @@
package it.niedermann.nextcloud.deck.database.dao.widgets.filter;
import androidx.room.Dao;
import androidx.room.Query;
import java.util.List;
import it.niedermann.nextcloud.deck.database.dao.GenericDao;
import it.niedermann.nextcloud.deck.model.widget.filter.FilterWidgetStack;
@Dao
public interface FilterWidgetStackDao extends GenericDao<FilterWidgetStack> {
@Query("SELECT * FROM FilterWidgetStack where filterBoardId = :filterWidgetBoardId")
List<FilterWidgetStack> getFilterWidgetStacksByFilterWidgetBoardIdDirectly(Long filterWidgetBoardId);
}

View file

@ -0,0 +1,15 @@
package it.niedermann.nextcloud.deck.database.dao.widgets.filter;
import androidx.room.Dao;
import androidx.room.Query;
import java.util.List;
import it.niedermann.nextcloud.deck.database.dao.GenericDao;
import it.niedermann.nextcloud.deck.model.widget.filter.FilterWidgetUser;
@Dao
public interface FilterWidgetUserDao extends GenericDao<FilterWidgetUser> {
@Query("SELECT * FROM FilterWidgetUser where filterAccountId = :filterWidgetAccountId")
List<FilterWidgetUser> getFilterWidgetUsersByFilterWidgetAccountIdDirectly(Long filterWidgetAccountId);
}

View file

@ -0,0 +1,59 @@
package it.niedermann.nextcloud.deck.database.migration;
import android.database.Cursor;
import androidx.annotation.NonNull;
import androidx.room.migration.Migration;
import androidx.sqlite.db.SupportSQLiteDatabase;
import it.niedermann.nextcloud.deck.model.enums.DBStatus;
/**
* Removes duplicate labels and ensures uniqueness
*/
public class Migration_10_11 extends Migration {
public Migration_10_11() {
super(10, 11);
}
@Override
public void migrate(@NonNull SupportSQLiteDatabase database) {
// replace duplicates with the server-known ones
Cursor duplucatesCursor = database.query("SELECT boardId, title, count(*) FROM Label group by boardid, title having count(*) > 1");
if (duplucatesCursor != null && duplucatesCursor.moveToFirst()) {
do {
long boardId = duplucatesCursor.getLong(0);
String title = duplucatesCursor.getString(1);
Cursor singleDuplicateCursor = database.query("select localId from Label where boardId = ? and title = ? order by id desc", new Object[]{boardId, title});
if (singleDuplicateCursor != null && singleDuplicateCursor.moveToFirst()) {
long idToUse = -1;
do {
if (idToUse < 0) {
// desc order -> first one is the one with remote ID or a random one. keep this one.
idToUse = singleDuplicateCursor.getLong(0);
continue;
}
long idToReplace = singleDuplicateCursor.getLong(0);
Cursor cardsAssignedToDuplicateCursor = database.query("select cardId, exists(select 1 from JoinCardWithLabel ij where ij.labelId = ? and ij.cardId = cardId) " +
"from JoinCardWithLabel where labelId = ?", new Object[]{idToUse, idToReplace});
if (cardsAssignedToDuplicateCursor != null && cardsAssignedToDuplicateCursor.moveToFirst()) {
do {
long cardId = cardsAssignedToDuplicateCursor.getLong(0);
boolean hasDestinationLabelAssigned = cardsAssignedToDuplicateCursor.getInt(1) > 0;
database.execSQL("DELETE FROM JoinCardWithLabel where labelId = ? and cardId = ?", new Object[]{idToReplace, cardId});
if (!hasDestinationLabelAssigned) {
database.execSQL("INSERT INTO JoinCardWithLabel (status,labelId,cardId) VALUES (?, ?, ?)", new Object[]{DBStatus.LOCAL_EDITED.getId(), idToUse, cardId});
}
} while (cardsAssignedToDuplicateCursor.moveToNext());
}
database.execSQL("DELETE FROM Label where localId = ?", new Object[]{idToReplace});
} while (singleDuplicateCursor.moveToNext());
}
} while (duplucatesCursor.moveToNext());
}
// database.execSQL("DELETE FROM Label WHERE id IS NULL AND EXISTS(SELECT 1 FROM Label il WHERE il.boardId = boardId AND il.title = title AND id IS NOT NULL)");
database.execSQL("CREATE UNIQUE INDEX idx_label_title_unique ON Label(boardId, title)");
}
}

View file

@ -0,0 +1,20 @@
package it.niedermann.nextcloud.deck.database.migration;
import androidx.room.migration.Migration;
import androidx.sqlite.db.SupportSQLiteDatabase;
/**
* Adds support for the Single note widget
*/
public class Migration_11_12 extends Migration {
public Migration_11_12() {
super(11, 12);
}
@Override
public void migrate(SupportSQLiteDatabase database) {
database.execSQL("CREATE TABLE `SingleCardWidgetModel` (`widgetId` INTEGER PRIMARY KEY, `accountId` INTEGER, `boardId` INTEGER, `cardId` INTEGER, FOREIGN KEY(`accountId`) REFERENCES `Account`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE, FOREIGN KEY(`boardId`) REFERENCES `Board`(`localId`) ON UPDATE NO ACTION ON DELETE CASCADE, FOREIGN KEY(`cardId`) REFERENCES `Card`(`localId`) ON UPDATE NO ACTION ON DELETE CASCADE )");
database.execSQL("CREATE INDEX `index_SingleCardWidgetModel_cardId` ON `SingleCardWidgetModel` (`cardId`)");
}
}

View file

@ -0,0 +1,17 @@
package it.niedermann.nextcloud.deck.database.migration;
import androidx.room.migration.Migration;
import androidx.sqlite.db.SupportSQLiteDatabase;
public class Migration_12_13 extends Migration {
public Migration_12_13() {
super(12, 13);
}
@Override
public void migrate(SupportSQLiteDatabase database) {
database.execSQL("CREATE INDEX `idx_cardWidgetModel_accountId` ON `SingleCardWidgetModel` (`accountId`)");
database.execSQL("CREATE INDEX `idx_cardWidgetModel_boardId` ON `SingleCardWidgetModel` (`boardId`)");
}
}

View file

@ -0,0 +1,20 @@
package it.niedermann.nextcloud.deck.database.migration;
import androidx.room.migration.Migration;
import androidx.sqlite.db.SupportSQLiteDatabase;
/**
* Adds support for comment responses
*/
public class Migration_13_14 extends Migration {
public Migration_13_14() {
super(13, 14);
}
@Override
public void migrate(SupportSQLiteDatabase database) {
database.execSQL("ALTER TABLE `DeckComment` ADD `parentId` INTEGER REFERENCES DeckComment(localId) ON DELETE CASCADE");
database.execSQL("CREATE INDEX `idx_comment_parentID` ON DeckComment(parentId)");
}
}

View file

@ -0,0 +1,35 @@
package it.niedermann.nextcloud.deck.database.migration;
import android.content.Context;
import androidx.annotation.NonNull;
import androidx.preference.PreferenceManager;
import androidx.room.migration.Migration;
import androidx.sqlite.db.SupportSQLiteDatabase;
import it.niedermann.nextcloud.deck.remote.SyncWorker;
/**
* @see <a href="https://github.com/stefan-niedermann/nextcloud-deck/issues/570">Reinitializes the background synchronization</a> and
* <a href="https://github.com/stefan-niedermann/nextcloud-deck/issues/525">cleans up old shared preferences</a>
*/
public class Migration_14_15 extends Migration {
@NonNull
private final Context context;
public Migration_14_15(@NonNull Context context) {
super(14, 15);
this.context = context;
}
@Override
public void migrate(@NonNull SupportSQLiteDatabase database) {
SyncWorker.update(context);
PreferenceManager
.getDefaultSharedPreferences(context)
.edit()
.remove("it.niedermann.nextcloud.deck.theme_text")
.apply();
}
}

View file

@ -0,0 +1,23 @@
package it.niedermann.nextcloud.deck.database.migration;
import androidx.room.migration.Migration;
import androidx.sqlite.db.SupportSQLiteDatabase;
/**
* Adds support for Stack widget
*/
public class Migration_15_16 extends Migration {
public Migration_15_16() {
super(15, 16);
}
@Override
public void migrate(SupportSQLiteDatabase database) {
database.execSQL("CREATE TABLE `StackWidgetModel` (`appWidgetId` INTEGER PRIMARY KEY, `accountId` INTEGER, `stackId` INTEGER, `darkTheme` INTEGER CHECK (`darkTheme` IN (0,1)) NOT NULL, " +
"FOREIGN KEY(`accountId`) REFERENCES `Account`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE, " +
"FOREIGN KEY(`stackId`) REFERENCES `Stack`(`localId`) ON UPDATE NO ACTION ON DELETE CASCADE )");
database.execSQL("CREATE INDEX `index_StackWidgetModel_stackId` ON `StackWidgetModel` (`stackId`)");
database.execSQL("CREATE INDEX `index_StackWidgetModel_accountId` ON `StackWidgetModel` (`accountId`)");
}
}

View file

@ -0,0 +1,34 @@
package it.niedermann.nextcloud.deck.database.migration;
import androidx.room.migration.Migration;
import androidx.sqlite.db.SupportSQLiteDatabase;
/**
* @see <a href="https://github.com/stefan-niedermann/nextcloud-deck/issues/573">Adds support for projects</a>
*/
public class Migration_16_17 extends Migration {
public Migration_16_17() {
super(16, 17);
}
@Override
public void migrate(SupportSQLiteDatabase database) {
database.execSQL("CREATE TABLE `OcsProject` (`localId` INTEGER PRIMARY KEY AUTOINCREMENT, `accountId` INTEGER NOT NULL, `id` INTEGER, `name` TEXT NOT NULL, `status` INTEGER NOT NULL, `lastModified` INTEGER, `lastModifiedLocal` INTEGER)");
database.execSQL("CREATE UNIQUE INDEX `index_OcsProject_accountId_id` ON `OcsProject` (`accountId`, `id`)");
database.execSQL("CREATE INDEX `index_project_accID` ON `OcsProject` (`accountId`)");
database.execSQL("CREATE INDEX `index_OcsProject_id` ON `OcsProject` (`id`)");
database.execSQL("CREATE INDEX `index_OcsProject_lastModifiedLocal` ON `OcsProject` (`lastModifiedLocal`)");
database.execSQL("CREATE TABLE `OcsProjectResource` (`localId` INTEGER PRIMARY KEY AUTOINCREMENT, `accountId` INTEGER NOT NULL, `id` INTEGER, `name` TEXT, `status` INTEGER NOT NULL, `lastModified` INTEGER, `lastModifiedLocal` INTEGER, `projectId` INTEGER NOT NULL, `type` TEXT , `link` TEXT , `path` TEXT, `iconUrl` TEXT , `previewAvailable` INTEGER, `mimetype` TEXT, FOREIGN KEY(`projectId`) REFERENCES `OcsProject`(`localId`) ON UPDATE NO ACTION ON DELETE CASCADE)");
database.execSQL("CREATE INDEX `index_projectResource_accID` ON `OcsProjectResource` (`accountId`)");
database.execSQL("CREATE INDEX `index_projectResource_projectId` ON `OcsProjectResource` (`projectId`)");
database.execSQL("CREATE UNIQUE INDEX `index_OcsProjectResource_accountId_id` ON `OcsProjectResource` (`accountId`, `id`, `projectId`)");
database.execSQL("CREATE INDEX `index_OcsProjectResource_id` ON `OcsProjectResource` (`id`)");
database.execSQL("CREATE INDEX `index_OcsProjectResource_lastModifiedLocal` ON `OcsProjectResource` (`lastModifiedLocal`)");
database.execSQL("CREATE TABLE `JoinCardWithProject` (`status` INTEGER NOT NULL, `projectId` INTEGER NOT NULL, `cardId` INTEGER NOT NULL, PRIMARY KEY (`projectId`, `cardId`), FOREIGN KEY(`cardId`) REFERENCES `Card`(`localId`) ON UPDATE NO ACTION ON DELETE CASCADE, FOREIGN KEY(`projectId`) REFERENCES `OcsProject`(`localId`) ON UPDATE NO ACTION ON DELETE CASCADE)");
database.execSQL("CREATE INDEX `index_JoinCardWithProject_projectId` ON `JoinCardWithProject` (`projectId`)");
database.execSQL("CREATE INDEX `index_JoinCardWithProject_cardId` ON `JoinCardWithProject` (`cardId`)");
}
}

View file

@ -0,0 +1,19 @@
package it.niedermann.nextcloud.deck.database.migration;
import androidx.room.migration.Migration;
import androidx.sqlite.db.SupportSQLiteDatabase;
/**
* <a href="https://github.com/stefan-niedermann/nextcloud-deck/issues/435">Implement ETags for Capabilities endpoint</a>
*/
public class Migration_17_18 extends Migration {
public Migration_17_18() {
super(17, 18);
}
@Override
public void migrate(SupportSQLiteDatabase database) {
database.execSQL("ALTER TABLE `Account` ADD `etag` TEXT");
}
}

View file

@ -0,0 +1,21 @@
package it.niedermann.nextcloud.deck.database.migration;
import androidx.room.migration.Migration;
import androidx.sqlite.db.SupportSQLiteDatabase;
/**
* @see <a href="https://github.com/stefan-niedermann/nextcloud-deck/issues/619">Handle unknown project types</a>
*/
public class Migration_18_19 extends Migration {
public Migration_18_19() {
super(18, 19);
}
@Override
public void migrate(SupportSQLiteDatabase database) {
database.execSQL("DROP INDEX `index_OcsProjectResource_accountId_id`");
database.execSQL("ALTER TABLE `OcsProjectResource` ADD `idString` TEXT");
database.execSQL("CREATE UNIQUE INDEX `index_OcsProjectResource_accountId_id` ON `OcsProjectResource` (`accountId`, `id`, `idString`, `projectId`)");
}
}

View file

@ -0,0 +1,35 @@
package it.niedermann.nextcloud.deck.database.migration;
import androidx.room.migration.Migration;
import androidx.sqlite.db.SupportSQLiteDatabase;
/**
* Fixes issues with LDAP users when filtering
* https://github.com/stefan-niedermann/nextcloud-deck/issues/492
* https://github.com/stefan-niedermann/nextcloud-deck/issues/631
*/
public class Migration_19_20 extends Migration {
public Migration_19_20() {
super(19, 20);
}
@Override
public void migrate(SupportSQLiteDatabase database) {
database.execSQL("CREATE TABLE `UserInGroup` (`groupId` INTEGER NOT NULL, `memberId` INTEGER NOT NULL, " +
"primary KEY(`groupId`, `memberId`), " +
"FOREIGN KEY(`groupId`) REFERENCES `User`(`localId`) ON UPDATE NO ACTION ON DELETE CASCADE, " +
"FOREIGN KEY(`memberId`) REFERENCES `User`(`localId`) ON UPDATE NO ACTION ON DELETE CASCADE)");
database.execSQL("CREATE UNIQUE INDEX `unique_idx_group_member` ON `UserInGroup` (`groupId`, `memberId`)");
database.execSQL("CREATE INDEX `index_UserInGroup_groupId` ON `UserInGroup` (`groupId`)");
database.execSQL("CREATE INDEX `index_UserInGroup_memberId` ON `UserInGroup` (`memberId`)");
database.execSQL("CREATE TABLE `UserInBoard` (`userId` INTEGER NOT NULL, `boardId` INTEGER NOT NULL, " +
"primary KEY(`userId`, `boardId`), " +
"FOREIGN KEY(`userId`) REFERENCES `User`(`localId`) ON UPDATE NO ACTION ON DELETE CASCADE, " +
"FOREIGN KEY(`boardId`) REFERENCES `Board`(`localId`) ON UPDATE NO ACTION ON DELETE CASCADE)");
database.execSQL("CREATE UNIQUE INDEX `unique_idx_user_board` ON `UserInBoard` (`userId`, `boardId`)");
database.execSQL("CREATE INDEX `index_UserInBoard_userId` ON `UserInBoard` (`userId`)");
database.execSQL("CREATE INDEX `index_UserInBoard_boardId` ON `UserInBoard` (`boardId`)");
}
}

View file

@ -0,0 +1,123 @@
package it.niedermann.nextcloud.deck.database.migration;
import android.database.Cursor;
import android.graphics.Color;
import androidx.annotation.ColorInt;
import androidx.room.migration.Migration;
import androidx.sqlite.db.SupportSQLiteDatabase;
import it.niedermann.android.util.ColorUtil;
/**
* @see <a href="https://github.com/stefan-niedermann/nextcloud-deck/issues/556">Store colors as integer in database</a>
*/
public class Migration_20_21 extends Migration {
public Migration_20_21() {
super(20, 21);
}
@Override
public void migrate(SupportSQLiteDatabase database) {
String suffix = "_new";
{
String tableName = "Account";
database.execSQL("CREATE TABLE `" + tableName + suffix + "` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `name` TEXT NOT NULL, `userName` TEXT NOT NULL, `url` TEXT NOT NULL, " +
"`color` INTEGER NOT NULL DEFAULT 0, `textColor` INTEGER NOT NULL DEFAULT 0, `serverDeckVersion` TEXT NOT NULL DEFAULT '0.6.4', `maintenanceEnabled` INTEGER NOT NULL DEFAULT 0, `etag` TEXT)");
Cursor cursor = database.query("select * from `" + tableName + "`");
while (cursor.moveToNext()) {
String colorAsString1 = cursor.getString(4); // color
String colorAsString2 = cursor.getString(5); // textColor
@ColorInt int color1;
@ColorInt int color2;
try {
color1 = Color.parseColor(ColorUtil.formatColorToParsableHexString(colorAsString1));
color2 = Color.parseColor(ColorUtil.formatColorToParsableHexString(colorAsString2));
} catch (Exception e) {
color1 = Color.GRAY;
color2 = Color.GRAY;
}
database.execSQL("Insert into `" + tableName + suffix + "` VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)", new Object[]{
cursor.getLong(0), cursor.getString(1), cursor.getString(2), cursor.getString(3),
color1, color2, cursor.getString(6), cursor.getInt(7), cursor.getString(8)});
}
database.execSQL("DROP TABLE `" + tableName + "`");
database.execSQL("ALTER TABLE `" + tableName + suffix + "` RENAME TO `" + tableName + "`");
database.execSQL("CREATE UNIQUE INDEX `index_Account_name` ON `" + tableName + "` (`name`)");
database.execSQL("UPDATE SQLITE_SEQUENCE SET seq = (select max(id) from " + tableName + ") WHERE name = ?", new Object[]{tableName});
}
{
String tableName = "Board";
database.execSQL("CREATE TABLE `" + tableName + suffix + "` (`localId` INTEGER PRIMARY KEY AUTOINCREMENT, `accountId` INTEGER NOT NULL, `id` INTEGER, `status` INTEGER NOT NULL, " +
"`lastModified` INTEGER, `lastModifiedLocal` INTEGER, `title` TEXT, `ownerId` INTEGER NOT NULL, `color` INTEGER, " +
"`archived` INTEGER NOT NULL, `shared` INTEGER NOT NULL, `deletedAt` INTEGER, `permissionRead` INTEGER NOT NULL, " +
"`permissionEdit` INTEGER NOT NULL, `permissionManage` INTEGER NOT NULL, `permissionShare` INTEGER NOT NULL, " +
"FOREIGN KEY(`ownerId`) REFERENCES `User`(`localId`) ON UPDATE NO ACTION ON DELETE SET NULL )");
Cursor cursor = database.query("select * from `" + tableName + "`");
while (cursor.moveToNext()) {
String colorAsString1 = cursor.getString(8); // color
@ColorInt int color1;
try {
color1 = Color.parseColor(ColorUtil.formatColorToParsableHexString(colorAsString1));
} catch (Exception e) {
color1 = Color.GRAY;
}
database.execSQL("Insert into `" + tableName + suffix + "` VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", new Object[]{
cursor.getLong(0), cursor.getLong(1), cursor.getLong(2), cursor.getInt(3),
cursor.getLong(4), cursor.getLong(5), cursor.getString(6), cursor.getLong(7), color1,
cursor.getInt(9), cursor.getInt(10), cursor.getInt(11), cursor.getInt(12),
cursor.getInt(13), cursor.getInt(14), cursor.getInt(15)
});
}
database.execSQL("DROP TABLE `" + tableName + "`");
database.execSQL("ALTER TABLE `" + tableName + suffix + "` RENAME TO `" + tableName + "`");
database.execSQL("CREATE INDEX `index_Board_accountId` ON `" + tableName + "` (`accountId`)");
database.execSQL("CREATE UNIQUE INDEX `index_Board_accountId_id` ON `" + tableName + "` (`accountId`, `id`)");
database.execSQL("CREATE INDEX `index_Board_id` ON `" + tableName + "` (`id`)");
database.execSQL("CREATE INDEX `index_Board_ownerId` ON `" + tableName + "` (`ownerId`)");
database.execSQL("CREATE INDEX `index_Board_lastModifiedLocal` ON `" + tableName + "` (`lastModifiedLocal`)");
database.execSQL("UPDATE SQLITE_SEQUENCE SET seq = (select max(id) from " + tableName + ") WHERE name = ?", new Object[]{tableName});
}
{
String tableName = "Label";
database.execSQL("CREATE TABLE `" + tableName + suffix + "` (`localId` INTEGER PRIMARY KEY AUTOINCREMENT, `accountId` INTEGER NOT NULL, `id` INTEGER, `status` INTEGER NOT NULL, " +
"`lastModified` INTEGER, `lastModifiedLocal` INTEGER, `title` TEXT, `color` INTEGER NOT NULL DEFAULT 0, `boardId` INTEGER NOT NULL, " +
"FOREIGN KEY(`boardId`) REFERENCES `Board`(`localId`) ON UPDATE NO ACTION ON DELETE CASCADE )");
Cursor cursor = database.query("select * from `" + tableName + "`");
while (cursor.moveToNext()) {
String colorAsString1 = cursor.getString(7); // color
@ColorInt int color1;
try {
color1 = Color.parseColor(ColorUtil.formatColorToParsableHexString(colorAsString1));
} catch (Exception e) {
color1 = Color.GRAY;
}
database.execSQL("Insert into `" + tableName + suffix + "` VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)", new Object[]{
cursor.getLong(0), cursor.getLong(1), cursor.getLong(2), cursor.getInt(3),
cursor.getLong(4), cursor.getLong(5), cursor.getString(6), color1, cursor.getLong(8)});
}
database.execSQL("DROP TABLE `" + tableName + "`");
database.execSQL("ALTER TABLE `" + tableName + suffix + "` RENAME TO `" + tableName + "`");
database.execSQL("CREATE UNIQUE INDEX `index_Label_accountId_id` ON `" + tableName + "` (`accountId`, `id`)");
database.execSQL("CREATE INDEX `index_Label_boardId` ON `" + tableName + "` (`boardId`)");
database.execSQL("CREATE INDEX `index_Label_accountId` ON `" + tableName + "` (`accountId`)");
database.execSQL("CREATE UNIQUE INDEX `idx_label_title_unique` ON `" + tableName + "` (`boardId`, `title`)");
database.execSQL("CREATE INDEX `index_Label_id` ON `" + tableName + "` (`id`)");
database.execSQL("CREATE INDEX `index_Label_lastModifiedLocal` ON `" + tableName + "` (`lastModifiedLocal`)");
database.execSQL("UPDATE SQLITE_SEQUENCE SET seq = (select max(id) from " + tableName + ") WHERE name = ?", new Object[]{tableName});
}
}
}

View file

@ -0,0 +1,34 @@
package it.niedermann.nextcloud.deck.database.migration;
import android.content.Context;
import android.content.SharedPreferences;
import android.database.Cursor;
import androidx.annotation.NonNull;
import androidx.room.migration.Migration;
import androidx.sqlite.db.SupportSQLiteDatabase;
/**
* @see <a href="https://github.com/stefan-niedermann/nextcloud-deck/issues/715">Migrate from java.util.Date and java.util.Calendar to java.time.*</a>
*/
public class Migration_21_22 extends Migration {
@NonNull
private final Context context;
public Migration_21_22(@NonNull Context context) {
super(21, 22);
this.context = context;
}
@Override
public void migrate(@NonNull SupportSQLiteDatabase database) {
final SharedPreferences.Editor lastSyncPref = context.getApplicationContext().getSharedPreferences("it.niedermann.nextcloud.deck.last_sync", Context.MODE_PRIVATE).edit();
final Cursor cursor = database.query("select id from `Account`");
while (cursor.moveToNext()) {
lastSyncPref.remove("lS_" + cursor.getLong(0));
}
cursor.close();
lastSyncPref.apply();
}
}

View file

@ -0,0 +1,30 @@
package it.niedermann.nextcloud.deck.database.migration;
import androidx.room.migration.Migration;
import androidx.sqlite.db.SupportSQLiteDatabase;
/**
* @see <a href="https://github.com/stefan-niedermann/nextcloud-deck/issues/359">Implement ETags for synchronization Speed-Up</a>
*/
public class Migration_22_23 extends Migration {
public Migration_22_23() {
super(22, 23);
}
@Override
public void migrate(SupportSQLiteDatabase database) {
database.execSQL("ALTER TABLE `Account` ADD `boardsEtag` TEXT");
database.execSQL("ALTER TABLE `Board` ADD `etag` TEXT");
database.execSQL("ALTER TABLE `Stack` ADD `etag` TEXT");
database.execSQL("ALTER TABLE `Card` ADD `etag` TEXT");
database.execSQL("ALTER TABLE `Label` ADD `etag` TEXT");
database.execSQL("ALTER TABLE `AccessControl` ADD `etag` TEXT");
database.execSQL("ALTER TABLE `Attachment` ADD `etag` TEXT");
database.execSQL("ALTER TABLE `User` ADD `etag` TEXT");
database.execSQL("ALTER TABLE `DeckComment` ADD `etag` TEXT");
database.execSQL("ALTER TABLE `Activity` ADD `etag` TEXT");
database.execSQL("ALTER TABLE `OcsProject` ADD `etag` TEXT");
database.execSQL("ALTER TABLE `OcsProjectResource` ADD `etag` TEXT");
}
}

View file

@ -0,0 +1,39 @@
package it.niedermann.nextcloud.deck.database.migration;
import android.content.Context;
import android.content.SharedPreferences;
import androidx.annotation.NonNull;
import androidx.preference.PreferenceManager;
import androidx.room.migration.Migration;
import androidx.sqlite.db.SupportSQLiteDatabase;
import it.niedermann.nextcloud.deck.R;
/**
* <a href="https://github.com/stefan-niedermann/nextcloud-deck/issues/392">Dark mode following system default</a>
*/
public class Migration_23_24 extends Migration {
@NonNull
private final Context context;
public Migration_23_24(@NonNull Context context) {
super(23, 24);
this.context = context;
}
@Override
public void migrate(@NonNull SupportSQLiteDatabase database) {
final SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
final String themePref = context.getString(R.string.pref_key_dark_theme);
if (sharedPreferences.contains(themePref)) {
SharedPreferences.Editor editor = sharedPreferences.edit();
final boolean darkTheme = sharedPreferences.getBoolean(themePref, false);
editor.remove(themePref);
editor.putString(themePref, darkTheme ? context.getString(R.string.pref_value_theme_dark) : context.getString(R.string.pref_value_theme_light));
editor.apply();
}
}
}

View file

@ -0,0 +1,22 @@
package it.niedermann.nextcloud.deck.database.migration;
import androidx.room.migration.Migration;
import androidx.sqlite.db.SupportSQLiteDatabase;
/**
* Reset ETags (comments weren't loading due to bug)
*/
public class Migration_24_25 extends Migration {
public Migration_24_25() {
super(24, 25);
}
@Override
public void migrate(SupportSQLiteDatabase database) {
database.execSQL("UPDATE `Account` SET `boardsEtag` = NULL");
database.execSQL("UPDATE `Board` SET `etag` = NULL");
database.execSQL("UPDATE `Stack` SET `etag` = NULL");
database.execSQL("UPDATE `Card` SET `etag` = NULL");
}
}

View file

@ -0,0 +1,42 @@
package it.niedermann.nextcloud.deck.database.migration;
import androidx.room.migration.Migration;
import androidx.sqlite.db.SupportSQLiteDatabase;
/**
* Implement <a href="https://github.com/stefan-niedermann/nextcloud-deck/issues/597">Filter widget</a>
*/
public class Migration_25_26 extends Migration {
public Migration_25_26() {
super(25, 26);
}
@Override
public void migrate(SupportSQLiteDatabase database) {
database.execSQL("CREATE TABLE `FilterWidget` (`id` INTEGER PRIMARY KEY NOT NULL, `title` TEXT, `dueType` INTEGER, `widgetType` INTEGER NOT NULL)");
database.execSQL("CREATE TABLE `FilterWidgetAccount` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `filterWidgetId` INTEGER, `accountId` INTEGER, `includeNoUser` INTEGER NOT NULL, `includeNoProject` INTEGER NOT NULL, FOREIGN KEY(`accountId`) REFERENCES `Account`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`filterWidgetId`) REFERENCES `FilterWidget`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )");
database.execSQL("CREATE TABLE `FilterWidgetBoard` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `filterAccountId` INTEGER, `boardId` INTEGER, `includeNoLabel` INTEGER NOT NULL, FOREIGN KEY(`boardId`) REFERENCES `Board`(`localId`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`filterAccountId`) REFERENCES `FilterWidgetAccount`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )");
database.execSQL("CREATE TABLE `FilterWidgetLabel` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `filterBoardId` INTEGER, `labelId` INTEGER, FOREIGN KEY(`labelId`) REFERENCES `Label`(`localId`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`filterBoardId`) REFERENCES `FilterWidgetBoard`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )");
database.execSQL("CREATE TABLE `FilterWidgetSort` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `filterWidgetId` INTEGER, `direction` INTEGER NOT NULL, `criteria` INTEGER NOT NULL, `ruleOrder` INTEGER NOT NULL, FOREIGN KEY(`filterWidgetId`) REFERENCES `FilterWidget`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )");
database.execSQL("CREATE TABLE `FilterWidgetStack` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `filterBoardId` INTEGER, `stackId` INTEGER, FOREIGN KEY(`stackId`) REFERENCES `Stack`(`localId`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`filterBoardId`) REFERENCES `FilterWidgetBoard`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )");
database.execSQL("CREATE TABLE `FilterWidgetUser` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `filterAccountId` INTEGER, `userId` INTEGER, FOREIGN KEY(`userId`) REFERENCES `User`(`localId`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`filterAccountId`) REFERENCES `FilterWidgetAccount`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )");
database.execSQL("CREATE TABLE `FilterWidgetProject` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `filterAccountId` INTEGER, `projectId` INTEGER, FOREIGN KEY(`projectId`) REFERENCES `OcsProject`(`localId`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`filterAccountId`) REFERENCES `FilterWidgetAccount`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )");
database.execSQL("CREATE INDEX `index_FilterWidgetAccount_filterWidgetId` ON `FilterWidgetAccount` (`filterWidgetId`)");
database.execSQL("CREATE INDEX `idx_FilterWidgetAccount_accountId` ON `FilterWidgetAccount` (`accountId`)");
database.execSQL("CREATE INDEX `idx_FilterWidgetBoard_boardId` ON `FilterWidgetBoard` (`boardId`)");
database.execSQL("CREATE INDEX `idx_FilterWidgetBoard_filterAccountId` ON `FilterWidgetBoard` (`filterAccountId`)");
database.execSQL("CREATE INDEX `idx_FilterWidgetLabel_filterBoardId` ON `FilterWidgetLabel` (`filterBoardId`)");
database.execSQL("CREATE INDEX `idx_FilterWidgetLabel_labelId` ON `FilterWidgetLabel` (`labelId`)");
database.execSQL("CREATE INDEX `idx_FilterWidgetSort_filterWidgetId` ON `FilterWidgetSort` (`filterWidgetId`)");
database.execSQL("CREATE INDEX `idx_FilterWidgetStack_filterBoardId` ON `FilterWidgetStack` (`filterBoardId`)");
database.execSQL("CREATE INDEX `idx_FilterWidgetStack_stackId` ON `FilterWidgetStack` (`stackId`)");
database.execSQL("CREATE INDEX `idx_FilterWidgetUser_filterAccountId` ON `FilterWidgetUser` (`filterAccountId`)");
database.execSQL("CREATE INDEX `idx_FilterWidgetUser_userId` ON `FilterWidgetUser` (`userId`)");
database.execSQL("CREATE INDEX `idx_FilterWidgetProject_filterAccountId` ON `FilterWidgetProject` (`filterAccountId`)");
database.execSQL("CREATE INDEX `idx_FilterWidgetProject_projectId` ON `FilterWidgetProject` (`projectId`)");
database.execSQL("CREATE INDEX `unique_idx_FilterWidgetSort_filterWidgetId_criteria` ON `FilterWidgetSort` (`filterWidgetId`, `criteria`)");
database.execSQL("CREATE INDEX `unique_idx_FilterWidgetSort_filterWidgetId_ruleOrder` ON `FilterWidgetSort` (`filterWidgetId`, `ruleOrder`)");
}
}

View file

@ -0,0 +1,62 @@
package it.niedermann.nextcloud.deck.database.migration;
import android.content.ContentValues;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import androidx.room.migration.Migration;
import androidx.sqlite.db.SupportSQLiteDatabase;
import it.niedermann.nextcloud.deck.model.widget.filter.EWidgetType;
/**
* @see <a href="https://github.com/stefan-niedermann/nextcloud-deck/issues/767">Migrate Stack Widget to Filter Widget infrastructure</a>
*/
public class Migration_26_27 extends Migration {
public Migration_26_27() {
super(26, 27);
}
@Override
public void migrate(SupportSQLiteDatabase database) {
Cursor cursor = database.query("select s.localId, s.boardId, s.accountId, w.appWidgetId from `StackWidgetModel` w inner join `Stack` s on s.localId = w.stackId");
while (cursor.moveToNext()) {
Long localStackId = cursor.getLong(0);
Long localBoardId = cursor.getLong(1);
Long accountId = cursor.getLong(2);
Long filterWidgetId = cursor.getLong(3);
// widget
ContentValues values = new ContentValues();
values.put("widgetType", EWidgetType.STACK_WIDGET.getId());
values.put("id", filterWidgetId);
database.insert("FilterWidget", SQLiteDatabase.CONFLICT_NONE, values);
// account
values = new ContentValues();
values.put("filterWidgetId", filterWidgetId);
values.put("accountId", accountId);
values.put("includeNoUser", false);
values.put("includeNoProject", false);
long filterWidgetAccountId = database.insert("FilterWidgetAccount", SQLiteDatabase.CONFLICT_NONE, values);
// board
values = new ContentValues();
values.put("filterAccountId", filterWidgetAccountId);
values.put("boardId", localBoardId);
values.put("includeNoLabel", false);
long filterWidgetBoardId = database.insert("FilterWidgetBoard", SQLiteDatabase.CONFLICT_NONE, values);
// stack
values = new ContentValues();
values.put("filterBoardId", filterWidgetBoardId);
values.put("stackId", localStackId);
database.insert("FilterWidgetStack", SQLiteDatabase.CONFLICT_NONE, values);
}
// cleanup
database.execSQL("DROP TABLE `StackWidgetModel`");
}
}

View file

@ -0,0 +1,19 @@
package it.niedermann.nextcloud.deck.database.migration;
import androidx.room.migration.Migration;
import androidx.sqlite.db.SupportSQLiteDatabase;
/**
* Adds support for new attachment types with Deck server <code>1.3.0</code>
*/
public class Migration_27_28 extends Migration {
public Migration_27_28() {
super(27, 28);
}
@Override
public void migrate(SupportSQLiteDatabase database) {
database.execSQL("ALTER TABLE `Attachment` ADD COLUMN `fileId` INTEGER");
}
}

View file

@ -0,0 +1,22 @@
package it.niedermann.nextcloud.deck.database.migration;
import androidx.room.migration.Migration;
import androidx.sqlite.db.SupportSQLiteDatabase;
/**
* Reset ETags for cards because <a href="https://github.com/nextcloud/deck/issues/2874">the attachments for this card might not be complete</a>.
*/
public class Migration_28_29 extends Migration {
public Migration_28_29() {
super(28, 29);
}
@Override
public void migrate(SupportSQLiteDatabase database) {
database.execSQL("UPDATE `Account` SET `boardsEtag` = NULL");
database.execSQL("UPDATE `Board` SET `etag` = NULL");
database.execSQL("UPDATE `Stack` SET `etag` = NULL");
database.execSQL("UPDATE `Card` SET `etag` = NULL");
}
}

View file

@ -0,0 +1,30 @@
package it.niedermann.nextcloud.deck.database.migration;
import android.content.Context;
import androidx.annotation.NonNull;
import androidx.preference.PreferenceManager;
import androidx.room.migration.Migration;
import androidx.sqlite.db.SupportSQLiteDatabase;
/**
* <a href="https://github.com/stefan-niedermann/nextcloud-deck/issues/392">Dark mode following system default</a>
*/
public class Migration_29_30 extends Migration {
@NonNull
private final Context context;
public Migration_29_30(@NonNull Context context) {
super(29, 30);
this.context = context;
}
@Override
public void migrate(@NonNull SupportSQLiteDatabase database) {
PreferenceManager.getDefaultSharedPreferences(context)
.edit()
.remove("branding")
.apply();
}
}

View file

@ -0,0 +1,182 @@
package it.niedermann.nextcloud.deck.database.migration;
import androidx.annotation.NonNull;
import androidx.room.migration.Migration;
import androidx.sqlite.db.SupportSQLiteDatabase;
/**
* <strong><a href="https://github.com/stefan-niedermann/nextcloud-deck/issues/923">Foreign keys don't cascade (Cards stay in the database after deleting an Account)</a></strong>
* <p>
* This migration had two issues in the past:
* <ul>
* <li>
* <a href="https://github.com/stefan-niedermann/nextcloud-deck/issues/936">1. Issue: SQLiteException</a>
* <p>
* <code>SQLiteException: table "Board" already exists (code 1): , while compiling: CREATE TABLE "Board" []</code><br />
* Caused by directly selecting the <code>CREATE</code> statements of the tables and executing them again.<br />
* The problem with this approach was that various different Android environments had different String quotes
* (<code>"</code>, <code>'</code>, <code>`</code>) which messed up table name replacement logic.<br />
* Fixed by explicitly creating each table manually.
* <p>
* Affected app versions: <code>1017000 (1.17.0)</code> - <code>1017002 (1.17.2)</code>
* </li>
* <li>
* <a href="https://github.com/stefan-niedermann/nextcloud-deck/issues/935">2. issue: SQLiteConstraintException</a>
* <p>
* <code>SQLiteConstraintException: NOT NULL constraint failed: Activity_tmp.type (code 1299 SQLITE_CONSTRAINT_NOTNULL[1299])</code><br />
* Fixed by explicitly selecting the values of each column manually instead of using a wildcard
* <p>
* To fix states where the migration was partially successful, the <code>*_tmp</code> tables will be dropped, so a half migration can successfully continue.
* <p>
* Affected app versions: <code>1017000 (1.17.3)</code> - <code>1017002 (1.17.6)</code>
* </li>
* </ul>
*/
public class Migration_30_31 extends Migration {
public Migration_30_31() {
super(30, 31);
}
@Override
public void migrate(@NonNull SupportSQLiteDatabase database) {
database.execSQL("DROP TABLE IF EXISTS `AccessControl_tmp`");
database.execSQL("CREATE TABLE `AccessControl_tmp` (`localId` INTEGER PRIMARY KEY AUTOINCREMENT, `accountId` INTEGER NOT NULL, `id` INTEGER, `status` INTEGER NOT NULL, `lastModified` INTEGER, `lastModifiedLocal` INTEGER, `etag` TEXT, `type` INTEGER, `boardId` INTEGER, `owner` INTEGER NOT NULL, `permissionEdit` INTEGER NOT NULL, `permissionShare` INTEGER NOT NULL, `permissionManage` INTEGER NOT NULL, `userId` INTEGER, FOREIGN KEY(`boardId`) REFERENCES `Board`(`localId`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`accountId`) REFERENCES `Account`(`id`) ON DELETE CASCADE )");
database.execSQL("DELETE FROM `AccessControl` where accountId not in (select id from `Account`)");
database.execSQL("INSERT INTO `AccessControl_tmp` (`localId`, `accountId`, `id`, `status`, `lastModified`, `lastModifiedLocal`, `etag`, `type`, `boardId`, `owner`, `permissionEdit`, `permissionShare`, `permissionManage`, `userId`) select `localId`, `accountId`, `id`, `status`, `lastModified`, `lastModifiedLocal`, `etag`, `type`, `boardId`, `owner`, `permissionEdit`, `permissionShare`, `permissionManage`, `userId` from `AccessControl`");
database.execSQL("DROP TABLE `AccessControl`");
database.execSQL("ALTER TABLE `AccessControl_tmp` RENAME TO `AccessControl`");
database.execSQL("CREATE INDEX `acl_accId` ON `AccessControl` (`accountId`)");
database.execSQL("CREATE INDEX `index_AccessControl_boardId` ON `AccessControl` (`boardId`)");
database.execSQL("CREATE INDEX `index_AccessControl_accountId` ON `AccessControl` (`accountId`)");
database.execSQL("CREATE INDEX `index_AccessControl_id` ON `AccessControl` (`id`)");
database.execSQL("CREATE INDEX `index_AccessControl_lastModifiedLocal` ON `AccessControl` (`lastModifiedLocal`)");
database.execSQL("CREATE UNIQUE INDEX `index_AccessControl_accountId_id` ON `AccessControl` (`accountId`, `id`)");
database.execSQL("DROP TABLE IF EXISTS `Activity_tmp`");
database.execSQL("CREATE TABLE `Activity_tmp` (`localId` INTEGER PRIMARY KEY AUTOINCREMENT, `accountId` INTEGER NOT NULL, `id` INTEGER, `status` INTEGER NOT NULL, `lastModified` INTEGER, `lastModifiedLocal` INTEGER, `etag` TEXT, `cardId` INTEGER NOT NULL, `subject` TEXT, `type` INTEGER NOT NULL, FOREIGN KEY(`cardId`) REFERENCES `Card`(`localId`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`accountId`) REFERENCES `Account`(`id`) ON DELETE CASCADE )");
database.execSQL("DELETE FROM `Activity` where accountId not in (select id from `Account`)");
database.execSQL("UPDATE `Activity` SET `type` = 2 WHERE `type` IS NULL");
database.execSQL("INSERT INTO `Activity_tmp` (`localId`, `accountId`, `id`, `status`, `lastModified`, `lastModifiedLocal`, `etag`, `cardId`, `subject`, `type`) select `localId`, `accountId`, `id`, `status`, `lastModified`, `lastModifiedLocal`, `etag`, `cardId`, `subject`, COALESCE(`type`, 2) from `Activity`");
database.execSQL("DROP TABLE `Activity`");
database.execSQL("ALTER TABLE `Activity_tmp` RENAME TO `Activity`");
database.execSQL("CREATE INDEX `activity_accID` ON `Activity` (`accountId`)");
database.execSQL("CREATE INDEX `activity_cardID` ON `Activity` (`cardId`)");
database.execSQL("CREATE INDEX `index_Activity_accountId` ON `Activity` (`accountId`)");
database.execSQL("CREATE INDEX `index_Activity_id` ON `Activity` (`id`)");
database.execSQL("CREATE INDEX `index_Activity_lastModifiedLocal` ON `Activity` (`lastModifiedLocal`)");
database.execSQL("CREATE UNIQUE INDEX `index_Activity_accountId_id` ON `Activity` (`accountId`, `id`)");
database.execSQL("DROP TABLE IF EXISTS `Attachment_tmp`");
database.execSQL("CREATE TABLE `Attachment_tmp` (`localId` INTEGER PRIMARY KEY AUTOINCREMENT, `accountId` INTEGER NOT NULL, `id` INTEGER, `status` INTEGER NOT NULL, `lastModified` INTEGER, `lastModifiedLocal` INTEGER, `etag` TEXT, `cardId` INTEGER NOT NULL, `type` TEXT, `data` TEXT, `createdAt` INTEGER, `createdBy` TEXT, `deletedAt` INTEGER, `filesize` INTEGER NOT NULL, `mimetype` TEXT, `dirname` TEXT, `basename` TEXT, `extension` TEXT, `filename` TEXT, `localPath` TEXT, `fileId` INTEGER, FOREIGN KEY(`cardId`) REFERENCES `Card`(`localId`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`accountId`) REFERENCES `Account`(`id`) ON DELETE CASCADE )");
database.execSQL("DELETE FROM `Attachment` where accountId not in (select id from `Account`)");
database.execSQL("INSERT INTO `Attachment_tmp` (`localId`, `accountId`, `id`, `status`, `lastModified`, `lastModifiedLocal`, `etag`, `cardId`, `type`, `data`, `createdAt`, `createdBy`, `deletedAt`, `filesize`, `mimetype`, `dirname`, `basename`, `extension`, `filename`, `localPath`, `fileId`) select `localId`, `accountId`, `id`, `status`, `lastModified`, `lastModifiedLocal`, `etag`, `cardId`, `type`, `data`, `createdAt`, `createdBy`, `deletedAt`, `filesize`, `mimetype`, `dirname`, `basename`, `extension`, `filename`, `localPath`, `fileId` from `Attachment`");
database.execSQL("DROP TABLE `Attachment`");
database.execSQL("ALTER TABLE `Attachment_tmp` RENAME TO `Attachment`");
database.execSQL("CREATE INDEX `index_Attachment_cardId` ON `Attachment` (`cardId`)");
database.execSQL("CREATE INDEX `index_Attachment_accountId` ON `Attachment` (`accountId`)");
database.execSQL("CREATE INDEX `index_Attachment_id` ON `Attachment` (`id`)");
database.execSQL("CREATE INDEX `index_Attachment_lastModifiedLocal` ON `Attachment` (`lastModifiedLocal`)");
database.execSQL("CREATE UNIQUE INDEX `index_Attachment_accountId_id` ON `Attachment` (`accountId`, `id`)");
database.execSQL("DROP TABLE IF EXISTS `Board_tmp`");
database.execSQL("CREATE TABLE `Board_tmp` (`localId` INTEGER PRIMARY KEY AUTOINCREMENT, `accountId` INTEGER NOT NULL, `id` INTEGER, `status` INTEGER NOT NULL, `lastModified` INTEGER, `lastModifiedLocal` INTEGER, `etag` TEXT, `title` TEXT, `ownerId` INTEGER NOT NULL, `color` INTEGER, `archived` INTEGER NOT NULL, `shared` INTEGER NOT NULL, `deletedAt` INTEGER, `permissionRead` INTEGER NOT NULL, `permissionEdit` INTEGER NOT NULL, `permissionManage` INTEGER NOT NULL, `permissionShare` INTEGER NOT NULL, FOREIGN KEY(`ownerId`) REFERENCES `User`(`localId`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`accountId`) REFERENCES `Account`(`id`) ON DELETE CASCADE )");
database.execSQL("DELETE FROM `Board` where accountId not in (select id from `Account`)");
database.execSQL("INSERT INTO `Board_tmp` (`localId`, `accountId`, `id`, `status`, `lastModified`, `lastModifiedLocal`, `etag`, `title`, `ownerId`, `color`, `archived`, `shared`, `deletedAt`, `permissionRead`, `permissionEdit`, `permissionManage`, `permissionShare`) select `localId`, `accountId`, `id`, `status`, `lastModified`, `lastModifiedLocal`, `etag`, `title`, `ownerId`, `color`, `archived`, `shared`, `deletedAt`, `permissionRead`, `permissionEdit`, `permissionManage`, `permissionShare` from `Board`");
database.execSQL("DROP TABLE `Board`");
database.execSQL("ALTER TABLE `Board_tmp` RENAME TO `Board`");
database.execSQL("CREATE INDEX `index_Board_ownerId` ON `Board` (`ownerId`)");
database.execSQL("CREATE INDEX `index_Board_accountId` ON `Board` (`accountId`)");
database.execSQL("CREATE INDEX `index_Board_id` ON `Board` (`id`)");
database.execSQL("CREATE INDEX `index_Board_lastModifiedLocal` ON `Board` (`lastModifiedLocal`)");
database.execSQL("CREATE UNIQUE INDEX `index_Board_accountId_id` ON `Board` (`accountId`, `id`)");
database.execSQL("DROP TABLE IF EXISTS `Card_tmp`");
database.execSQL("CREATE TABLE `Card_tmp` (`localId` INTEGER PRIMARY KEY AUTOINCREMENT, `accountId` INTEGER NOT NULL, `id` INTEGER, `status` INTEGER NOT NULL, `lastModified` INTEGER, `lastModifiedLocal` INTEGER, `etag` TEXT, `title` TEXT, `description` TEXT, `stackId` INTEGER NOT NULL, `type` TEXT, `createdAt` INTEGER, `deletedAt` INTEGER, `attachmentCount` INTEGER NOT NULL, `userId` INTEGER, `order` INTEGER NOT NULL, `archived` INTEGER NOT NULL, `dueDate` INTEGER, `notified` INTEGER NOT NULL, `overdue` INTEGER NOT NULL, `commentsUnread` INTEGER NOT NULL, FOREIGN KEY(`stackId`) REFERENCES `Stack`(`localId`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`accountId`) REFERENCES `Account`(`id`) ON DELETE CASCADE )");
database.execSQL("DELETE FROM `Card` where accountId not in (select id from `Account`)");
database.execSQL("INSERT INTO `Card_tmp` (`localId`, `accountId`, `id`, `status`, `lastModified`, `lastModifiedLocal`, `etag`, `title`, `description`, `stackId`, `type`, `createdAt`, `deletedAt`, `attachmentCount`, `userId`, `order`, `archived`, `dueDate`, `notified`, `overdue`, `commentsUnread`) select `localId`, `accountId`, `id`, `status`, `lastModified`, `lastModifiedLocal`, `etag`, `title`, `description`, `stackId`, `type`, `createdAt`, `deletedAt`, `attachmentCount`, `userId`, `order`, `archived`, `dueDate`, `notified`, `overdue`, `commentsUnread` from `Card`");
database.execSQL("DROP TABLE `Card`");
database.execSQL("ALTER TABLE `Card_tmp` RENAME TO `Card`");
database.execSQL("CREATE INDEX `card_accID` ON `Card` (`accountId`)");
database.execSQL("CREATE INDEX `index_Card_stackId` ON `Card` (`stackId`)");
database.execSQL("CREATE INDEX `index_Card_accountId` ON `Card` (`accountId`)");
database.execSQL("CREATE INDEX `index_Card_id` ON `Card` (`id`)");
database.execSQL("CREATE INDEX `index_Card_lastModifiedLocal` ON `Card` (`lastModifiedLocal`)");
database.execSQL("CREATE UNIQUE INDEX `index_Card_accountId_id` ON `Card` (`accountId`, `id`)");
database.execSQL("DROP TABLE IF EXISTS `DeckComment_tmp`");
database.execSQL("CREATE TABLE `DeckComment_tmp` (`localId` INTEGER PRIMARY KEY AUTOINCREMENT, `accountId` INTEGER NOT NULL, `id` INTEGER, `status` INTEGER NOT NULL, `lastModified` INTEGER, `lastModifiedLocal` INTEGER, `etag` TEXT, `objectId` INTEGER, `actorType` TEXT, `creationDateTime` INTEGER, `actorId` TEXT, `actorDisplayName` TEXT, `message` TEXT, `parentId` INTEGER, FOREIGN KEY(`objectId`) REFERENCES `Card`(`localId`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`parentId`) REFERENCES `DeckComment`(`localId`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`accountId`) REFERENCES `Account`(`id`) ON DELETE CASCADE )");
database.execSQL("DELETE FROM `DeckComment` where accountId not in (select id from `Account`)");
database.execSQL("INSERT INTO `DeckComment_tmp` (`localId`, `accountId`, `id`, `status`, `lastModified`, `lastModifiedLocal`, `etag`, `objectId`, `actorType`, `creationDateTime`, `actorId`, `actorDisplayName`, `message`, `parentId`) select `localId`, `accountId`, `id`, `status`, `lastModified`, `lastModifiedLocal`, `etag`, `objectId`, `actorType`, `creationDateTime`, `actorId`, `actorDisplayName`, `message`, `parentId` from `DeckComment`");
database.execSQL("DROP TABLE `DeckComment`");
database.execSQL("ALTER TABLE `DeckComment_tmp` RENAME TO `DeckComment`");
database.execSQL("CREATE INDEX `comment_accID` ON `DeckComment` (`accountId`)");
database.execSQL("CREATE INDEX `index_DeckComment_objectId` ON `DeckComment` (`objectId`)");
database.execSQL("CREATE INDEX `idx_comment_parentID` ON `DeckComment` (`parentId`)");
database.execSQL("CREATE INDEX `index_DeckComment_accountId` ON `DeckComment` (`accountId`)");
database.execSQL("CREATE INDEX `index_DeckComment_id` ON `DeckComment` (`id`)");
database.execSQL("CREATE INDEX `index_DeckComment_lastModifiedLocal` ON `DeckComment` (`lastModifiedLocal`)");
database.execSQL("CREATE UNIQUE INDEX `index_DeckComment_accountId_id` ON `DeckComment` (`accountId`, `id`)");
database.execSQL("DROP TABLE IF EXISTS `Label_tmp`");
database.execSQL("CREATE TABLE `Label_tmp` (`localId` INTEGER PRIMARY KEY AUTOINCREMENT, `accountId` INTEGER NOT NULL, `id` INTEGER, `status` INTEGER NOT NULL, `lastModified` INTEGER, `lastModifiedLocal` INTEGER, `etag` TEXT, `title` TEXT, `color` INTEGER NOT NULL DEFAULT 0, `boardId` INTEGER NOT NULL, FOREIGN KEY(`boardId`) REFERENCES `Board`(`localId`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`accountId`) REFERENCES `Account`(`id`) ON DELETE CASCADE )");
database.execSQL("DELETE FROM `Label` where accountId not in (select id from `Account`)");
database.execSQL("INSERT INTO `Label_tmp` (`localId`, `accountId`, `id`, `status`, `lastModified`, `lastModifiedLocal`, `etag`, `title`, `color`, `boardId`) select `localId`, `accountId`, `id`, `status`, `lastModified`, `lastModifiedLocal`, `etag`, `title`, `color`, `boardId` from `Label`");
database.execSQL("DROP TABLE `Label`");
database.execSQL("ALTER TABLE `Label_tmp` RENAME TO `Label`");
database.execSQL("CREATE INDEX `index_Label_boardId` ON `Label` (`boardId`)");
database.execSQL("CREATE UNIQUE INDEX `idx_label_title_unique` ON `Label` (`boardId`, `title`)");
database.execSQL("CREATE INDEX `index_Label_accountId` ON `Label` (`accountId`)");
database.execSQL("CREATE INDEX `index_Label_id` ON `Label` (`id`)");
database.execSQL("CREATE INDEX `index_Label_lastModifiedLocal` ON `Label` (`lastModifiedLocal`)");
database.execSQL("CREATE UNIQUE INDEX `index_Label_accountId_id` ON `Label` (`accountId`, `id`)");
database.execSQL("DROP TABLE IF EXISTS `OcsProject_tmp`");
database.execSQL("CREATE TABLE `OcsProject_tmp` (`localId` INTEGER PRIMARY KEY AUTOINCREMENT, `accountId` INTEGER NOT NULL, `id` INTEGER, `status` INTEGER NOT NULL, `lastModified` INTEGER, `lastModifiedLocal` INTEGER, `etag` TEXT, `name` TEXT NOT NULL, FOREIGN KEY(`accountId`) REFERENCES `Account`(`id`) ON DELETE CASCADE )");
database.execSQL("DELETE FROM `OcsProject` where accountId not in (select id from `Account`)");
database.execSQL("INSERT INTO `OcsProject_tmp` (`localId`, `accountId`, `id`, `status`, `lastModified`, `lastModifiedLocal`, `etag`, `name`) select `localId`, `accountId`, `id`, `status`, `lastModified`, `lastModifiedLocal`, `etag`, `name` from `OcsProject`");
database.execSQL("DROP TABLE `OcsProject`");
database.execSQL("ALTER TABLE `OcsProject_tmp` RENAME TO `OcsProject`");
database.execSQL("CREATE INDEX `index_project_accID` ON `OcsProject` (`accountId`)");
database.execSQL("CREATE INDEX `index_OcsProject_accountId` ON `OcsProject` (`accountId`)");
database.execSQL("CREATE INDEX `index_OcsProject_id` ON `OcsProject` (`id`)");
database.execSQL("CREATE INDEX `index_OcsProject_lastModifiedLocal` ON `OcsProject` (`lastModifiedLocal`)");
database.execSQL("CREATE UNIQUE INDEX `index_OcsProject_accountId_id` ON `OcsProject` (`accountId`, `id`)");
database.execSQL("DROP TABLE IF EXISTS `OcsProjectResource_tmp`");
database.execSQL("CREATE TABLE `OcsProjectResource_tmp` (`localId` INTEGER PRIMARY KEY AUTOINCREMENT, `accountId` INTEGER NOT NULL, `id` INTEGER, `status` INTEGER NOT NULL, `lastModified` INTEGER, `lastModifiedLocal` INTEGER, `etag` TEXT, `type` TEXT, `name` TEXT, `link` TEXT, `path` TEXT, `iconUrl` TEXT, `mimetype` TEXT, `previewAvailable` INTEGER, `idString` TEXT, `projectId` INTEGER NOT NULL, FOREIGN KEY(`projectId`) REFERENCES `OcsProject`(`localId`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`accountId`) REFERENCES `Account`(`id`) ON DELETE CASCADE )");
database.execSQL("DELETE FROM `OcsProjectResource` where accountId not in (select id from `Account`)");
database.execSQL("INSERT INTO `OcsProjectResource_tmp` (`localId`, `accountId`, `id`, `status`, `lastModified`, `lastModifiedLocal`, `etag`, `type`, `name`, `link`, `path`, `iconUrl`, `mimetype`, `previewAvailable`, `idString`, `projectId`) select `localId`, `accountId`, `id`, `status`, `lastModified`, `lastModifiedLocal`, `etag`, `type`, `name`, `link`, `path`, `iconUrl`, `mimetype`, `previewAvailable`, `idString`, `projectId` from `OcsProjectResource`");
database.execSQL("DROP TABLE `OcsProjectResource`");
database.execSQL("ALTER TABLE `OcsProjectResource_tmp` RENAME TO `OcsProjectResource`");
database.execSQL("CREATE INDEX `index_OcsProjectResource_id` ON `OcsProjectResource` (`id`)");
database.execSQL("CREATE INDEX `index_OcsProjectResource_lastModifiedLocal` ON `OcsProjectResource` (`lastModifiedLocal`)");
database.execSQL("CREATE UNIQUE INDEX `index_OcsProjectResource_accountId_id` ON `OcsProjectResource` (`accountId`, `id`, `idString`, `projectId`)");
database.execSQL("CREATE INDEX `index_projectResource_accID` ON `OcsProjectResource` (`accountId`)");
database.execSQL("CREATE INDEX `index_projectResource_projectId` ON `OcsProjectResource` (`projectId`)");
database.execSQL("DROP TABLE IF EXISTS `Stack_tmp`");
database.execSQL("CREATE TABLE `Stack_tmp` (`localId` INTEGER PRIMARY KEY AUTOINCREMENT, `accountId` INTEGER NOT NULL, `id` INTEGER, `status` INTEGER NOT NULL, `lastModified` INTEGER, `lastModifiedLocal` INTEGER, `etag` TEXT, `title` TEXT, `boardId` INTEGER NOT NULL, `deletedAt` INTEGER, `order` INTEGER NOT NULL, FOREIGN KEY(`boardId`) REFERENCES `Board`(`localId`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`accountId`) REFERENCES `Account`(`id`) ON DELETE CASCADE )");
database.execSQL("DELETE FROM `Stack` where accountId not in (select id from `Account`)");
database.execSQL("INSERT INTO `Stack_tmp` (`localId`, `accountId`, `id`, `status`, `lastModified`, `lastModifiedLocal`, `etag`, `title`, `boardId`, `deletedAt`, `order`) select `localId`, `accountId`, `id`, `status`, `lastModified`, `lastModifiedLocal`, `etag`, `title`, `boardId`, `deletedAt`, `order` from `Stack`");
database.execSQL("DROP TABLE `Stack`");
database.execSQL("ALTER TABLE `Stack_tmp` RENAME TO `Stack`");
database.execSQL("CREATE INDEX `index_Stack_boardId` ON `Stack` (`boardId`)");
database.execSQL("CREATE INDEX `index_Stack_accountId` ON `Stack` (`accountId`)");
database.execSQL("CREATE INDEX `index_Stack_id` ON `Stack` (`id`)");
database.execSQL("CREATE INDEX `index_Stack_lastModifiedLocal` ON `Stack` (`lastModifiedLocal`)");
database.execSQL("CREATE UNIQUE INDEX `index_Stack_accountId_id` ON `Stack` (`accountId`, `id`)");
database.execSQL("DROP TABLE IF EXISTS `User_tmp`");
database.execSQL("CREATE TABLE `User_tmp` (`localId` INTEGER PRIMARY KEY AUTOINCREMENT, `accountId` INTEGER NOT NULL, `id` INTEGER, `status` INTEGER NOT NULL, `lastModified` INTEGER, `lastModifiedLocal` INTEGER, `etag` TEXT, `primaryKey` TEXT, `uid` TEXT, `displayname` TEXT, FOREIGN KEY(`accountId`) REFERENCES `Account`(`id`) ON DELETE CASCADE )");
database.execSQL("DELETE FROM `User` where accountId not in (select id from `Account`)");
database.execSQL("INSERT INTO `User_tmp` (`localId`, `accountId`, `id`, `status`, `lastModified`, `lastModifiedLocal`, `etag`, `primaryKey`, `uid`, `displayname`) select `localId`, `accountId`, `id`, `status`, `lastModified`, `lastModifiedLocal`, `etag`, `primaryKey`, `uid`, `displayname` from `User`");
database.execSQL("DROP TABLE `User`");
database.execSQL("ALTER TABLE `User_tmp` RENAME TO `User`");
database.execSQL("CREATE INDEX `user_uid` ON `User` (`uid`)");
database.execSQL("CREATE INDEX `index_User_accountId` ON `User` (`accountId`)");
database.execSQL("CREATE INDEX `index_User_id` ON `User` (`id`)");
database.execSQL("CREATE INDEX `index_User_lastModifiedLocal` ON `User` (`lastModifiedLocal`)");
database.execSQL("CREATE UNIQUE INDEX `index_User_accountId_id` ON `User` (`accountId`, `id`)");
}
}

View file

@ -0,0 +1,29 @@
package it.niedermann.nextcloud.deck.database.migration;
import android.content.Context;
import androidx.annotation.NonNull;
import androidx.preference.PreferenceManager;
import androidx.room.migration.Migration;
import androidx.sqlite.db.SupportSQLiteDatabase;
/**
*/
public class Migration_31_32 extends Migration {
@NonNull
private final Context context;
public Migration_31_32(@NonNull Context context) {
super(31, 32);
this.context = context;
}
@Override
public void migrate(@NonNull SupportSQLiteDatabase database) {
PreferenceManager.getDefaultSharedPreferences(context)
.edit()
.remove("it.niedermann.nextcloud.deck.theme_main")
.remove("it.niedermann.nextcloud.deck.last_account_color")
.apply();
}
}

View file

@ -0,0 +1,25 @@
package it.niedermann.nextcloud.deck.database.migration;
import androidx.annotation.NonNull;
import androidx.room.migration.Migration;
import androidx.sqlite.db.SupportSQLiteDatabase;
/**
* Adds support for marking a card as done: https://github.com/stefan-niedermann/nextcloud-deck/issues/1556
*/
public class Migration_32_33 extends Migration {
public Migration_32_33() {
super(32, 33);
}
@Override
public void migrate(@NonNull SupportSQLiteDatabase database) {
database.execSQL("ALTER TABLE `Card` add column done INTEGER");
// Reset ETags: Refetch all cards to support Done state which did not change ETags
database.execSQL("UPDATE `Account` SET `boardsEtag` = NULL");
database.execSQL("UPDATE `Board` SET `etag` = NULL");
database.execSQL("UPDATE `Stack` SET `etag` = NULL");
database.execSQL("UPDATE `Card` SET `etag` = NULL");
}
}

View file

@ -0,0 +1,22 @@
package it.niedermann.nextcloud.deck.database.migration;
import androidx.annotation.NonNull;
import androidx.room.migration.Migration;
import androidx.sqlite.db.SupportSQLiteDatabase;
/**
* Adds support for marking a card as done: https://github.com/stefan-niedermann/nextcloud-deck/issues/1556
*/
public class Migration_33_34 extends Migration {
public Migration_33_34() {
super(33, 34);
}
@Override
public void migrate(@NonNull SupportSQLiteDatabase database) {
database.execSQL("ALTER TABLE `User` add column type INTEGER not null default 0");
// Reset ETags to refetch Users
database.execSQL("UPDATE `Account` SET `boardsEtag` = NULL");
}
}

View file

@ -0,0 +1,27 @@
package it.niedermann.nextcloud.deck.database.migration;
import androidx.room.migration.Migration;
import androidx.sqlite.db.SupportSQLiteDatabase;
/**
* Adds support for comments
*/
public class Migration_8_9 extends Migration {
public Migration_8_9() {
super(8, 9);
}
@Override
public void migrate(SupportSQLiteDatabase database) {
database.execSQL("CREATE TABLE `DeckComment` (`localId` INTEGER PRIMARY KEY AUTOINCREMENT, `accountId` INTEGER NOT NULL, `id` INTEGER, `status` INTEGER NOT NULL, `lastModified` INTEGER, `lastModifiedLocal` INTEGER, `objectId` INTEGER, `actorType` TEXT, `creationDateTime` INTEGER, `actorId` TEXT, `actorDisplayName` TEXT, `message` TEXT, FOREIGN KEY(`objectId`) REFERENCES `Card`(`localId`) ON UPDATE NO ACTION ON DELETE CASCADE )");
database.execSQL("CREATE TABLE `Mention` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `commentId` INTEGER, `mentionId` TEXT, `mentionType` TEXT, `mentionDisplayName` TEXT, FOREIGN KEY(`commentId`) REFERENCES `DeckComment`(`localId`) ON UPDATE NO ACTION ON DELETE CASCADE )");
database.execSQL("CREATE INDEX `index_DeckComment_accountId` ON `DeckComment` (`accountId`)");
database.execSQL("CREATE INDEX `comment_accID` ON `DeckComment` (`accountId`)");
database.execSQL("CREATE UNIQUE INDEX `index_DeckComment_accountId_id` ON `DeckComment` (`accountId`, `id`)");
database.execSQL("CREATE INDEX `index_DeckComment_id` ON `DeckComment` (`id`)");
database.execSQL("CREATE INDEX `index_DeckComment_lastModifiedLocal` ON `DeckComment` (`lastModifiedLocal`)");
database.execSQL("CREATE INDEX `index_DeckComment_objectId` ON `DeckComment` (`objectId`)");
database.execSQL("CREATE INDEX `index_Mention_commentId` ON `Mention` (`commentId`)");
}
}

View file

@ -0,0 +1,22 @@
package it.niedermann.nextcloud.deck.database.migration;
import androidx.room.migration.Migration;
import androidx.sqlite.db.SupportSQLiteDatabase;
/**
* Adds support for account colors, deck server versions and Nextcloud maintenance mode
*/
public class Migration_9_10 extends Migration {
public Migration_9_10() {
super(9, 10);
}
@Override
public void migrate(SupportSQLiteDatabase database) {
database.execSQL("ALTER TABLE `Account` ADD `color` TEXT NOT NULL DEFAULT '#0082c9'");
database.execSQL("ALTER TABLE `Account` ADD `textColor` TEXT NOT NULL DEFAULT '#ffffff'");
database.execSQL("ALTER TABLE `Account` ADD `serverDeckVersion` TEXT NOT NULL DEFAULT '0.6.4'");
database.execSQL("ALTER TABLE `Account` ADD `maintenanceEnabled` INTEGER NOT NULL DEFAULT 0");
}
}

View file

@ -0,0 +1,22 @@
package it.niedermann.nextcloud.deck.exceptions;
public class DeckException extends IllegalArgumentException {
public enum Hint {
CAPABILITIES_NOT_PARSABLE,
CAPABILITIES_VERSION_NOT_PARSABLE,
UNKNOWN_ACCOUNT_USER_ID,
DEPENDENCY_NOT_SYNCED_YET
}
private final Hint hint;
public DeckException(Hint hint, String message) {
super(message);
this.hint = hint;
}
public Hint getHint() {
return hint;
}
}

View file

@ -0,0 +1,67 @@
package it.niedermann.nextcloud.deck.exceptions;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.google.gson.JsonSyntaxException;
import com.nextcloud.android.sso.exceptions.NextcloudHttpRequestFailedException;
public enum HandledServerErrors {
UNKNOWN(1337, "hopefully won't occur"),
LABELS_TITLE_MUST_BE_UNIQUE(400, "Title must be unique"),
ATTACHMENTS_FILE_ALREADY_EXISTS(409, "File already exists."),
;
private final int status;
private final String message;
HandledServerErrors(int status, String message) {
this.status = status;
this.message = message;
}
public static HandledServerErrors fromThrowable(Throwable throwable) {
if (throwable instanceof NextcloudHttpRequestFailedException requestFailedException) {
if (requestFailedException.getCause() != null) {
String errorString = requestFailedException.getCause().getMessage();
try {
JsonElement jsonElement = JsonParser.parseString(errorString);
if (jsonElement.isJsonObject()){
ServerError error = new ServerError();
error.status = requestFailedException.getStatusCode();
JsonObject errorObj = jsonElement.getAsJsonObject();
if (errorObj.has("message")){
error.message = errorObj.get("message").getAsString();
}
return findByServerError(error);
}
} catch (JsonSyntaxException e){
return HandledServerErrors.UNKNOWN;
}
}
}
return HandledServerErrors.UNKNOWN;
}
private static HandledServerErrors findByServerError(ServerError error) {
for (HandledServerErrors value : HandledServerErrors.values()) {
if (value.status == error.status && value.message.equals(error.message)){
return value;
}
}
return HandledServerErrors.UNKNOWN;
}
public String getMessage() {
return message;
}
public int getStatus() {
return status;
}
private static class ServerError {
private int status;
private String message;
}
}

View file

@ -0,0 +1,51 @@
package it.niedermann.nextcloud.deck.exceptions;
import androidx.annotation.NonNull;
import androidx.annotation.StringRes;
import it.niedermann.nextcloud.deck.R;
public class OfflineException extends IllegalStateException {
private final Reason reason;
public OfflineException() {
this(Reason.OFFLINE);
}
public OfflineException(@NonNull Reason reason) {
super(reason.getKey());
this.reason = reason;
}
@NonNull
public Reason getReason() {
return reason;
}
public enum Reason {
OFFLINE("Device is currently offline", R.string.error_dialog_tip_offline_no_internet),
CONNECTION_REFUSED("Connection refused", R.string.error_dialog_tip_offline_connection_refused),
CONNECTION_TIMEOUT("Connection timeout", R.string.error_dialog_tip_offline_connection_timeout),
CONNECTION_REJECTED("Connection rejected", R.string.error_dialog_tip_connection_rejected),
;
private final String key;
@StringRes
private final int message;
Reason(@NonNull String key, @StringRes int message) {
this.key = key;
this.message = message;
}
public String getKey() {
return key;
}
@StringRes
public int getMessage() {
return message;
}
}
}

View file

@ -0,0 +1,36 @@
package it.niedermann.nextcloud.deck.exceptions;
import it.niedermann.nextcloud.deck.DeckLog;
public class TraceableException extends RuntimeException {
private TraceableException(String message, Throwable cause) {
super(message, cause);
}
public static void makeTraceableIfFails(Runnable runnable, Object... args) {
try {
runnable.run();
} catch (TraceableException t) {
throw t;
} catch (Throwable t) {
final StringBuilder message = new StringBuilder("Sorry, a wild error appeared!\n\n" +
"⚠️ If you want to tell us about the following issue, " +
"please make sure to censor sensitive data beforehand! ⚠️\n\n" +
"Failed to run traceable code");
if (args != null && args.length > 0) {
message.append(" with arguments:\n");
for (Object arg : args) {
message.append(arg == null ? "null" : arg.toString()).append("\n");
}
} else {
message.append(":\n");
}
message.append("Cause: ").append(t.getLocalizedMessage());
TraceableException ex = new TraceableException(message.toString(), t);
DeckLog.logError(ex);
throw ex;
}
}
}

View file

@ -0,0 +1,12 @@
package it.niedermann.nextcloud.deck.exceptions;
public class UploadAttachmentFailedException extends Exception {
public UploadAttachmentFailedException(String message) {
super(message);
}
public UploadAttachmentFailedException(String message, Throwable cause) {
super(message, cause);
}
}

View file

@ -0,0 +1,188 @@
package it.niedermann.nextcloud.deck.model;
import androidx.room.Entity;
import androidx.room.ForeignKey;
import androidx.room.Ignore;
import androidx.room.Index;
import com.google.gson.annotations.SerializedName;
import java.util.Objects;
import it.niedermann.nextcloud.deck.model.interfaces.AbstractRemoteEntity;
import it.niedermann.nextcloud.deck.model.ocs.user.GroupMemberUIDs;
@Entity(inheritSuperIndices = true,
indices = {
@Index(value = "accountId", name = "acl_accId"),
@Index("boardId")
},
foreignKeys = {
@ForeignKey(
entity = Board.class,
parentColumns = "localId",
childColumns = "boardId", onDelete = ForeignKey.CASCADE
),
@ForeignKey(
entity = Account.class,
parentColumns = "id",
childColumns = "accountId", onDelete = ForeignKey.CASCADE
)
}
)
public class AccessControl extends AbstractRemoteEntity {
private Long type;
private Long boardId;
private boolean owner;
private boolean permissionEdit;
private boolean permissionShare;
private boolean permissionManage;
private Long userId;
@Ignore
@SerializedName("participant")
private User user;
@Ignore
private GroupMemberUIDs groupMemberUIDs;
public AccessControl() {
super();
}
public AccessControl(AccessControl accessControl) {
this.type = accessControl.getType();
this.boardId = accessControl.getBoardId();
this.owner = accessControl.isOwner();
this.permissionEdit = accessControl.isPermissionEdit();
this.permissionShare = accessControl.isPermissionShare();
this.permissionManage = accessControl.isPermissionManage();
this.userId = accessControl.getUserId();
this.user = accessControl.getUser();
}
public Long getType() {
return type;
}
public void setType(Long type) {
this.type = type;
}
public Long getBoardId() {
return boardId;
}
public void setBoardId(Long boardId) {
this.boardId = boardId;
}
public boolean isOwner() {
return owner;
}
public void setOwner(boolean owner) {
this.owner = owner;
}
public boolean isPermissionEdit() {
return permissionEdit;
}
public void setPermissionEdit(boolean permissionEdit) {
this.permissionEdit = permissionEdit;
}
public boolean isPermissionShare() {
return permissionShare;
}
public void setPermissionShare(boolean permissionShare) {
this.permissionShare = permissionShare;
}
public boolean isPermissionManage() {
return permissionManage;
}
public void setPermissionManage(boolean permissionManage) {
this.permissionManage = permissionManage;
}
public Long getUserId() {
return userId;
}
public void setUserId(Long userId) {
this.userId = userId;
}
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
public GroupMemberUIDs getGroupMemberUIDs() {
return groupMemberUIDs;
}
public void setGroupMemberUIDs(GroupMemberUIDs groupMemberUIDs) {
this.groupMemberUIDs = groupMemberUIDs;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
if (!super.equals(o)) return false;
AccessControl that = (AccessControl) o;
if (owner != that.owner) return false;
if (permissionEdit != that.permissionEdit) return false;
if (permissionShare != that.permissionShare) return false;
if (permissionManage != that.permissionManage) return false;
if (!Objects.equals(type, that.type)) return false;
if (!Objects.equals(boardId, that.boardId)) return false;
if (!Objects.equals(userId, that.userId)) return false;
return Objects.equals(user, that.user);
}
@Override
public int hashCode() {
int result = super.hashCode();
result = 31 * result + (type != null ? type.hashCode() : 0);
result = 31 * result + (boardId != null ? boardId.hashCode() : 0);
result = 31 * result + (owner ? 1 : 0);
result = 31 * result + (permissionEdit ? 1 : 0);
result = 31 * result + (permissionShare ? 1 : 0);
result = 31 * result + (permissionManage ? 1 : 0);
result = 31 * result + (userId != null ? userId.hashCode() : 0);
result = 31 * result + (user != null ? user.hashCode() : 0);
return result;
}
@Override
public String toString() {
return "AccessControl{" +
"type=" + type +
", boardId=" + boardId +
", owner=" + owner +
", permissionEdit=" + permissionEdit +
", permissionShare=" + permissionShare +
", permissionManage=" + permissionManage +
", userId=" + userId +
", user=" + user +
", localId=" + localId +
", accountId=" + accountId +
", id=" + id +
", status=" + status +
", lastModified=" + lastModified +
", lastModifiedLocal=" + lastModifiedLocal +
"} " + super.toString();
}
}

View file

@ -0,0 +1,284 @@
package it.niedermann.nextcloud.deck.model;
import android.content.Context;
import android.net.Uri;
import androidx.annotation.ColorInt;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.Px;
import androidx.room.ColumnInfo;
import androidx.room.Entity;
import androidx.room.Ignore;
import androidx.room.Index;
import androidx.room.PrimaryKey;
import com.bumptech.glide.Glide;
import com.nextcloud.android.sso.AccountImporter;
import com.nextcloud.android.sso.exceptions.NextcloudFilesAppAccountNotFoundException;
import com.nextcloud.android.sso.model.SingleSignOnAccount;
import java.io.Serializable;
import java.util.Objects;
import java.util.Optional;
import it.niedermann.nextcloud.deck.DeckLog;
import it.niedermann.nextcloud.deck.model.ocs.Capabilities;
import it.niedermann.nextcloud.deck.model.ocs.Version;
import it.niedermann.nextcloud.sso.glide.SingleSignOnUrl;
@Entity(indices = {@Index(value = "name", unique = true)})
public class Account implements Serializable {
@Ignore
private static final long serialVersionUID = 0;
@PrimaryKey(autoGenerate = true)
protected Long id;
@NonNull
private String name;
@NonNull
private String userName;
@Ignore
@Nullable
private String userDisplayName;
@NonNull
private String url;
/**
* See {@link Capabilities#DEFAULT_COLOR}
*/
@NonNull
@ColumnInfo(defaultValue = "0")
private Integer color = Capabilities.DEFAULT_COLOR;
@NonNull
@ColumnInfo(defaultValue = "0")
private Integer textColor = Capabilities.DEFAULT_TEXT_COLOR;
@NonNull
@ColumnInfo(defaultValue = "0.6.4")
private String serverDeckVersion = "0.6.4";
@NonNull
@ColumnInfo(defaultValue = "0")
private boolean maintenanceEnabled = false;
private String etag;
private String boardsEtag;
@Ignore
public Account(Long id, @NonNull String name, @NonNull String userName, @NonNull String url) {
this(name, userName, url);
this.id = id;
}
@Ignore
public Account(@NonNull String name, @NonNull String userName, @NonNull String url) {
this.name = name;
this.userName = userName;
this.url = url;
}
@Ignore
public Account(Long id) {
this.id = id;
}
public Account() {
}
public void applyCapabilities(Capabilities capabilities, String eTag) {
if (capabilities == null) {
maintenanceEnabled = true;
return;
}
maintenanceEnabled = capabilities.isMaintenanceEnabled();
if (!isMaintenanceEnabled()) {
try {
// Nextcloud might return color format #000 which cannot be parsed by Color.parseColor()
// https://github.com/stefan-niedermann/nextcloud-deck/issues/466
color = capabilities.getColor();
textColor = capabilities.getTextColor();
} catch (Exception e) {
DeckLog.logError(e);
color = Capabilities.DEFAULT_COLOR;
textColor = Capabilities.DEFAULT_TEXT_COLOR;
}
if (capabilities.getDeckVersion() != null) {
serverDeckVersion = capabilities.getDeckVersion().getOriginalVersion();
}
if (eTag != null) {
this.etag = eTag;
}
}
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
@NonNull
public String getName() {
return name;
}
public void setName(@NonNull String name) {
this.name = name;
}
@NonNull
public String getUserName() {
return userName;
}
public void setUserName(@NonNull String userName) {
this.userName = userName;
}
@NonNull
public String getUrl() {
return url;
}
public void setUrl(@NonNull String url) {
this.url = url;
}
public static long getSerialVersionUID() {
return serialVersionUID;
}
@ColorInt
@NonNull
public Integer getColor() {
return color;
}
public void setColor(@NonNull Integer color) {
this.color = color;
}
@NonNull
public Integer getTextColor() {
return textColor;
}
@Deprecated
public void setTextColor(@NonNull Integer textColor) {
this.textColor = textColor;
}
public Version getServerDeckVersionAsObject() {
return Version.of(serverDeckVersion);
}
@NonNull
public String getServerDeckVersion() {
return serverDeckVersion;
}
public void setServerDeckVersion(@NonNull String serverDeckVersion) {
this.serverDeckVersion = serverDeckVersion;
}
public boolean isMaintenanceEnabled() {
return maintenanceEnabled;
}
public void setMaintenanceEnabled(boolean maintenanceEnabled) {
this.maintenanceEnabled = maintenanceEnabled;
}
@Nullable
public String getUserDisplayName() {
return userDisplayName;
}
public void setUserDisplayName(@Nullable String userDisplayName) {
this.userDisplayName = userDisplayName;
}
public String getEtag() {
return etag;
}
public void setEtag(String etag) {
this.etag = etag;
}
public String getBoardsEtag() {
return boardsEtag;
}
public void setBoardsEtag(String boardsEtag) {
this.boardsEtag = boardsEtag;
}
/**
* @return The {@link #getAvatarUrl(int, String)} of this {@link Account}
*/
public SingleSignOnUrl getAvatarUrl(@Px int size) {
return getAvatarUrl(size, getUserName());
}
/**
* @return a {@link SingleSignOnUrl} to fetch the avatar of the given <code>userName</code> from the instance of this {@link Account} via {@link Glide}.
*/
public SingleSignOnUrl getAvatarUrl(@Px int size, @NonNull String userName) {
return new SingleSignOnUrl(getName(), getUrl() + "/index.php/avatar/" + Uri.encode(userName) + "/" + size);
}
@NonNull
public Optional<SingleSignOnAccount> getSingleSignOnAccount(@NonNull Context context) {
try {
return Optional.of(AccountImporter.getSingleSignOnAccount(context, getName()));
} catch (NextcloudFilesAppAccountNotFoundException e) {
return Optional.empty();
}
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Account account = (Account) o;
return maintenanceEnabled == account.maintenanceEnabled &&
Objects.equals(id, account.id) &&
name.equals(account.name) &&
userName.equals(account.userName) &&
Objects.equals(userDisplayName, account.userDisplayName) &&
url.equals(account.url) &&
color.equals(account.color) &&
textColor.equals(account.textColor) &&
serverDeckVersion.equals(account.serverDeckVersion);
}
@Override
public int hashCode() {
return Objects.hash(id, name, userName, userDisplayName, url, color, textColor, serverDeckVersion, maintenanceEnabled, etag, boardsEtag);
}
@NonNull
@Override
public String toString() {
return "Account{" +
"id=" + id +
", name='" + name + '\'' +
", userName='" + userName + '\'' +
", url='" + url + '\'' +
", color='" + color + '\'' +
", textColor='" + textColor + '\'' +
", serverDeckVersion='" + serverDeckVersion + '\'' +
", maintenanceEnabled=" + maintenanceEnabled +
", eTag='" + etag + '\'' +
'}';
}
}

View file

@ -0,0 +1,247 @@
package it.niedermann.nextcloud.deck.model;
import androidx.annotation.Nullable;
import androidx.room.Entity;
import androidx.room.ForeignKey;
import androidx.room.Index;
import java.io.Serializable;
import java.time.Instant;
import java.util.Objects;
import it.niedermann.nextcloud.deck.model.enums.EAttachmentType;
import it.niedermann.nextcloud.deck.model.interfaces.AbstractRemoteEntity;
@Entity(inheritSuperIndices = true,
indices = {@Index("cardId")},
foreignKeys = {
@ForeignKey(
entity = Card.class,
parentColumns = "localId",
childColumns = "cardId",
onDelete = ForeignKey.CASCADE
),
@ForeignKey(
entity = Account.class,
parentColumns = "id",
childColumns = "accountId", onDelete = ForeignKey.CASCADE
)
}
)
public class Attachment extends AbstractRemoteEntity implements Comparable<Attachment>, Serializable {
private long cardId;
// TODO use EAttachmentType
private EAttachmentType type = EAttachmentType.DECK_FILE;
private String data;
private Instant createdAt;
private String createdBy;
private Instant deletedAt;
private long filesize;
private String mimetype;
private String dirname;
private String basename;
private String extension;
private String filename;
private String localPath;
@Nullable
private Long fileId;
public long getCardId() {
return cardId;
}
public void setCardId(long cardId) {
this.cardId = cardId;
}
public EAttachmentType getType() {
return type;
}
public void setType(EAttachmentType type) {
this.type = type;
}
public String getData() {
return data;
}
public void setData(String data) {
this.data = data;
}
public Instant getCreatedAt() {
return createdAt;
}
public void setCreatedAt(Instant createdAt) {
this.createdAt = createdAt;
}
public String getCreatedBy() {
return createdBy;
}
public void setCreatedBy(String createdBy) {
this.createdBy = createdBy;
}
public Instant getDeletedAt() {
return deletedAt;
}
public void setDeletedAt(Instant deletedAt) {
this.deletedAt = deletedAt;
}
public long getFilesize() {
return filesize;
}
public void setFilesize(long filesize) {
this.filesize = filesize;
}
public String getMimetype() {
return mimetype;
}
public void setMimetype(String mimetype) {
this.mimetype = mimetype;
}
public String getDirname() {
return dirname;
}
public void setDirname(String dirname) {
this.dirname = dirname;
}
public String getBasename() {
return basename;
}
public void setBasename(String basename) {
this.basename = basename;
}
public String getExtension() {
return extension;
}
public void setExtension(String extension) {
this.extension = extension;
}
public String getFilename() {
return filename;
}
public void setFilename(String filename) {
this.filename = filename;
}
public String getLocalPath() {
return localPath;
}
public void setLocalPath(String localPath) {
this.localPath = localPath;
}
@Nullable
public Long getFileId() {
return this.fileId;
}
public void setFileId(@Nullable Long fileId) {
this.fileId = fileId;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
if (!super.equals(o)) return false;
Attachment that = (Attachment) o;
if (cardId != that.cardId) return false;
if (filesize != that.filesize) return false;
if (!Objects.equals(type, that.type)) return false;
if (!Objects.equals(data, that.data)) return false;
if (!Objects.equals(createdAt, that.createdAt))
return false;
if (!Objects.equals(createdBy, that.createdBy))
return false;
if (!Objects.equals(deletedAt, that.deletedAt))
return false;
if (!Objects.equals(mimetype, that.mimetype))
return false;
if (!Objects.equals(dirname, that.dirname)) return false;
if (!Objects.equals(basename, that.basename))
return false;
if (!Objects.equals(extension, that.extension))
return false;
if (!Objects.equals(filename, that.filename))
return false;
return Objects.equals(localPath, that.localPath);
}
@Override
public int hashCode() {
int result = super.hashCode();
result = 31 * result + (int) (cardId ^ (cardId >>> 32));
result = 31 * result + (type != null ? type.hashCode() : 0);
result = 31 * result + (data != null ? data.hashCode() : 0);
result = 31 * result + (createdAt != null ? createdAt.hashCode() : 0);
result = 31 * result + (createdBy != null ? createdBy.hashCode() : 0);
result = 31 * result + (deletedAt != null ? deletedAt.hashCode() : 0);
result = 31 * result + (int) (filesize ^ (filesize >>> 32));
result = 31 * result + (mimetype != null ? mimetype.hashCode() : 0);
result = 31 * result + (dirname != null ? dirname.hashCode() : 0);
result = 31 * result + (basename != null ? basename.hashCode() : 0);
result = 31 * result + (extension != null ? extension.hashCode() : 0);
result = 31 * result + (filename != null ? filename.hashCode() : 0);
result = 31 * result + (localPath != null ? localPath.hashCode() : 0);
return result;
}
@Override
public int compareTo(Attachment other) {
// DESC order
long res = other.getModificationTimeForComparsion() - getModificationTimeForComparsion();
if (res == 0) {
return longToComparsionResult(other.getCreationTimeForComparsion() - getCreationTimeForComparsion());
}
return longToComparsionResult(res);
}
private static int longToComparsionResult(long diff) {
if (diff > 0) {
return 1;
} else if (diff < 0) {
return -1;
}
return 0;
}
public long getModificationTimeForComparsion() {
if (lastModifiedLocal != null) {
return lastModifiedLocal.toEpochMilli();
}
if (lastModified != null) {
return lastModified.toEpochMilli();
}
return Instant.now().toEpochMilli();
}
public long getCreationTimeForComparsion() {
if (createdAt != null) {
return createdAt.toEpochMilli();
}
return Instant.now().toEpochMilli();
}
}

View file

@ -0,0 +1,242 @@
package it.niedermann.nextcloud.deck.model;
import android.graphics.Color;
import androidx.annotation.ColorInt;
import androidx.room.Entity;
import androidx.room.ForeignKey;
import androidx.room.Ignore;
import androidx.room.Index;
import com.google.gson.annotations.JsonAdapter;
import java.io.Serializable;
import java.time.Instant;
import java.util.Objects;
import it.niedermann.android.util.ColorUtil;
import it.niedermann.nextcloud.deck.DeckLog;
import it.niedermann.nextcloud.deck.model.enums.DBStatus;
import it.niedermann.nextcloud.deck.model.interfaces.AbstractRemoteEntity;
import it.niedermann.nextcloud.deck.remote.api.json.JsonColorSerializer;
@Entity(
inheritSuperIndices = true,
indices = {@Index("ownerId")},
foreignKeys = {
@ForeignKey(
entity = User.class,
parentColumns = "localId",
childColumns = "ownerId", onDelete = ForeignKey.CASCADE
),
@ForeignKey(
entity = Account.class,
parentColumns = "id",
childColumns = "accountId", onDelete = ForeignKey.CASCADE
)
}
)
public class Board extends AbstractRemoteEntity implements Serializable {
public Board() {
}
@Ignore
public Board(String title, @ColorInt int color) {
setTitle(title);
setColor(color);
}
private String title;
private long ownerId;
@JsonAdapter(JsonColorSerializer.class)
private Integer color;
private boolean archived;
private int shared;
private Instant deletedAt;
private boolean permissionRead = false;
private boolean permissionEdit = false;
private boolean permissionManage = false;
private boolean permissionShare = false;
@Override
public Instant getLastModified() {
return lastModified;
}
@Override
public void setLastModified(Instant lastModified) {
this.lastModified = lastModified;
}
@Override
public Instant getLastModifiedLocal() {
return lastModifiedLocal;
}
@Override
public void setLastModifiedLocal(Instant lastModifiedLocal) {
this.lastModifiedLocal = lastModifiedLocal;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
@ColorInt
public Integer getColor() {
return color;
}
public void setColor(String color) {
try {
setColor(Color.parseColor(ColorUtil.formatColorToParsableHexString(color)));
} catch (Exception e) {
DeckLog.logError(e);
setColor(Color.GRAY);
}
}
public void setColor(@ColorInt Integer color) {
this.color = color;
}
public boolean isArchived() {
return archived;
}
//
// public String getAcl() {
// return acl;
// }
//
// public void setAcl(String acl) {
// this.acl = acl;
// }
public int getShared() {
return shared;
}
public void setShared(int shared) {
this.shared = shared;
}
public Instant getDeletedAt() {
return deletedAt;
}
public void setDeletedAt(Instant deletedAt) {
this.deletedAt = deletedAt;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public DBStatus getStatusEnum() {
return DBStatus.findById(status);
}
public void setStatusEnum(DBStatus status) {
this.status = status.getId();
}
public void setStatus(int status) {
this.status = status;
}
public int getStatus() {
return this.status;
}
public long getOwnerId() {
return this.ownerId;
}
public void setOwnerId(long ownerId) {
this.ownerId = ownerId;
}
public void setArchived(boolean archived) {
this.archived = archived;
}
public boolean isPermissionRead() {
return permissionRead;
}
public void setPermissionRead(boolean permissionRead) {
this.permissionRead = permissionRead;
}
public boolean isPermissionEdit() {
return permissionEdit;
}
public void setPermissionEdit(boolean permissionEdit) {
this.permissionEdit = permissionEdit;
}
public boolean isPermissionManage() {
return permissionManage;
}
public void setPermissionManage(boolean permissionManage) {
this.permissionManage = permissionManage;
}
public boolean isPermissionShare() {
return permissionShare;
}
public void setPermissionShare(boolean permissionShare) {
this.permissionShare = permissionShare;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
if (!super.equals(o)) return false;
Board board = (Board) o;
if (ownerId != board.ownerId) return false;
if (archived != board.archived) return false;
if (shared != board.shared) return false;
if (permissionRead != board.permissionRead) return false;
if (permissionEdit != board.permissionEdit) return false;
if (permissionManage != board.permissionManage) return false;
if (permissionShare != board.permissionShare) return false;
if (!Objects.equals(title, board.title)) return false;
if (!Objects.equals(color, board.color)) return false;
return Objects.equals(deletedAt, board.deletedAt);
}
@Override
public int hashCode() {
int result = super.hashCode();
result = 31 * result + (title != null ? title.hashCode() : 0);
result = 31 * result + (int) (ownerId ^ (ownerId >>> 32));
result = 31 * result + (color != null ? color.hashCode() : 0);
result = 31 * result + (archived ? 1 : 0);
result = 31 * result + shared;
result = 31 * result + (deletedAt != null ? deletedAt.hashCode() : 0);
result = 31 * result + (permissionRead ? 1 : 0);
result = 31 * result + (permissionEdit ? 1 : 0);
result = 31 * result + (permissionManage ? 1 : 0);
result = 31 * result + (permissionShare ? 1 : 0);
return result;
}
}

View file

@ -0,0 +1,340 @@
package it.niedermann.nextcloud.deck.model;
import androidx.annotation.NonNull;
import androidx.room.Entity;
import androidx.room.ForeignKey;
import androidx.room.Ignore;
import androidx.room.Index;
import com.google.gson.annotations.SerializedName;
import java.time.Instant;
import java.util.Objects;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import it.niedermann.nextcloud.deck.model.enums.DBStatus;
import it.niedermann.nextcloud.deck.model.interfaces.AbstractRemoteEntity;
@Entity(inheritSuperIndices = true,
indices = {
@Index(value = "accountId", name = "card_accID"),
@Index("stackId")
},
foreignKeys = {
@ForeignKey(
entity = Stack.class,
parentColumns = "localId",
childColumns = "stackId", onDelete = ForeignKey.CASCADE
),
@ForeignKey(
entity = Account.class,
parentColumns = "id",
childColumns = "accountId", onDelete = ForeignKey.CASCADE
)
}
)
public class Card extends AbstractRemoteEntity {
private static Pattern PATTERN_MD_TASK = Pattern.compile("\\[([xX ])]");
public static class TaskStatus {
public int taskCount;
public int doneCount;
public TaskStatus(int taskCount, int doneCount) {
this.taskCount = taskCount;
this.doneCount = doneCount;
}
}
@Ignore
private TaskStatus taskStatus = null;
private String title;
private String description;
@NonNull
private Long stackId;
private String type;
private Instant createdAt;
private Instant deletedAt;
private Instant done;
private int attachmentCount;
private Long userId;
private int order;
private boolean archived;
@SerializedName("duedate")
private Instant dueDate;
private boolean notified;
private int overdue;
private int commentsUnread;
public Card() {
}
@Ignore
public Card(String title, String description, long stackId) {
this.title = title;
this.description = description;
this.stackId = stackId;
}
public Card(Card card) {
super(card);
this.title = card.getTitle();
this.description = card.getDescription();
this.stackId = card.getStackId();
this.type = card.getType();
this.createdAt = card.getCreatedAt();
this.deletedAt = card.getDeletedAt();
this.attachmentCount = card.getAttachmentCount();
this.userId = card.getUserId();
this.order = card.getOrder();
this.archived = card.isArchived();
this.dueDate = card.getDueDate();
this.done = card.getDone();
this.notified = card.isNotified();
this.overdue = card.getOverdue();
this.commentsUnread = card.getCommentsUnread();
}
@NonNull
public TaskStatus getTaskStatus() {
if (taskStatus == null) {
int count = 0, done = 0;
if (description != null) {
final Matcher matcher = PATTERN_MD_TASK.matcher(description);
while (matcher.find()) {
count++;
char c = matcher.group().charAt(1);
if (c == 'x' || c == 'X') {
done++;
}
}
}
taskStatus = new TaskStatus(count, done);
}
return taskStatus;
}
public boolean isNotified() {
return notified;
}
public void setNotified(boolean notified) {
this.notified = notified;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public DBStatus getStatusEnum() {
return DBStatus.findById(status);
}
public void setStatusEnum(DBStatus status) {
this.status = status.getId();
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
this.taskStatus = null;
}
public Long getStackId() {
return stackId;
}
public void setStackId(Long stackId) {
this.stackId = stackId;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public Instant getCreatedAt() {
return createdAt;
}
public void setCreatedAt(Instant createdAt) {
this.createdAt = createdAt;
}
public Instant getDeletedAt() {
return deletedAt;
}
public void setDeletedAt(Instant deletedAt) {
this.deletedAt = deletedAt;
}
public int getAttachmentCount() {
return attachmentCount;
}
public void setAttachmentCount(int attachmentCount) {
this.attachmentCount = attachmentCount;
}
public void setOrder(int order) {
this.order = order;
}
public boolean isArchived() {
return archived;
}
public void setArchived(boolean archived) {
this.archived = archived;
}
public Instant getDueDate() {
return dueDate;
}
public void setDueDate(Instant dateTime) {
this.dueDate = dateTime;
}
public int getOverdue() {
return overdue;
}
public void setOverdue(int overdue) {
this.overdue = overdue;
}
public int getCommentsUnread() {
return commentsUnread;
}
public void setCommentsUnread(int commentsUnread) {
this.commentsUnread = commentsUnread;
}
public void setStatus(int status) {
this.status = status;
}
public int getStatus() {
return this.status;
}
public Long getUserId() {
return this.userId;
}
public void setUserId(Long userId) {
this.userId = userId;
}
public int getOrder() {
return this.order;
}
public Instant getDone() {
return done;
}
public void setDone(Instant done) {
this.done = done;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
if (!super.equals(o)) return false;
Card card = (Card) o;
if (stackId != card.stackId) return false;
if (attachmentCount != card.attachmentCount) return false;
if (order != card.order) return false;
if (archived != card.archived) return false;
if (notified != card.notified) return false;
if (overdue != card.overdue) return false;
if (commentsUnread != card.commentsUnread) return false;
if (!Objects.equals(title, card.title)) return false;
if (!Objects.equals(description, card.description))
return false;
if (!Objects.equals(type, card.type)) return false;
if (!Objects.equals(createdAt, card.createdAt))
return false;
if (!Objects.equals(deletedAt, card.deletedAt))
return false;
if (!Objects.equals(done, card.done))
return false;
if (!Objects.equals(userId, card.userId)) return false;
return Objects.equals(dueDate, card.dueDate);
}
@Override
public int hashCode() {
int result = title != null ? title.hashCode() : 0;
result = 31 * result + (description != null ? description.hashCode() : 0);
result = 31 * result + (int) (stackId ^ (stackId >>> 32));
result = 31 * result + (type != null ? type.hashCode() : 0);
result = 31 * result + (createdAt != null ? createdAt.hashCode() : 0);
result = 31 * result + (deletedAt != null ? deletedAt.hashCode() : 0);
result = 31 * result + (done != null ? done.hashCode() : 0);
result = 31 * result + attachmentCount;
result = 31 * result + (userId != null ? userId.hashCode() : 0);
result = 31 * result + order;
result = 31 * result + (archived ? 1 : 0);
result = 31 * result + (dueDate != null ? dueDate.hashCode() : 0);
result = 31 * result + (notified ? 1 : 0);
result = 31 * result + overdue;
result = 31 * result + commentsUnread;
return result;
}
@Override
public String toString() {
return "Card{" +
"title='" + title + '\'' +
", description='" + description + '\'' +
", stackId=" + stackId +
", type='" + type + '\'' +
", createdAt=" + createdAt +
", deletedAt=" + deletedAt +
", done=" + done +
", attachmentCount=" + attachmentCount +
", userId=" + userId +
", order=" + order +
", archived=" + archived +
", dueDate=" + dueDate +
", notified=" + notified +
", overdue=" + overdue +
", commentsUnread=" + commentsUnread +
", localId=" + localId +
", accountId=" + accountId +
", id=" + id +
", status=" + status +
", lastModified=" + lastModified +
", lastModifiedLocal=" + lastModifiedLocal +
'}';
}
}

View file

@ -0,0 +1,60 @@
package it.niedermann.nextcloud.deck.model;
import androidx.annotation.NonNull;
import androidx.room.Entity;
import androidx.room.ForeignKey;
import androidx.room.Index;
import it.niedermann.nextcloud.deck.model.interfaces.AbstractJoinEntity;
@Entity(
primaryKeys = {"labelId", "boardId"},
indices = {@Index("boardId"), @Index("labelId")},
foreignKeys = {
@ForeignKey(entity = Board.class,
parentColumns = "localId",
childColumns = "boardId", onDelete = ForeignKey.CASCADE),
@ForeignKey(entity = Label.class,
parentColumns = "localId",
childColumns = "labelId", onDelete = ForeignKey.CASCADE)
})
public class JoinBoardWithLabel extends AbstractJoinEntity {
@NonNull
private Long boardId;
@NonNull
private Long labelId;
public Long getLabelId() {
return labelId;
}
public void setLabelId(Long labelId) {
this.labelId = labelId;
}
public Long getBoardId() {
return boardId;
}
public void setBoardId(Long boardId) {
this.boardId = boardId;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
JoinBoardWithLabel that = (JoinBoardWithLabel) o;
if (!boardId.equals(that.boardId)) return false;
return labelId.equals(that.labelId);
}
@Override
public int hashCode() {
int result = boardId.hashCode();
result = 31 * result + labelId.hashCode();
return result;
}
}

View file

@ -0,0 +1,59 @@
package it.niedermann.nextcloud.deck.model;
import androidx.annotation.NonNull;
import androidx.room.Entity;
import androidx.room.ForeignKey;
import androidx.room.Index;
import it.niedermann.nextcloud.deck.model.interfaces.AbstractJoinEntity;
@Entity(
primaryKeys = {"permissionId", "boardId"},
indices = {@Index("boardId"), @Index("permissionId")},
foreignKeys = {
@ForeignKey(entity = Board.class,
parentColumns = "localId",
childColumns = "boardId", onDelete = ForeignKey.CASCADE),
@ForeignKey(entity = Permission.class,
parentColumns = "id",
childColumns = "permissionId", onDelete = ForeignKey.CASCADE)
})
public class JoinBoardWithPermission extends AbstractJoinEntity {
@NonNull
private Long permissionId;
@NonNull
private Long boardId;
public Long getPermissionId() {
return permissionId;
}
public void setPermissionId(Long permissionId) {
this.permissionId = permissionId;
}
public Long getBoardId() {
return boardId;
}
public void setBoardId(Long boardId) {
this.boardId = boardId;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
JoinBoardWithPermission that = (JoinBoardWithPermission) o;
if (!permissionId.equals(that.permissionId)) return false;
return boardId.equals(that.boardId);
}
@Override
public int hashCode() {
int result = permissionId.hashCode();
result = 31 * result + boardId.hashCode();
return result;
}
}

View file

@ -0,0 +1,59 @@
package it.niedermann.nextcloud.deck.model;
import androidx.annotation.NonNull;
import androidx.room.Entity;
import androidx.room.ForeignKey;
import androidx.room.Index;
import it.niedermann.nextcloud.deck.model.interfaces.AbstractJoinEntity;
@Entity(
primaryKeys = {"userId", "boardId"},
indices = {@Index("boardId"), @Index("userId")},
foreignKeys = {
@ForeignKey(entity = Board.class,
parentColumns = "localId",
childColumns = "boardId", onDelete = ForeignKey.CASCADE),
@ForeignKey(entity = User.class,
parentColumns = "localId",
childColumns = "userId", onDelete = ForeignKey.CASCADE)
})
public class JoinBoardWithUser extends AbstractJoinEntity {
@NonNull
private Long userId;
@NonNull
private Long boardId;
public Long getUserId() {
return userId;
}
public void setUserId(Long userId) {
this.userId = userId;
}
public Long getBoardId() {
return boardId;
}
public void setBoardId(Long boardId) {
this.boardId = boardId;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
JoinBoardWithUser that = (JoinBoardWithUser) o;
if (!userId.equals(that.userId)) return false;
return boardId.equals(that.boardId);
}
@Override
public int hashCode() {
int result = userId.hashCode();
result = 31 * result + boardId.hashCode();
return result;
}
}

View file

@ -0,0 +1,63 @@
package it.niedermann.nextcloud.deck.model;
import androidx.room.Entity;
import androidx.room.ForeignKey;
import androidx.room.Index;
import androidx.annotation.NonNull;
import it.niedermann.nextcloud.deck.model.interfaces.AbstractJoinEntity;
@Entity(
primaryKeys = {"labelId", "cardId"},
indices = {@Index("cardId"), @Index("labelId")},
foreignKeys = {
@ForeignKey(entity = Label.class,
parentColumns = "localId",
childColumns = "labelId",
onDelete = ForeignKey.CASCADE
),
@ForeignKey(entity = Card.class,
parentColumns = "localId",
childColumns = "cardId",
onDelete = ForeignKey.CASCADE
)
})
public class JoinCardWithLabel extends AbstractJoinEntity {
@NonNull
private Long labelId;
@NonNull
private Long cardId;
public Long getLabelId() {
return labelId;
}
public void setLabelId(Long labelId) {
this.labelId = labelId;
}
public Long getCardId() {
return cardId;
}
public void setCardId(Long cardId) {
this.cardId = cardId;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
JoinCardWithLabel that = (JoinCardWithLabel) o;
if (!labelId.equals(that.labelId)) return false;
return cardId.equals(that.cardId);
}
@Override
public int hashCode() {
int result = labelId.hashCode();
result = 31 * result + cardId.hashCode();
return result;
}
}

View file

@ -0,0 +1,59 @@
package it.niedermann.nextcloud.deck.model;
import androidx.annotation.NonNull;
import androidx.room.Entity;
import androidx.room.ForeignKey;
import androidx.room.Index;
import it.niedermann.nextcloud.deck.model.interfaces.AbstractJoinEntity;
@Entity(
primaryKeys = {"userId", "cardId"},
indices = {@Index("cardId"), @Index("userId")},
foreignKeys = {
@ForeignKey(entity = User.class,
parentColumns = "localId",
childColumns = "userId", onDelete = ForeignKey.CASCADE),
@ForeignKey(entity = Card.class,
parentColumns = "localId",
childColumns = "cardId", onDelete = ForeignKey.CASCADE)
})
public class JoinCardWithUser extends AbstractJoinEntity {
@NonNull
private Long userId;
@NonNull
private Long cardId;
public Long getUserId() {
return userId;
}
public void setUserId(Long userId) {
this.userId = userId;
}
public Long getCardId() {
return cardId;
}
public void setCardId(Long cardId) {
this.cardId = cardId;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
JoinCardWithUser that = (JoinCardWithUser) o;
if (!userId.equals(that.userId)) return false;
return cardId.equals(that.cardId);
}
@Override
public int hashCode() {
int result = userId.hashCode();
result = 31 * result + cardId.hashCode();
return result;
}
}

View file

@ -0,0 +1,128 @@
package it.niedermann.nextcloud.deck.model;
import android.graphics.Color;
import androidx.annotation.ColorInt;
import androidx.annotation.NonNull;
import androidx.room.ColumnInfo;
import androidx.room.Entity;
import androidx.room.ForeignKey;
import androidx.room.Index;
import com.google.gson.annotations.JsonAdapter;
import java.io.Serializable;
import java.util.Objects;
import it.niedermann.android.util.ColorUtil;
import it.niedermann.nextcloud.deck.DeckLog;
import it.niedermann.nextcloud.deck.model.interfaces.AbstractRemoteEntity;
import it.niedermann.nextcloud.deck.remote.api.json.JsonColorSerializer;
@Entity(inheritSuperIndices = true,
indices = {@Index("boardId"), @Index(value = {"boardId", "title"}, unique = true, name = "idx_label_title_unique")},
foreignKeys = {
@ForeignKey(
entity = Board.class,
parentColumns = "localId",
childColumns = "boardId",
onDelete = ForeignKey.CASCADE
),
@ForeignKey(
entity = Account.class,
parentColumns = "id",
childColumns = "accountId", onDelete = ForeignKey.CASCADE
)
}
)
public class Label extends AbstractRemoteEntity implements Serializable {
private String title;
@JsonAdapter(JsonColorSerializer.class)
@NonNull
@ColumnInfo(defaultValue = "0")
private Integer color;
private long boardId;
public Label() {
}
public Label(Label labelToCopy) {
super(labelToCopy);
this.title = labelToCopy.getTitle();
this.color = labelToCopy.getColor();
this.boardId = labelToCopy.getBoardId();
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
@NonNull
@ColorInt
public Integer getColor() {
return color;
}
public void setColor(@NonNull @ColorInt Integer color) {
this.color = color;
}
public void setColor(String color) {
try {
setColor(Color.parseColor(ColorUtil.formatColorToParsableHexString(color)));
} catch (Exception e) {
DeckLog.logError(e);
setColor(Color.GRAY);
}
}
public long getBoardId() {
return boardId;
}
public void setBoardId(long boardId) {
this.boardId = boardId;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
if (!super.equals(o)) return false;
Label label = (Label) o;
if (boardId != label.boardId) return false;
if (!Objects.equals(title, label.title)) return false;
return color.equals(label.color);
}
@Override
public int hashCode() {
int result = super.hashCode();
result = 31 * result + (title != null ? title.hashCode() : 0);
result = 31 * result + color.hashCode();
result = 31 * result + (int) (boardId ^ (boardId >>> 32));
return result;
}
@Override
public String toString() {
return "Label{" +
"title='" + title + '\'' +
", color='" + color + '\'' +
", boardId=" + boardId +
", localId=" + localId +
", accountId=" + accountId +
", id=" + id +
", status=" + status +
", lastModified=" + lastModified +
", lastModifiedLocal=" + lastModifiedLocal +
'}';
}
}

View file

@ -0,0 +1,44 @@
package it.niedermann.nextcloud.deck.model;
import androidx.room.Entity;
import androidx.room.PrimaryKey;
import it.niedermann.nextcloud.deck.model.enums.PermissionType;
@Entity(inheritSuperIndices = true)
public class Permission {
@PrimaryKey(autoGenerate = true)
private long id;
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public PermissionType getType() {
return PermissionType.findById(id);
}
public void setType(PermissionType type) {
this.id = type.getId();
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Permission that = (Permission) o;
return id == that.id;
}
@Override
public int hashCode() {
return (int) (id ^ (id >>> 32));
}
}

View file

@ -0,0 +1,123 @@
package it.niedermann.nextcloud.deck.model;
import androidx.room.Entity;
import androidx.room.ForeignKey;
import androidx.room.Ignore;
import androidx.room.Index;
import java.time.Instant;
import java.util.Objects;
import it.niedermann.nextcloud.deck.model.interfaces.AbstractRemoteEntity;
@Entity(
inheritSuperIndices = true,
indices = {@Index("boardId")},
foreignKeys = {
@ForeignKey(
entity = Board.class,
parentColumns = "localId",
childColumns = "boardId", onDelete = ForeignKey.CASCADE
),
@ForeignKey(
entity = Account.class,
parentColumns = "id",
childColumns = "accountId", onDelete = ForeignKey.CASCADE
)
}
)
public class Stack extends AbstractRemoteEntity {
public Stack() {
}
@Ignore
public Stack(String title, long boardId) {
this.title = title;
this.boardId = boardId;
}
private String title;
private long boardId;
private Instant deletedAt;
private int order;
//
// @ToMany
// @JoinEntity(entity = JoinStackWithCard.class, sourceProperty = "stackId", targetProperty = "cardId")
// private List<Card> cards;
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public long getBoardId() {
return boardId;
}
public void setBoardId(long boardId) {
this.boardId = boardId;
}
public Instant getDeletedAt() {
return deletedAt;
}
public void setDeletedAt(Instant deletedAt) {
this.deletedAt = deletedAt;
}
public int getOrder() {
return order;
}
public void setOrder(int order) {
this.order = order;
}
public void setStatus(int status) {
this.status = status;
}
public int getStatus() {
return this.status;
}
public long getAccountId() {
return this.accountId;
}
public void setAccountId(long accountId) {
this.accountId = accountId;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Stack stack = (Stack) o;
if (boardId != stack.boardId) return false;
if (order != stack.order) return false;
if (!Objects.equals(title, stack.title)) return false;
return Objects.equals(deletedAt, stack.deletedAt);
}
@Override
public int hashCode() {
int result = title != null ? title.hashCode() : 0;
result = 31 * result + (int) (boardId ^ (boardId >>> 32));
result = 31 * result + (deletedAt != null ? deletedAt.hashCode() : 0);
result = 31 * result + order;
return result;
}
}

View file

@ -0,0 +1,122 @@
package it.niedermann.nextcloud.deck.model;
import androidx.room.Entity;
import androidx.room.ForeignKey;
import androidx.room.Ignore;
import androidx.room.Index;
import java.io.Serializable;
import it.niedermann.nextcloud.deck.model.interfaces.AbstractRemoteEntity;
@Entity(inheritSuperIndices = true,
indices = {
@Index(value = "uid", name = "user_uid")
},
foreignKeys = {
@ForeignKey(
entity = Account.class,
parentColumns = "id",
childColumns = "accountId", onDelete = ForeignKey.CASCADE
)
}
)
public class User extends AbstractRemoteEntity implements Serializable {
public static final long TYPE_USER = 0L;
public static final long TYPE_GROUP = 1L;
private String primaryKey;
private String uid;
private String displayname;
private long type;
public User() {
super();
}
@Ignore
public User(String primaryKey, String uid, String displayname) {
this.primaryKey = primaryKey;
this.uid = uid;
this.displayname = displayname;
}
public User(User user) {
super(user);
this.primaryKey = user.getPrimaryKey();
this.uid = user.getUid();
this.type = user.getType();
this.displayname = user.getDisplayname();
}
public String getPrimaryKey() {
return primaryKey;
}
public void setPrimaryKey(String primaryKey) {
this.primaryKey = primaryKey;
}
public String getUid() {
return uid;
}
public void setUid(String uid) {
this.uid = uid;
}
public String getDisplayname() {
return displayname;
}
public void setDisplayname(String displayname) {
this.displayname = displayname;
}
public long getType() {
return type;
}
public void setType(long type) {
this.type = type;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
if (!super.equals(o)) return false;
User user = (User) o;
return type == user.type && primaryKey.equals(user.primaryKey) &&
uid.equals(user.uid) && displayname.equals(user.displayname);
}
@Override
public int hashCode() {
int result = super.hashCode();
result = 31 * result + primaryKey.hashCode();
result = 31 * result + uid.hashCode();
result = 31 * result + displayname.hashCode();
result = 31 * result + Long.hashCode(type);
return result;
}
@Override
public String toString() {
return "User{" +
"primaryKey='" + primaryKey + '\'' +
", uid='" + uid + '\'' +
", displayname='" + displayname + '\'' +
", type=" + type +
", localId=" + localId +
", accountId=" + accountId +
", id=" + id +
", status=" + status +
", lastModified=" + lastModified +
", lastModifiedLocal=" + lastModifiedLocal +
", etag='" + etag + '\'' +
"} " + super.toString();
}
}

View file

@ -0,0 +1,68 @@
package it.niedermann.nextcloud.deck.model.appwidgets;
import androidx.room.Entity;
import androidx.room.ForeignKey;
import androidx.room.Index;
import androidx.room.PrimaryKey;
import it.niedermann.nextcloud.deck.model.Account;
import it.niedermann.nextcloud.deck.model.Stack;
@Entity(
indices = {
@Index("stackId"),
@Index("accountId")
},
foreignKeys = {
@ForeignKey(
entity = Account.class,
parentColumns = "id",
childColumns = "accountId", onDelete = ForeignKey.CASCADE
),
@ForeignKey(
entity = Stack.class,
parentColumns = "localId",
childColumns = "stackId", onDelete = ForeignKey.CASCADE
)
}
)
public class StackWidgetModel {
@PrimaryKey()
private Integer appWidgetId;
private Long accountId;
private Long stackId;
private boolean darkTheme;
public Integer getAppWidgetId() {
return appWidgetId;
}
public void setAppWidgetId(Integer appWidgetId) {
this.appWidgetId = appWidgetId;
}
public Long getAccountId() {
return accountId;
}
public void setAccountId(Long accountId) {
this.accountId = accountId;
}
public Long getStackId() {
return stackId;
}
public void setStackId(Long stackId) {
this.stackId = stackId;
}
public boolean getDarkTheme() {
return darkTheme;
}
public void setDarkTheme(boolean darkTheme) {
this.darkTheme = darkTheme;
}
}

View file

@ -0,0 +1,62 @@
package it.niedermann.nextcloud.deck.model.enums;
import it.niedermann.nextcloud.deck.DeckLog;
public enum ActivityType {
DECK (1, "deck-dark.svg"),
CHANGE (2, "change.svg"),
ADD (3, "add-color.svg"),
DELETE (4, "delete-color.svg"),
ARCHIVE (5, "archive.svg"),
HISTORY (6, "actions/history.svg"),
FILES (7, "places/files.svg"),
COMMENT (8, "actions/comment.svg"),
TAGGED_WITH_LABEL (9, "actions/tag.svg")
;
int id;
String name;
ActivityType(int id, String name) {
this.id = id;
this.name = name;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public static ActivityType findById(int id) {
for (ActivityType s : ActivityType.values()) {
if (s.getId() == id) {
return s;
}
}
DeckLog.error("unknown ActivityType path:", id);
return CHANGE;
}
public static ActivityType findByPath(String path) {
if (path == null) {
throw new IllegalArgumentException("path is null");
}
for (ActivityType s : ActivityType.values()) {
if (path.trim().endsWith(s.getName())) {
return s;
}
}
DeckLog.error("unknown ActivityType path:", path);
return CHANGE;
}
}

View file

@ -0,0 +1,56 @@
package it.niedermann.nextcloud.deck.model.enums;
/**
* Helps to distinguish between different local change types for Server Synchronization.
* Created by stefan on 19.09.15.
*/
public enum DBStatus {
/**
* UP_TO_DATE means, that the Entity was not modified locally
*/
UP_TO_DATE(1),
/**
* LOCAL_EDITED means that a Entity was changed since the last successful synchronization.
* If it was newly created, then REMOTE_ID is 0
*/
LOCAL_EDITED(2),
/**
* LOCAL_DELETED means that the Entity was deleted locally, but this information was not yet synchronized.
* Therefore, the Entity has to be kept locally until the synchronization has succeeded.
* However, Entitys with this status should not be displayed in the UI.
*/
LOCAL_DELETED(3),
/**
* LOCAL_MOVED means that the Entity was moved locally, but this information was not yet synchronized.
* Therefore, the Entity has to be kept locally until the synchronization has succeeded.
*/
LOCAL_MOVED(4),
/**
* LOCAL_EDITED_SILENT means the same as LOCAL_EDITED, but doesn't need to be shown as changed in the UI
*/
LOCAL_EDITED_SILENT(5);
private final int id;
public int getId() {
return id;
}
DBStatus(int id) {
this.id = id;
}
public static DBStatus findById(int id) {
for (DBStatus s : DBStatus.values()) {
if (s.getId() == id) {
return s;
}
}
throw new IllegalArgumentException("unknown DBStatus key");
}
}

View file

@ -0,0 +1,33 @@
package it.niedermann.nextcloud.deck.model.enums;
public enum EAttachmentType {
// Do not change values. They match the Deck server apps values.
DECK_FILE(1, "deck_file"),
FILE(2, "file"),
UNKNOWN(1337, "unknown");
private final int id;
private final String value;
EAttachmentType(int id, String value) {
this.id = id;
this.value = value;
}
public int getId() {
return id;
}
public static EAttachmentType findByValue(String value) {
for (EAttachmentType s : EAttachmentType.values()) {
if (s.value.equals(value)) {
return s;
}
}
return UNKNOWN;
}
public String getValue() {
return value;
}
}

View file

@ -0,0 +1,40 @@
package it.niedermann.nextcloud.deck.model.enums;
import android.content.Context;
import androidx.annotation.NonNull;
import androidx.annotation.StringRes;
import it.niedermann.nextcloud.deck.R;
public enum EDoneType {
NO_FILTER(1, R.string.filter_done_no_filter),
DONE(2, R.string.filter_done_done),
UNDONE(3, R.string.filter_done_undone);
private final int value;
private final int id;
EDoneType(int id, @StringRes int value) {
this.value = value;
this.id = id;
}
public int getId() {
return id;
}
public static EDoneType findById(int id) {
for (EDoneType s : EDoneType.values()) {
if (s.getId() == id) {
return s;
}
}
throw new IllegalArgumentException("unknown " + EDoneType.class.getSimpleName() + " key: " + id);
}
@NonNull
public String toString(Context context) {
return context.getString(this.value);
}
}

View file

@ -0,0 +1,43 @@
package it.niedermann.nextcloud.deck.model.enums;
import android.content.Context;
import androidx.annotation.NonNull;
import androidx.annotation.StringRes;
import it.niedermann.nextcloud.deck.R;
public enum EDueType {
NO_FILTER(1, R.string.filter_no_filter),
OVERDUE(2, R.string.filter_overdue),
TODAY(3, R.string.filter_today),
WEEK(4, R.string.filter_week),
MONTH(5, R.string.filter_month),
NO_DUE(6, R.string.filter_no_due);
private final int value;
private final int id;
EDueType(int id, @StringRes int value) {
this.value = value;
this.id = id;
}
public int getId() {
return id;
}
public static EDueType findById(int id) {
for (EDueType s : EDueType.values()) {
if (s.getId() == id) {
return s;
}
}
throw new IllegalArgumentException("unknown " + EDueType.class.getSimpleName() + " key: " + id);
}
@NonNull
public String toString(Context context) {
return context.getString(this.value);
}
}

View file

@ -0,0 +1,35 @@
package it.niedermann.nextcloud.deck.model.enums;
public enum ESortCriteria {
/**
* Account Board Stack
*/
LOCATION(1),
/**
* Modification date of the card including comments, attachments etc.
*/
MODIFIED(2),
LAST_COMMENTED(3),
DUE_DATE(4),
ASSIGNEE(5),
LABEL(6);
private final int id;
ESortCriteria(int id) {
this.id = id;
}
public int getId() {
return id;
}
public static ESortCriteria findById(int id) {
for (ESortCriteria s : ESortCriteria.values()) {
if (s.getId() == id) {
return s;
}
}
throw new IllegalArgumentException("unknown " + ESortCriteria.class.getSimpleName() + " key: " + id);
}
}

View file

@ -0,0 +1,43 @@
package it.niedermann.nextcloud.deck.model.enums;
public enum PermissionType {
READ(1, "PERMISSION_READ"),
EDIT(2, "PERMISSION_EDIT"),
MANAGE(3, "PERMISSION_MANAGE"),
SHARE(4, "PERMISSION_SHARE");
private final long id;
private final String key;
PermissionType(long id, String key) {
this.id = id;
this.key = key;
}
public static PermissionType findByKey(String key) {
for (PermissionType s : PermissionType.values()) {
if (s.getKey().equals(key)) {
return s;
}
}
throw new IllegalArgumentException("unknown Permission key");
}
public static PermissionType findById(long key) {
for (PermissionType s : PermissionType.values()) {
if (s.getId() == key) {
return s;
}
}
throw new IllegalArgumentException("unknown Permission ID");
}
public String getKey() {
return key;
}
public long getId() {
return id;
}
}

View file

@ -0,0 +1,128 @@
package it.niedermann.nextcloud.deck.model.full;
import androidx.annotation.NonNull;
import androidx.room.Embedded;
import androidx.room.Ignore;
import androidx.room.Relation;
import java.util.List;
import java.util.Objects;
import it.niedermann.nextcloud.deck.model.AccessControl;
import it.niedermann.nextcloud.deck.model.Board;
import it.niedermann.nextcloud.deck.model.Label;
import it.niedermann.nextcloud.deck.model.Stack;
import it.niedermann.nextcloud.deck.model.User;
import it.niedermann.nextcloud.deck.model.interfaces.IRemoteEntity;
public class FullBoard implements IRemoteEntity {
@Embedded
public Board board;
@Relation(entity = Label.class, parentColumn = "localId", entityColumn = "boardId")
public List<Label> labels;
@Relation(parentColumn = "ownerId", entityColumn = "localId")
public User owner;
@Relation(entity = AccessControl.class, parentColumn = "localId", entityColumn = "boardId")
public List<AccessControl> participants;
@Relation(entity = Stack.class, parentColumn = "localId", entityColumn = "boardId")
public List<Stack> stacks;
@Ignore
public List<User> users;
public User getOwner() {
return owner;
}
public void setOwner(User owner) {
this.owner = owner;
}
public Board getBoard() {
return board;
}
public void setBoard(Board board) {
this.board = board;
}
public List<Label> getLabels() {
return labels;
}
public void setLabels(List<Label> labels) {
this.labels = labels;
}
public List<User> getUsers() {
return users;
}
public void setUsers(List<User> users) {
this.users = users;
}
@Ignore
@Override
public Board getEntity() {
return board;
}
public List<AccessControl> getParticipants() {
return participants;
}
public void setParticipants(List<AccessControl> participants) {
this.participants = participants;
}
public List<Stack> getStacks() {
return stacks;
}
public void setStacks(List<Stack> stacks) {
this.stacks = stacks;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
FullBoard fullBoard = (FullBoard) o;
if (!Objects.equals(board, fullBoard.board)) return false;
if (!Objects.equals(labels, fullBoard.labels))
return false;
if (!Objects.equals(owner, fullBoard.owner)) return false;
if (!Objects.equals(participants, fullBoard.participants))
return false;
return Objects.equals(stacks, fullBoard.stacks);
}
@Override
public int hashCode() {
int result = board != null ? board.hashCode() : 0;
result = 31 * result + (labels != null ? labels.hashCode() : 0);
result = 31 * result + (owner != null ? owner.hashCode() : 0);
result = 31 * result + (participants != null ? participants.hashCode() : 0);
result = 31 * result + (stacks != null ? stacks.hashCode() : 0);
return result;
}
@NonNull
@Override
public String toString() {
return "FullBoard{" +
"board=" + board +
", labels=" + labels +
", owner=" + owner +
", participants=" + participants +
", stacks=" + stacks +
'}';
}
}

View file

@ -0,0 +1,178 @@
package it.niedermann.nextcloud.deck.model.full;
import androidx.annotation.NonNull;
import androidx.room.Embedded;
import androidx.room.Ignore;
import androidx.room.Junction;
import androidx.room.Relation;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import it.niedermann.android.crosstabdnd.DragAndDropModel;
import it.niedermann.nextcloud.deck.model.Attachment;
import it.niedermann.nextcloud.deck.model.Card;
import it.niedermann.nextcloud.deck.model.JoinCardWithLabel;
import it.niedermann.nextcloud.deck.model.JoinCardWithUser;
import it.niedermann.nextcloud.deck.model.Label;
import it.niedermann.nextcloud.deck.model.User;
import it.niedermann.nextcloud.deck.model.interfaces.IRemoteEntity;
import it.niedermann.nextcloud.deck.model.ocs.comment.DeckComment;
public class FullCard implements IRemoteEntity, DragAndDropModel {
@Ignore
protected transient boolean isAttachmentsSorted = false;
@Embedded
public Card card;
@Relation(entity = Label.class, parentColumn = "localId", entityColumn = "localId",
associateBy = @Junction(value = JoinCardWithLabel.class, parentColumn = "cardId", entityColumn = "labelId"))
public List<Label> labels = new ArrayList<>();
@Relation(entity = User.class, parentColumn = "localId", entityColumn = "localId",
associateBy = @Junction(value = JoinCardWithUser.class, parentColumn = "cardId", entityColumn = "userId"))
public List<User> assignedUsers = new ArrayList<>();
@Relation(parentColumn = "userId", entityColumn = "localId")
public List<User> owner;
@Relation(parentColumn = "localId", entityColumn = "cardId")
public List<Attachment> attachments;
@Relation(entity = DeckComment.class, parentColumn = "localId", entityColumn = "objectId", projection = "localId")
public List<Long> commentIDs;
public FullCard() {
super();
}
public FullCard(FullCard fullCard) {
this.card = new Card(fullCard.getCard());
this.labels = copyList(fullCard.getLabels());
this.assignedUsers = copyList(fullCard.getAssignedUsers());
this.owner = copyList(fullCard.getOwner());
this.attachments = copyList(fullCard.getAttachments());
this.commentIDs = copyList(fullCard.getCommentIDs());
}
public Card getCard() {
return card;
}
public void setCard(Card card) {
this.card = card;
}
public List<Label> getLabels() {
return labels;
}
public void setLabels(List<Label> labels) {
this.labels = labels;
}
public List<User> getAssignedUsers() {
return assignedUsers;
}
public void setAssignedUsers(List<User> assignedUsers) {
this.assignedUsers = assignedUsers;
}
public void setCommentIDs(List<Long> commentIDs) {
this.commentIDs = commentIDs;
}
public List<Long> getCommentIDs() {
return commentIDs;
}
public int getCommentCount() {
return commentIDs == null ? 0 : commentIDs.size();
}
public List<User> getOwner() {
return owner;
}
public void setOwner(User owner) {
List<User> user = new ArrayList<>();
user.add(owner);
this.owner = user;
}
public void setOwner(List<User> owner) {
this.owner = owner;
}
public List<Attachment> getAttachments() {
if (!isAttachmentsSorted && attachments != null) {
Collections.sort(attachments);
isAttachmentsSorted = true;
}
return attachments;
}
public void setAttachments(List<Attachment> attachments) {
this.attachments = attachments;
}
@Ignore
@Override
public Card getEntity() {
return card;
}
@NonNull
@Override
public String toString() {
return "FullCard{" +
"card=" + card +
", labels=" + labels +
", assignedUsers=" + assignedUsers +
", owner=" + owner +
", attachments=" + attachments +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
FullCard fullCard = (FullCard) o;
if (!Objects.equals(card, fullCard.card)) return false;
if (!Objects.equals(labels, fullCard.labels))
return false;
if (!Objects.equals(assignedUsers, fullCard.assignedUsers))
return false;
if (!Objects.equals(owner, fullCard.owner)) return false;
if (!Objects.equals(attachments, fullCard.attachments))
return false;
return Objects.equals(commentIDs, fullCard.commentIDs);
}
@Override
public int hashCode() {
int result = (isAttachmentsSorted ? 1 : 0);
result = 31 * result + (card != null ? card.hashCode() : 0);
result = 31 * result + (labels != null ? labels.hashCode() : 0);
result = 31 * result + (assignedUsers != null ? assignedUsers.hashCode() : 0);
result = 31 * result + (owner != null ? owner.hashCode() : 0);
result = 31 * result + (attachments != null ? attachments.hashCode() : 0);
result = 31 * result + (commentIDs != null ? commentIDs.hashCode() : 0);
return result;
}
@NonNull
@Override
public Long getComparableId() {
return getLocalId();
}
}

View file

@ -0,0 +1,83 @@
package it.niedermann.nextcloud.deck.model.full;
import androidx.annotation.NonNull;
import androidx.room.Junction;
import androidx.room.Relation;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import it.niedermann.nextcloud.deck.model.ocs.projects.JoinCardWithProject;
import it.niedermann.nextcloud.deck.model.ocs.projects.OcsProject;
import it.niedermann.nextcloud.deck.model.ocs.projects.full.OcsProjectWithResources;
public class FullCardWithProjects extends FullCard {
@NonNull
@Relation(entity = OcsProject.class, parentColumn = "localId", entityColumn = "localId",
associateBy = @Junction(value = JoinCardWithProject.class, parentColumn = "cardId", entityColumn = "projectId"))
private List<OcsProjectWithResources> projects = new ArrayList<>();
public FullCardWithProjects() {
super();
}
public FullCardWithProjects(FullCardWithProjects fullCard) {
super(fullCard);
this.projects = copyList(fullCard.getProjects());
}
@NonNull
public List<OcsProjectWithResources> getProjects() {
return projects;
}
public void setProjects(@NonNull List<OcsProjectWithResources> projects) {
this.projects = projects;
}
@NonNull
@Override
public String toString() {
return "FullCard{" +
"card=" + card +
", labels=" + labels +
", assignedUsers=" + assignedUsers +
", owner=" + owner +
", attachments=" + attachments +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
FullCardWithProjects fullCard = (FullCardWithProjects) o;
if (!Objects.equals(card, fullCard.card)) return false;
if (!Objects.equals(labels, fullCard.labels))
return false;
if (!Objects.equals(assignedUsers, fullCard.assignedUsers))
return false;
if (!Objects.equals(owner, fullCard.owner)) return false;
if (!Objects.equals(attachments, fullCard.attachments))
return false;
return Objects.equals(commentIDs, fullCard.commentIDs);
}
@Override
public int hashCode() {
int result = (isAttachmentsSorted ? 1 : 0);
result = 31 * result + (card != null ? card.hashCode() : 0);
result = 31 * result + (labels != null ? labels.hashCode() : 0);
result = 31 * result + (assignedUsers != null ? assignedUsers.hashCode() : 0);
result = 31 * result + (owner != null ? owner.hashCode() : 0);
result = 31 * result + (attachments != null ? attachments.hashCode() : 0);
result = 31 * result + (commentIDs != null ? commentIDs.hashCode() : 0);
return result;
}
}

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