diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml
new file mode 100644
index 00000000..3ece0787
--- /dev/null
+++ b/.github/FUNDING.yml
@@ -0,0 +1,2 @@
+github: etesync
+custom: https://www.etesync.com/contribute/#donate
diff --git a/ChangeLog.md b/ChangeLog.md
index f72302a4..47b84309 100644
--- a/ChangeLog.md
+++ b/ChangeLog.md
@@ -1,6 +1,50 @@
# Changelog
*NOTE:* may be removed in the future in favor of the fastlane changelog.
+## Version 2.2.3
+* Fix issues with the Tasks.org integration and subtasks (due to rewriting UIDs).
+
+## Version 2.2.2
+* Fix "potential vendor bugs" message constantly showing.
+
+## Version 2.2.1
+* Fix crash when importing events and also when syncing legacy events
+
+## Version 2.2.0
+* Support resizable activities
+* Update ical4android dep - should fix issues with duplicate tasks and events
+* Update vcard4android dep
+* Update gradle and sdk version
+* Update translations
+
+## Version 2.1.5
+* Improve error handling in sync and import
+* Update translations
+* Fix some crashes
+
+## Version 2.1.4
+* Event invitations: only send invitations if we are the organizers
+* Fix rare crash when pushing changes with EteSync 1.0 accounts
+
+## Version 2.1.3
+* Fix crashes on older Android devices
+* Fix crashes with some screen not loading for some users.
+
+## Version 2.1.2
+* Fix crash when generating email invitations while using a French locale
+* Uptdate etebase dep to fix issue with custom urls not ending with a slash.
+
+## Version 2.1.1
+* Debug info: fix manually sending of crash reports to have visual feedback.
+* Debug info: fix manually sending of crash reports to include more crash information.
+* Fixed a few crashes that were happening in some rare cases.
+
+## Version 2.1.0
+* Change the crash reporting to not rely on email (use HTTP instead)
+
+## Version 2.0.0
+* EteSync 2.0 support \o/
+
## Version 1.16.2
* Update OkHttp3 dependency.
diff --git a/app/build.gradle b/app/build.gradle
index d45cb316..28566b96 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -21,8 +21,8 @@ android {
minSdkVersion 21
targetSdkVersion 29
- versionCode 200
- versionName "2.0.0"
+ versionCode 20203
+ versionName "2.2.3"
buildConfigField "boolean", "customCerts", "true"
}
@@ -122,6 +122,9 @@ android {
buildTypes.release.signingConfig = null
}
compileOptions {
+ // enable because ical4android requires desugaring
+ coreLibraryDesugaringEnabled true
+
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
@@ -130,20 +133,24 @@ android {
jvmTarget = "1.8"
}
- dataBinding.enabled = true
+ buildFeatures {
+ dataBinding = true
+ }
}
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
+ coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.0.9'
+
implementation "org.jetbrains.anko:anko-commons:0.10.4"
implementation "com.etesync:journalmanager:1.1.1"
- def etebaseVersion = '0.2.2'
+ def etebaseVersion = '2.3.2'
implementation "com.etebase:client:$etebaseVersion"
- def acraVersion = '5.3.0'
- implementation "ch.acra:acra-mail:$acraVersion"
- implementation "ch.acra:acra-notification:$acraVersion"
+ def acraVersion = '5.7.0'
+ implementation "ch.acra:acra-http:$acraVersion"
+ implementation "ch.acra:acra-dialog:$acraVersion"
def supportVersion = '1.0.0'
implementation "androidx.legacy:legacy-support-core-ui:$supportVersion"
implementation "androidx.core:core:$supportVersion"
diff --git a/app/proguard-rules.txt b/app/proguard-rules.txt
index b981122e..345ebb71 100644
--- a/app/proguard-rules.txt
+++ b/app/proguard-rules.txt
@@ -10,7 +10,6 @@
-optimizations !code/simplification/arithmetic,!code/simplification/cast,!field/*,!class/merging/*
-optimizationpasses 5
-allowaccessmodification
--dontpreverify
# Kotlin
-dontwarn kotlin.**
@@ -25,14 +24,7 @@
-keep class ezvcard.property.** { *; } # keep all vCard properties (created at runtime)
# ical4j: ignore unused dynamic libraries
--dontwarn aQute.**
--dontwarn groovy.** # Groovy-based ContentBuilder not used
--dontwarn javax.cache.** # no JCache support in Android
--dontwarn net.fortuna.ical4j.model.**
--dontwarn org.codehaus.groovy.**
--dontwarn org.apache.log4j.** # ignore warnings from log4j dependency
-keep class net.fortuna.ical4j.** { *; } # keep all model classes (properties/factories, created at runtime)
--keep class org.threeten.bp.** { *; } # keep ThreeTen (for time zone processing)
# okhttp
# JSR 305 annotations are for embedding nullability information.
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 0c4fe99b..0b5f8112 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -52,6 +52,7 @@
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme"
+ android:resizeableActivity="true"
tools:ignore="UnusedAttribute">
+
{
@@ -119,6 +124,6 @@ class EtebaseLocalCache private constructor(context: Context, username: String)
}
}
-data class CachedCollection(val col: Collection, val meta: CollectionMetadata)
+data class CachedCollection(val col: Collection, val meta: ItemMetadata, val collectionType: String)
data class CachedItem(val item: Item, val meta: ItemMetadata, val content: String)
\ No newline at end of file
diff --git a/app/src/main/java/com/etesync/syncadapter/resource/LocalAddressBook.kt b/app/src/main/java/com/etesync/syncadapter/resource/LocalAddressBook.kt
index b4788739..da43839f 100644
--- a/app/src/main/java/com/etesync/syncadapter/resource/LocalAddressBook.kt
+++ b/app/src/main/java/com/etesync/syncadapter/resource/LocalAddressBook.kt
@@ -424,7 +424,7 @@ class LocalAddressBook(
val values = ContentValues(1)
values.put(Groups.TITLE, title)
val uri = provider.insert(syncAdapterURI(Groups.CONTENT_URI), values)
- return ContentUris.parseId(uri)
+ return ContentUris.parseId(uri!!)
}
fun removeEmptyGroups() {
diff --git a/app/src/main/java/com/etesync/syncadapter/resource/LocalCalendar.kt b/app/src/main/java/com/etesync/syncadapter/resource/LocalCalendar.kt
index 8017e033..b9963659 100644
--- a/app/src/main/java/com/etesync/syncadapter/resource/LocalCalendar.kt
+++ b/app/src/main/java/com/etesync/syncadapter/resource/LocalCalendar.kt
@@ -10,7 +10,6 @@ package com.etesync.syncadapter.resource
import android.accounts.Account
import android.content.ContentProviderClient
-import android.content.ContentProviderOperation
import android.content.ContentUris
import android.content.ContentValues
import android.net.Uri
@@ -24,7 +23,6 @@ import com.etesync.syncadapter.log.Logger
import com.etesync.syncadapter.model.JournalEntity
import com.etesync.syncadapter.resource.LocalEvent.Companion.COLUMN_UID
import org.apache.commons.lang3.StringUtils
-import org.dmfs.tasks.contract.TaskContract
import java.util.*
import java.util.logging.Level
@@ -210,15 +208,15 @@ class LocalCalendar private constructor(
cursor2!!.close()
val batch = BatchOperation(provider)
// re-schedule original event and set it to DIRTY
- batch.enqueue(BatchOperation.Operation(
- ContentProviderOperation.newUpdate(syncAdapterURI(ContentUris.withAppendedId(Events.CONTENT_URI, originalID)))
+ batch.enqueue(
+ BatchOperation.CpoBuilder.newUpdate(syncAdapterURI(ContentUris.withAppendedId(Events.CONTENT_URI, originalID)))
.withValue(LocalEvent.COLUMN_SEQUENCE, originalSequence + 1)
.withValue(Events.DIRTY, 1)
- ))
+ )
// remove exception
- batch.enqueue(BatchOperation.Operation(
- ContentProviderOperation.newDelete(syncAdapterURI(ContentUris.withAppendedId(Events.CONTENT_URI, id)))
- ))
+ batch.enqueue(
+ BatchOperation.CpoBuilder.newDelete(syncAdapterURI(ContentUris.withAppendedId(Events.CONTENT_URI, id)))
+ )
batch.commit()
}
cursor!!.close()
@@ -242,16 +240,14 @@ class LocalCalendar private constructor(
val batch = BatchOperation(provider)
// original event to DIRTY
- batch.enqueue(BatchOperation.Operation(
- ContentProviderOperation.newUpdate(syncAdapterURI(ContentUris.withAppendedId(Events.CONTENT_URI, originalID)))
+ batch.enqueue(BatchOperation.CpoBuilder.newUpdate(syncAdapterURI(ContentUris.withAppendedId(Events.CONTENT_URI, originalID)))
.withValue(Events.DIRTY, 1)
- ))
+ )
// increase SEQUENCE and set DIRTY to 0
- batch.enqueue(BatchOperation.Operation(
- ContentProviderOperation.newUpdate(syncAdapterURI(ContentUris.withAppendedId(Events.CONTENT_URI, id)))
+ batch.enqueue(BatchOperation.CpoBuilder.newUpdate(syncAdapterURI(ContentUris.withAppendedId(Events.CONTENT_URI, id)))
.withValue(LocalEvent.COLUMN_SEQUENCE, sequence + 1)
.withValue(Events.DIRTY, 0)
- ))
+ )
batch.commit()
}
cursor!!.close()
diff --git a/app/src/main/java/com/etesync/syncadapter/resource/LocalContact.kt b/app/src/main/java/com/etesync/syncadapter/resource/LocalContact.kt
index b6c9d6a3..88d4dc1d 100644
--- a/app/src/main/java/com/etesync/syncadapter/resource/LocalContact.kt
+++ b/app/src/main/java/com/etesync/syncadapter/resource/LocalContact.kt
@@ -141,17 +141,15 @@ class LocalContact : AndroidContact, LocalAddress {
super.insertDataRows(batch)
if (contact?.unknownProperties != null) {
- val op: BatchOperation.Operation
- val builder = ContentProviderOperation.newInsert(dataSyncURI())
+ var builder = BatchOperation.CpoBuilder.newInsert(dataSyncURI())
if (id == null) {
- op = BatchOperation.Operation(builder, UnknownProperties.RAW_CONTACT_ID, 0)
+ builder = builder.withValue(UnknownProperties.RAW_CONTACT_ID, 0)
} else {
- op = BatchOperation.Operation(builder)
- builder.withValue(UnknownProperties.RAW_CONTACT_ID, id)
+ builder = builder.withValue(UnknownProperties.RAW_CONTACT_ID, id)
}
builder.withValue(UnknownProperties.MIMETYPE, UnknownProperties.CONTENT_ITEM_TYPE)
.withValue(UnknownProperties.UNKNOWN_PROPERTIES, contact?.unknownProperties)
- batch.enqueue(op)
+ batch.enqueue(builder)
}
}
@@ -166,7 +164,7 @@ class LocalContact : AndroidContact, LocalAddress {
return this.add()
}
- override fun buildContact(builder: ContentProviderOperation.Builder, update: Boolean) {
+ override fun buildContact(builder: BatchOperation.CpoBuilder, update: Boolean) {
super.buildContact(builder, update)
builder.withValue(ContactsContract.RawContacts.DIRTY, if (saveAsDirty) 1 else 0)
}
@@ -202,10 +200,10 @@ class LocalContact : AndroidContact, LocalAddress {
if (batch == null)
addressBook.provider!!.update(rawContactSyncURI(), values, null, null)
else {
- val builder = ContentProviderOperation
+ val builder = BatchOperation.CpoBuilder
.newUpdate(rawContactSyncURI())
- .withValues(values)
- batch.enqueue(BatchOperation.Operation(builder))
+ .withValue(COLUMN_HASHCODE, hashCode)
+ batch.enqueue(builder)
}
}
@@ -223,33 +221,28 @@ class LocalContact : AndroidContact, LocalAddress {
fun addToGroup(batch: BatchOperation, groupID: Long) {
- batch.enqueue(BatchOperation.Operation(
- ContentProviderOperation.newInsert(dataSyncURI())
+ batch.enqueue(BatchOperation.CpoBuilder.newInsert(dataSyncURI())
.withValue(GroupMembership.MIMETYPE, GroupMembership.CONTENT_ITEM_TYPE)
.withValue(GroupMembership.RAW_CONTACT_ID, id)
.withValue(GroupMembership.GROUP_ROW_ID, groupID)
- ))
+ )
groupMemberships.add(groupID)
- batch.enqueue(BatchOperation.Operation(
- ContentProviderOperation.newInsert(dataSyncURI())
+ batch.enqueue(BatchOperation.CpoBuilder.newInsert(dataSyncURI())
.withValue(CachedGroupMembership.MIMETYPE, CachedGroupMembership.CONTENT_ITEM_TYPE)
.withValue(CachedGroupMembership.RAW_CONTACT_ID, id)
.withValue(CachedGroupMembership.GROUP_ID, groupID)
- .withYieldAllowed(true)
- ))
+ )
cachedGroupMemberships.add(groupID)
}
fun removeGroupMemberships(batch: BatchOperation) {
- batch.enqueue(BatchOperation.Operation(
- ContentProviderOperation.newDelete(dataSyncURI())
+ batch.enqueue(BatchOperation.CpoBuilder.newDelete(dataSyncURI())
.withSelection(
Data.RAW_CONTACT_ID + "=? AND " + Data.MIMETYPE + " IN (?,?)",
arrayOf(id.toString(), GroupMembership.CONTENT_ITEM_TYPE, CachedGroupMembership.CONTENT_ITEM_TYPE)
)
- .withYieldAllowed(true)
- ))
+ )
groupMemberships.clear()
cachedGroupMemberships.clear()
}
diff --git a/app/src/main/java/com/etesync/syncadapter/resource/LocalEvent.kt b/app/src/main/java/com/etesync/syncadapter/resource/LocalEvent.kt
index e5206ee5..e0e90f1e 100644
--- a/app/src/main/java/com/etesync/syncadapter/resource/LocalEvent.kt
+++ b/app/src/main/java/com/etesync/syncadapter/resource/LocalEvent.kt
@@ -83,7 +83,7 @@ class LocalEvent : AndroidEvent, LocalResource {
weAreOrganizer = isOrganizer != null && isOrganizer != 0
}
- override fun buildEvent(recurrence: Event?, builder: ContentProviderOperation.Builder) {
+ override fun buildEvent(recurrence: Event?, builder: BatchOperation.CpoBuilder) {
super.buildEvent(recurrence, builder)
val buildException = recurrence != null
@@ -101,7 +101,7 @@ class LocalEvent : AndroidEvent, LocalResource {
.withValue(COLUMN_ETAG, eTag)
}
- override fun insertReminder(batch: BatchOperation, idxEvent: Int, alarm: VAlarm) {
+ override fun insertReminder(batch: BatchOperation, idxEvent: Int?, alarm: VAlarm) {
// We only support DISPLAY and AUDIO alarms so modify when inserting
val action = alarm.action
val modifiedAlarm = when (action?.value) {
@@ -133,7 +133,7 @@ class LocalEvent : AndroidEvent, LocalResource {
override fun legacyPrepareForUpload(fileName_: String?) {
var uid: String? = null
- val c = calendar.provider.query(eventSyncURI(), arrayOf(COLUMN_UID), null, null, null)
+ val c = calendar.provider.query(eventSyncURI(), arrayOf(COLUMN_UID), null, null, null)!!
if (c.moveToNext())
uid = c.getString(0)
if (uid == null)
diff --git a/app/src/main/java/com/etesync/syncadapter/resource/LocalGroup.kt b/app/src/main/java/com/etesync/syncadapter/resource/LocalGroup.kt
index 6af2801a..eb25a461 100644
--- a/app/src/main/java/com/etesync/syncadapter/resource/LocalGroup.kt
+++ b/app/src/main/java/com/etesync/syncadapter/resource/LocalGroup.kt
@@ -135,23 +135,20 @@ class LocalGroup : AndroidGroup, LocalAddress {
val batch = BatchOperation(addressBook.provider!!)
// delete cached group memberships
- batch.enqueue(BatchOperation.Operation(
- ContentProviderOperation.newDelete(addressBook.syncAdapterURI(ContactsContract.Data.CONTENT_URI))
+ batch.enqueue(BatchOperation.CpoBuilder.newDelete(addressBook.syncAdapterURI(ContactsContract.Data.CONTENT_URI))
.withSelection(
CachedGroupMembership.MIMETYPE + "=? AND " + CachedGroupMembership.GROUP_ID + "=?",
arrayOf(CachedGroupMembership.CONTENT_ITEM_TYPE, id.toString())
)
- ))
+ )
// insert updated cached group memberships
for (member in getMembers())
- batch.enqueue(BatchOperation.Operation(
- ContentProviderOperation.newInsert(addressBook.syncAdapterURI(ContactsContract.Data.CONTENT_URI))
+ batch.enqueue(BatchOperation.CpoBuilder.newInsert(addressBook.syncAdapterURI(ContactsContract.Data.CONTENT_URI))
.withValue(CachedGroupMembership.MIMETYPE, CachedGroupMembership.CONTENT_ITEM_TYPE)
.withValue(CachedGroupMembership.RAW_CONTACT_ID, member)
.withValue(CachedGroupMembership.GROUP_ID, id)
- .withYieldAllowed(true)
- ))
+ )
batch.commit()
}
@@ -214,11 +211,9 @@ class LocalGroup : AndroidGroup, LocalAddress {
.forEach { it.updateHashCode(batch) }
// remove pending memberships
- batch.enqueue(BatchOperation.Operation(
- ContentProviderOperation.newUpdate(addressBook.syncAdapterURI(ContentUris.withAppendedId(Groups.CONTENT_URI, id)))
+ batch.enqueue(BatchOperation.CpoBuilder.newUpdate(addressBook.syncAdapterURI(ContentUris.withAppendedId(Groups.CONTENT_URI, id)))
.withValue(COLUMN_PENDING_MEMBERS, null)
- .withYieldAllowed(true)
- ))
+ )
}
diff --git a/app/src/main/java/com/etesync/syncadapter/resource/LocalTask.kt b/app/src/main/java/com/etesync/syncadapter/resource/LocalTask.kt
index d39d8d4b..15ed97fb 100644
--- a/app/src/main/java/com/etesync/syncadapter/resource/LocalTask.kt
+++ b/app/src/main/java/com/etesync/syncadapter/resource/LocalTask.kt
@@ -12,10 +12,7 @@ import android.content.ContentProviderOperation
import android.content.ContentValues
import android.net.Uri
import android.text.TextUtils
-import at.bitfire.ical4android.AndroidTask
-import at.bitfire.ical4android.AndroidTaskFactory
-import at.bitfire.ical4android.AndroidTaskList
-import at.bitfire.ical4android.Task
+import at.bitfire.ical4android.*
import com.etesync.syncadapter.log.Logger
import org.dmfs.tasks.contract.TaskContract
import java.io.ByteArrayOutputStream
@@ -75,7 +72,7 @@ class LocalTask : AndroidTask, LocalResource {
task?.sequence = values.getAsInteger(COLUMN_SEQUENCE)
}
- override fun buildTask(builder: ContentProviderOperation.Builder, update: Boolean) {
+ override fun buildTask(builder: BatchOperation.CpoBuilder, update: Boolean) {
super.buildTask(builder, update)
builder.withValue(TaskContract.Tasks._SYNC_ID, fileName)
.withValue(COLUMN_UID, task?.uid)
@@ -98,7 +95,7 @@ class LocalTask : AndroidTask, LocalResource {
override fun legacyPrepareForUpload(fileName_: String?) {
var uid: String? = null
- val c = taskList.provider.client.query(taskSyncURI(), arrayOf(COLUMN_UID), null, null, null)
+ val c = taskList.provider.client.query(taskSyncURI(), arrayOf(COLUMN_UID), null, null, null)!!
if (c.moveToNext())
uid = c.getString(0)
if (uid == null)
diff --git a/app/src/main/java/com/etesync/syncadapter/syncadapter/AddressBooksSyncAdapterService.kt b/app/src/main/java/com/etesync/syncadapter/syncadapter/AddressBooksSyncAdapterService.kt
index 67e7a300..b13c085f 100644
--- a/app/src/main/java/com/etesync/syncadapter/syncadapter/AddressBooksSyncAdapterService.kt
+++ b/app/src/main/java/com/etesync/syncadapter/syncadapter/AddressBooksSyncAdapterService.kt
@@ -77,7 +77,7 @@ class AddressBooksSyncAdapterService : SyncAdapterService() {
val etebase = EtebaseLocalCache.getEtebase(context, httpClient.okHttpClient, settings)
val colMgr = etebase.collectionManager
- collections = etebaseLocalCache.collectionList(colMgr).filter { it.meta.collectionType == Constants.ETEBASE_TYPE_ADDRESS_BOOK }
+ collections = etebaseLocalCache.collectionList(colMgr).filter { it.collectionType == Constants.ETEBASE_TYPE_ADDRESS_BOOK }
}
for (collection in collections) {
diff --git a/app/src/main/java/com/etesync/syncadapter/syncadapter/CalendarSyncManager.kt b/app/src/main/java/com/etesync/syncadapter/syncadapter/CalendarSyncManager.kt
index ad266529..6536314d 100644
--- a/app/src/main/java/com/etesync/syncadapter/syncadapter/CalendarSyncManager.kt
+++ b/app/src/main/java/com/etesync/syncadapter/syncadapter/CalendarSyncManager.kt
@@ -147,7 +147,7 @@ constructor(context: Context, account: Account, settings: AccountSettings, extra
for (local in localDirty) {
val event = local.event
- if (event?.attendees?.isEmpty()!!) {
+ if (event?.attendees?.isEmpty()!! || !event.organizer?.value?.replace("mailto:", "").equals(account.name)) {
return
}
createInviteAttendeesNotification(event, local.content)
diff --git a/app/src/main/java/com/etesync/syncadapter/syncadapter/CalendarsSyncAdapterService.kt b/app/src/main/java/com/etesync/syncadapter/syncadapter/CalendarsSyncAdapterService.kt
index 8dc159e9..29d8b4e7 100644
--- a/app/src/main/java/com/etesync/syncadapter/syncadapter/CalendarsSyncAdapterService.kt
+++ b/app/src/main/java/com/etesync/syncadapter/syncadapter/CalendarsSyncAdapterService.kt
@@ -63,7 +63,7 @@ class CalendarsSyncAdapterService : SyncAdapterService() {
val etebase = EtebaseLocalCache.getEtebase(context, httpClient.okHttpClient, settings)
val colMgr = etebase.collectionManager
- collections = etebaseLocalCache.collectionList(colMgr).filter { it.meta.collectionType == Constants.ETEBASE_TYPE_CALENDAR }
+ collections = etebaseLocalCache.collectionList(colMgr).filter { it.collectionType == Constants.ETEBASE_TYPE_CALENDAR }
}
for (collection in collections) {
diff --git a/app/src/main/java/com/etesync/syncadapter/syncadapter/ContactsSyncManager.kt b/app/src/main/java/com/etesync/syncadapter/syncadapter/ContactsSyncManager.kt
index e58f864b..cb99d777 100644
--- a/app/src/main/java/com/etesync/syncadapter/syncadapter/ContactsSyncManager.kt
+++ b/app/src/main/java/com/etesync/syncadapter/syncadapter/ContactsSyncManager.kt
@@ -102,11 +102,9 @@ constructor(context: Context, account: Account, settings: AccountSettings, extra
val currentGroups = contact.getGroupMemberships()
for (groupID in SetUtils.disjunction(cachedGroups, currentGroups)) {
Logger.log.fine("Marking group as dirty: " + groupID!!)
- batch.enqueue(BatchOperation.Operation(
- ContentProviderOperation.newUpdate(addressBook.syncAdapterURI(ContentUris.withAppendedId(ContactsContract.Groups.CONTENT_URI, groupID)))
+ batch.enqueue(BatchOperation.CpoBuilder.newUpdate(addressBook.syncAdapterURI(ContentUris.withAppendedId(ContactsContract.Groups.CONTENT_URI, groupID)))
.withValue(ContactsContract.Groups.DIRTY, 1)
- .withYieldAllowed(true)
- ))
+ )
}
} catch (ignored: FileNotFoundException) {
}
diff --git a/app/src/main/java/com/etesync/syncadapter/syncadapter/SyncAdapterService.kt b/app/src/main/java/com/etesync/syncadapter/syncadapter/SyncAdapterService.kt
index 63b2273b..62488fc5 100644
--- a/app/src/main/java/com/etesync/syncadapter/syncadapter/SyncAdapterService.kt
+++ b/app/src/main/java/com/etesync/syncadapter/syncadapter/SyncAdapterService.kt
@@ -26,10 +26,11 @@ import com.etebase.client.FetchOptions
import com.etebase.client.exceptions.ConnectionException
import com.etebase.client.exceptions.TemporaryServerErrorException
import com.etebase.client.exceptions.UnauthorizedException
-import com.etesync.syncadapter.*
import com.etesync.journalmanager.Crypto
import com.etesync.journalmanager.Exceptions
import com.etesync.journalmanager.JournalManager
+import com.etesync.syncadapter.*
+import com.etesync.syncadapter.Constants.COLLECTION_TYPES
import com.etesync.syncadapter.log.Logger
import com.etesync.syncadapter.model.CollectionInfo
import com.etesync.syncadapter.model.JournalEntity
@@ -243,7 +244,6 @@ abstract class SyncAdapterService : Service() {
return
}
-
val etebaseLocalCache = EtebaseLocalCache.getInstance(context, account.name)
synchronized(etebaseLocalCache) {
val cacheAge = 5 * 1000 // 5 seconds - it's just a hack for burst fetching
@@ -259,7 +259,7 @@ abstract class SyncAdapterService : Service() {
var stoken = etebaseLocalCache.loadStoken()
var done = false
while (!done) {
- val colList = colMgr.list(FetchOptions().stoken(stoken))
+ val colList = colMgr.list(COLLECTION_TYPES, FetchOptions().stoken(stoken))
for (col in colList.data) {
etebaseLocalCache.collectionSet(colMgr, col)
}
diff --git a/app/src/main/java/com/etesync/syncadapter/syncadapter/SyncManager.kt b/app/src/main/java/com/etesync/syncadapter/syncadapter/SyncManager.kt
index 0722be42..8ff0d4ae 100644
--- a/app/src/main/java/com/etesync/syncadapter/syncadapter/SyncManager.kt
+++ b/app/src/main/java/com/etesync/syncadapter/syncadapter/SyncManager.kt
@@ -571,7 +571,7 @@ constructor(protected val context: Context, protected val account: Account, prot
break
}
Logger.log.info("Added/changed resource with UUID: " + local.uuid)
- local.clearDirty(local.uuid!!)
+ local.clearDirty(local.uuid)
}
if (left > 0) {
localDirty = localDirty.drop(left)
@@ -644,15 +644,48 @@ constructor(protected val context: Context, protected val account: Account, prot
item.meta = meta
}
+ private fun prepareLocalItemForUpload(colUid: String, local: T): Item {
+ val cacheItem = if (local.fileName != null) etebaseLocalCache.itemGet(itemMgr, colUid, local.fileName!!) else null
+ val item: Item
+ if (cacheItem != null) {
+ item = cacheItem.item
+ itemUpdateMtime(item)
+ } else {
+ val uid = local.uuid ?: UUID.randomUUID().toString()
+ val meta = ItemMetadata()
+ meta.name = uid
+ meta.mtime = System.currentTimeMillis()
+ item = itemMgr.create(meta, "")
+
+ local.prepareForUpload(item.uid, uid)
+ }
+
+ try {
+ item.setContent(local.content)
+ } catch (e: Exception) {
+ Logger.log.warning("Failed creating local entry ${local.uuid}")
+ if (local is LocalContact) {
+ Logger.log.warning("Contact with title ${local.contact?.displayName}")
+ } else if (local is LocalEvent) {
+ Logger.log.warning("Event with title ${local.event?.summary}")
+ } else if (local is LocalTask) {
+ Logger.log.warning("Task with title ${local.task?.summary}")
+ }
+ throw e
+ }
+
+ return item
+ }
+
private fun createPushItems(): List- {
val ret = LinkedList
- ()
val colUid = cachedCollection.col.uid
synchronized(etebaseLocalCache) {
for (local in localDeleted!!) {
- val item = etebaseLocalCache.itemGet(itemMgr, colUid, local.fileName!!)!!.item
- itemUpdateMtime(item)
+ val item = prepareLocalItemForUpload(colUid, local)
item.delete()
+
ret.add(item)
if (ret.size == MAX_PUSH) {
@@ -663,34 +696,7 @@ constructor(protected val context: Context, protected val account: Account, prot
synchronized(etebaseLocalCache) {
for (local in localDirty) {
- val cacheItem = if (local.fileName != null) etebaseLocalCache.itemGet(itemMgr, colUid, local.fileName!!) else null
- val item: Item
- if (cacheItem != null) {
- item = cacheItem.item
- itemUpdateMtime(item)
- } else {
- val uid = UUID.randomUUID().toString()
- val meta = ItemMetadata()
- meta.name = uid
- meta.mtime = System.currentTimeMillis()
- item = itemMgr.create(meta, "")
-
- local.prepareForUpload(item.uid, uid)
- }
-
- try {
- item.setContent(local.content)
- } catch (e: Exception) {
- Logger.log.warning("Failed creating local entry ${local.uuid}")
- if (local is LocalContact) {
- Logger.log.warning("Contact with title ${local.contact?.displayName}")
- } else if (local is LocalEvent) {
- Logger.log.warning("Event with title ${local.event?.summary}")
- } else if (local is LocalTask) {
- Logger.log.warning("Task with title ${local.task?.summary}")
- }
- throw e
- }
+ val item = prepareLocalItemForUpload(colUid, local)
ret.add(item)
diff --git a/app/src/main/java/com/etesync/syncadapter/syncadapter/SyncNotification.kt b/app/src/main/java/com/etesync/syncadapter/syncadapter/SyncNotification.kt
index 0c478331..500ca303 100644
--- a/app/src/main/java/com/etesync/syncadapter/syncadapter/SyncNotification.kt
+++ b/app/src/main/java/com/etesync/syncadapter/syncadapter/SyncNotification.kt
@@ -129,6 +129,8 @@ class SyncNotification(internal val context: Context, internal val notificationT
val detailsIntent: Intent
if (e is Exceptions.UnauthorizedException) {
detailsIntent = Intent(this, AccountSettingsActivity::class.java)
+ } else if (e is PermissionDeniedException || e is UnauthorizedException) {
+ detailsIntent = Intent(this, AccountSettingsActivity::class.java)
} else if (e is Exceptions.UserInactiveException) {
WebViewActivity.openUrl(this, Constants.dashboard)
return
diff --git a/app/src/main/java/com/etesync/syncadapter/syncadapter/TasksSyncAdapterService.kt b/app/src/main/java/com/etesync/syncadapter/syncadapter/TasksSyncAdapterService.kt
index 22d18caf..b2c4224b 100644
--- a/app/src/main/java/com/etesync/syncadapter/syncadapter/TasksSyncAdapterService.kt
+++ b/app/src/main/java/com/etesync/syncadapter/syncadapter/TasksSyncAdapterService.kt
@@ -83,7 +83,7 @@ class TasksSyncAdapterService: SyncAdapterService() {
val etebase = EtebaseLocalCache.getEtebase(context, httpClient.okHttpClient, settings)
val colMgr = etebase.collectionManager
- collections = etebaseLocalCache.collectionList(colMgr).filter { it.meta.collectionType == Constants.ETEBASE_TYPE_TASKS }
+ collections = etebaseLocalCache.collectionList(colMgr).filter { it.collectionType == Constants.ETEBASE_TYPE_TASKS }
}
for (collection in collections) {
diff --git a/app/src/main/java/com/etesync/syncadapter/ui/AboutActivity.kt b/app/src/main/java/com/etesync/syncadapter/ui/AboutActivity.kt
index 1953c47f..9853dac8 100644
--- a/app/src/main/java/com/etesync/syncadapter/ui/AboutActivity.kt
+++ b/app/src/main/java/com/etesync/syncadapter/ui/AboutActivity.kt
@@ -100,7 +100,7 @@ class AboutActivity : BaseActivity() {
}
override fun onCreateLoader(id: Int, args: Bundle?): Loader {
- return LicenseLoader(context!!, args!!.getString(KEY_FILE_NAME))
+ return LicenseLoader(requireContext(), args!!.getString(KEY_FILE_NAME)!!)
}
override fun onLoadFinished(loader: Loader, license: Spanned) {
diff --git a/app/src/main/java/com/etesync/syncadapter/ui/AccountActivity.kt b/app/src/main/java/com/etesync/syncadapter/ui/AccountActivity.kt
index dbea30be..5b275254 100644
--- a/app/src/main/java/com/etesync/syncadapter/ui/AccountActivity.kt
+++ b/app/src/main/java/com/etesync/syncadapter/ui/AccountActivity.kt
@@ -10,7 +10,6 @@ package com.etesync.syncadapter.ui
import android.accounts.Account
import android.accounts.AccountManager
-import android.app.LoaderManager
import android.content.*
import android.content.ContentResolver.SYNC_OBSERVER_TYPE_ACTIVE
import android.net.Uri
@@ -22,9 +21,14 @@ import android.provider.ContactsContract
import android.text.TextUtils
import android.view.*
import android.widget.*
+import androidx.activity.viewModels
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.widget.Toolbar
import androidx.core.content.ContextCompat
+import androidx.lifecycle.LifecycleOwner
+import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.observe
import at.bitfire.ical4android.TaskProvider.Companion.TASK_PROVIDERS
import at.bitfire.vcard4android.ContactsStorageException
import com.etebase.client.CollectionAccessLevel
@@ -52,11 +56,14 @@ import com.etesync.syncadapter.utils.ShowcaseBuilder
import com.etesync.syncadapter.utils.packageInstalled
import com.google.android.material.snackbar.Snackbar
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
+import org.acra.ACRA
import org.jetbrains.anko.doAsync
+import org.jetbrains.anko.uiThread
import tourguide.tourguide.ToolTip
import java.util.logging.Level
-class AccountActivity : BaseActivity(), Toolbar.OnMenuItemClickListener, PopupMenu.OnMenuItemClickListener, LoaderManager.LoaderCallbacks, Refreshable {
+class AccountActivity : BaseActivity(), Toolbar.OnMenuItemClickListener, PopupMenu.OnMenuItemClickListener, Refreshable {
+ private val model: AccountInfoViewModel by viewModels()
private lateinit var account: Account
private lateinit var settings: AccountSettings
@@ -101,10 +108,13 @@ class AccountActivity : BaseActivity(), Toolbar.OnMenuItemClickListener, PopupMe
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
- account = intent.getParcelableExtra(EXTRA_ACCOUNT)
+ account = intent.getParcelableExtra(EXTRA_ACCOUNT)!!
title = account.name
settings = AccountSettings(this, account)
+ // Set it for ACRA in case we crash in any of the user views
+ ACRA.getErrorReporter().putCustomData("username", account.name)
+
setContentView(R.layout.activity_account)
val icMenu = ContextCompat.getDrawable(this, R.drawable.ic_menu_light)
@@ -139,7 +149,13 @@ class AccountActivity : BaseActivity(), Toolbar.OnMenuItemClickListener, PopupMe
}
// load CardDAV/CalDAV journals
- loaderManager.initLoader(0, intent.extras, this)
+ if (savedInstanceState == null) {
+ model.initialize(this, account)
+ model.loadAccount()
+ model.observe(this) {
+ updateUi(it)
+ }
+ }
if (!HintManager.getHintSeen(this, HINT_VIEW_COLLECTION)) {
ShowcaseBuilder.getBuilder(this)
@@ -160,6 +176,7 @@ class AccountActivity : BaseActivity(), Toolbar.OnMenuItemClickListener, PopupMe
if (settings.isLegacy) {
val invitations = menu.findItem(R.id.invitations)
invitations.setVisible(false)
+ menu.findItem(R.id.migration_v2).setVisible(true)
}
return true
}
@@ -194,6 +211,10 @@ class AccountActivity : BaseActivity(), Toolbar.OnMenuItemClickListener, PopupMe
val intent = InvitationsActivity.newIntent(this, account)
startActivity(intent)
}
+ R.id.migration_v2 -> {
+ val intent = MigrateV2Activity.newIntent(this, account)
+ startActivity(intent)
+ }
else -> return super.onOptionsItemSelected(item)
}
return true
@@ -272,15 +293,11 @@ class AccountActivity : BaseActivity(), Toolbar.OnMenuItemClickListener, PopupMe
}
}
- override fun onCreateLoader(id: Int, args: Bundle?): Loader {
- return AccountLoader(this, account)
- }
-
override fun refresh() {
- loaderManager.restartLoader(0, intent.extras, this)
+ model.loadAccount()
}
- override fun onLoadFinished(loader: Loader, info: AccountInfo) {
+ fun updateUi(info: AccountInfo) {
accountInfo = info
if (info.carddav != null) {
@@ -331,41 +348,47 @@ class AccountActivity : BaseActivity(), Toolbar.OnMenuItemClickListener, PopupMe
}
}
- override fun onLoaderReset(loader: Loader) {
- if (listCardDAV != null)
- listCardDAV!!.adapter = null
-
- if (listCalDAV != null)
- listCalDAV!!.adapter = null
-
- if (listTaskDAV != null)
- listTaskDAV!!.adapter = null
- }
-
- private class AccountLoader(context: Context, private val account: Account) : AsyncTaskLoader(context), AccountUpdateService.RefreshingStatusListener, ServiceConnection, SyncStatusObserver {
+ class AccountInfoViewModel : ViewModel(), AccountUpdateService.RefreshingStatusListener, ServiceConnection, SyncStatusObserver {
+ private val holder = MutableLiveData()
+ private lateinit var context: Context
+ private lateinit var account: Account
private var davService: AccountUpdateService.InfoBinder? = null
private var syncStatusListener: Any? = null
- override fun onStartLoading() {
+ fun initialize(context: Context, account: Account) {
+ this.context = context
+ this.account = account
+
syncStatusListener = ContentResolver.addStatusChangeListener(SYNC_OBSERVER_TYPE_ACTIVE, this)
context.bindService(Intent(context, AccountUpdateService::class.java), this, Context.BIND_AUTO_CREATE)
}
- override fun onStopLoading() {
+ fun loadAccount() {
+ doAsync {
+ val info = doLoad()
+ uiThread {
+ holder.value = info
+ }
+ }
+ }
+
+ override fun onCleared() {
davService?.removeRefreshingStatusListener(this)
context.unbindService(this)
- if (syncStatusListener != null)
+ if (syncStatusListener != null) {
ContentResolver.removeStatusChangeListener(syncStatusListener)
+ syncStatusListener = null
+ }
}
override fun onServiceConnected(name: ComponentName, service: IBinder) {
davService = service as AccountUpdateService.InfoBinder
davService!!.addRefreshingStatusListener(this, false)
- forceLoad()
+ loadAccount()
}
override fun onServiceDisconnected(name: ComponentName) {
@@ -373,18 +396,19 @@ class AccountActivity : BaseActivity(), Toolbar.OnMenuItemClickListener, PopupMe
}
override fun onDavRefreshStatusChanged(id: Long, refreshing: Boolean) {
- forceLoad()
+ loadAccount()
}
override fun onStatusChanged(which: Int) {
- forceLoad()
+ loadAccount()
}
private fun getLegacyJournals(data: MyEntityDataStore, serviceEntity: ServiceEntity): List {
return JournalEntity.getJournals(data, serviceEntity).map {
val info = it.info
val isAdmin = it.isOwner(account.name)
- CollectionListItemInfo(it.uid, info.enumType!!, info.displayName!!, info.description ?: "", info.color, it.isReadOnly, isAdmin, info)
+ CollectionListItemInfo(it.uid, info.enumType!!, info.displayName!!, info.description
+ ?: "", info.color, it.isReadOnly, isAdmin, info)
}
}
@@ -398,8 +422,9 @@ class AccountActivity : BaseActivity(), Toolbar.OnMenuItemClickListener, PopupMe
synchronized(etebaseLocalCache) {
return etebaseLocalCache.collectionList(colMgr).map {
val meta = it.meta
+ val collectionType = it.collectionType
- if (strType != meta.collectionType) {
+ if (strType != collectionType) {
return@map null
}
@@ -409,14 +434,14 @@ class AccountActivity : BaseActivity(), Toolbar.OnMenuItemClickListener, PopupMe
val metaColor = meta.color
val color = if (!metaColor.isNullOrBlank()) LocalCalendar.parseColor(metaColor) else null
- CollectionListItemInfo(it.col.uid, type, meta.name, meta.description
+ CollectionListItemInfo(it.col.uid, type, meta.name!!, meta.description
?: "", color, isReadOnly, isAdmin, null)
}.filterNotNull()
}
}
- override fun loadInBackground(): AccountInfo {
- val info = AccountInfo()
+ private fun doLoad(): AccountActivity.AccountInfo {
+ val info = AccountActivity.AccountInfo()
val settings: AccountSettings
try {
settings = AccountSettings(context, account)
@@ -432,7 +457,7 @@ class AccountActivity : BaseActivity(), Toolbar.OnMenuItemClickListener, PopupMe
when (service) {
CollectionInfo.Type.ADDRESS_BOOK -> {
info.carddav = AccountInfo.ServiceInfo()
- info.carddav!!.refreshing = davService != null && davService!!.isRefreshing(id) || ContentResolver.isSyncActive(account, App.addressBooksAuthority)
+ info.carddav!!.refreshing = ContentResolver.isSyncActive(account, App.addressBooksAuthority)
info.carddav!!.infos = getLegacyJournals(data, serviceEntity)
val accountManager = AccountManager.get(context)
@@ -448,16 +473,14 @@ class AccountActivity : BaseActivity(), Toolbar.OnMenuItemClickListener, PopupMe
}
CollectionInfo.Type.CALENDAR -> {
info.caldav = AccountInfo.ServiceInfo()
- info.caldav!!.refreshing = davService != null && davService!!.isRefreshing(id) ||
- ContentResolver.isSyncActive(account, CalendarContract.AUTHORITY)
+ info.caldav!!.refreshing = ContentResolver.isSyncActive(account, CalendarContract.AUTHORITY)
info.caldav!!.infos = getLegacyJournals(data, serviceEntity)
}
CollectionInfo.Type.TASKS -> {
info.taskdav = AccountInfo.ServiceInfo()
- info.taskdav!!.refreshing = davService != null && davService!!.isRefreshing(id) ||
- TASK_PROVIDERS.any {
- ContentResolver.isSyncActive(account, it.authority)
- }
+ info.taskdav!!.refreshing = TASK_PROVIDERS.any {
+ ContentResolver.isSyncActive(account, it.authority)
+ }
info.taskdav!!.infos = getLegacyJournals(data, serviceEntity)
}
}
@@ -497,6 +520,12 @@ class AccountActivity : BaseActivity(), Toolbar.OnMenuItemClickListener, PopupMe
return info
}
+
+ fun observe(owner: LifecycleOwner, observer: (AccountActivity.AccountInfo) -> Unit) =
+ holder.observe(owner, observer)
+
+ val value: AccountActivity.AccountInfo?
+ get() = holder.value
}
@@ -606,4 +635,4 @@ class AccountActivity : BaseActivity(), Toolbar.OnMenuItemClickListener, PopupMe
private val HINT_VIEW_COLLECTION = "ViewCollection"
}
-}
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/etesync/syncadapter/ui/AccountSettingsActivity.kt b/app/src/main/java/com/etesync/syncadapter/ui/AccountSettingsActivity.kt
index 893a946b..0cad0e8d 100644
--- a/app/src/main/java/com/etesync/syncadapter/ui/AccountSettingsActivity.kt
+++ b/app/src/main/java/com/etesync/syncadapter/ui/AccountSettingsActivity.kt
@@ -17,20 +17,24 @@ import android.os.Bundle
import android.provider.CalendarContract
import android.text.TextUtils
import android.view.MenuItem
-import android.widget.Toast
+import androidx.appcompat.app.AlertDialog
import androidx.core.app.NavUtils
+import androidx.core.net.toUri
import androidx.fragment.app.Fragment
import androidx.loader.app.LoaderManager
import androidx.loader.content.AsyncTaskLoader
import androidx.loader.content.Loader
import androidx.preference.*
import at.bitfire.ical4android.TaskProvider.Companion.TASK_PROVIDERS
+import com.etebase.client.exceptions.EtebaseException
import com.etesync.syncadapter.*
import com.etesync.syncadapter.Constants.KEY_ACCOUNT
import com.etesync.syncadapter.R
import com.etesync.syncadapter.log.Logger
import com.etesync.syncadapter.ui.setup.LoginCredentials
import com.etesync.syncadapter.ui.setup.LoginCredentialsChangeFragment
+import org.jetbrains.anko.doAsync
+import org.jetbrains.anko.uiThread
class AccountSettingsActivity : BaseActivity() {
private lateinit var account: Account
@@ -38,7 +42,7 @@ class AccountSettingsActivity : BaseActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
- account = intent.getParcelableExtra(KEY_ACCOUNT)
+ account = intent.getParcelableExtra(KEY_ACCOUNT)!!
title = getString(R.string.settings_title, account.name)
supportActionBar!!.setDisplayHomeAsUpEnabled(true)
@@ -81,18 +85,40 @@ class AccountSettingsFragment() : PreferenceFragmentCompat(), LoaderManager.Load
}
override fun onCreateLoader(id: Int, args: Bundle?): Loader {
- return AccountSettingsLoader(context!!, args!!.getParcelable(KEY_ACCOUNT) as Account)
+ return AccountSettingsLoader(requireContext(), (args!!.getParcelable(KEY_ACCOUNT) as Account?)!!)
}
override fun onLoadFinished(loader: Loader, settings: AccountSettings?) {
if (settings == null) {
- activity!!.finish()
+ activity?.finish()
return
}
// Category: dashboard
val prefManageAccount = findPreference("manage_account")
prefManageAccount.onPreferenceClickListener = Preference.OnPreferenceClickListener { _ ->
- Toast.makeText(requireContext(), "Not yet supported", Toast.LENGTH_LONG).show()
+ doAsync {
+ try {
+
+ val httpClient = HttpClient.Builder(requireContext()).build()
+ val etebase = EtebaseLocalCache.getEtebase(requireContext(), httpClient.okHttpClient, settings)
+ val url = etebase.fetchDashboardUrl()
+ uiThread {
+ WebViewActivity.openUrl(requireActivity(), url.toUri())
+ }
+ } catch (e: EtebaseException) {
+ uiThread {
+ val context = context
+ if (context != null) {
+ AlertDialog.Builder(context)
+ .setIcon(R.drawable.ic_error_dark)
+ .setTitle(R.string.exception)
+ .setMessage(e.localizedMessage)
+ .setPositiveButton(android.R.string.yes) { _, _ -> }
+ .show()
+ }
+ }
+ }
+ }
true
}
@@ -171,7 +197,7 @@ class LegacyAccountSettingsFragment : PreferenceFragmentCompat(), LoaderManager.
}
override fun onCreateLoader(id: Int, args: Bundle?): Loader {
- return AccountSettingsLoader(context!!, args!!.getParcelable(KEY_ACCOUNT) as Account)
+ return AccountSettingsLoader(context!!, (args!!.getParcelable(KEY_ACCOUNT) as? Account)!!)
}
override fun onLoadFinished(loader: Loader, settings: AccountSettings?) {
diff --git a/app/src/main/java/com/etesync/syncadapter/ui/CollectionMembersActivity.kt b/app/src/main/java/com/etesync/syncadapter/ui/CollectionMembersActivity.kt
index 5371cd1e..6b17693f 100644
--- a/app/src/main/java/com/etesync/syncadapter/ui/CollectionMembersActivity.kt
+++ b/app/src/main/java/com/etesync/syncadapter/ui/CollectionMembersActivity.kt
@@ -68,7 +68,7 @@ class CollectionMembersActivity : BaseActivity(), Refreshable {
supportActionBar!!.setDisplayHomeAsUpEnabled(true)
- account = intent.extras!!.getParcelable(EXTRA_ACCOUNT)
+ account = intent.extras!!.getParcelable(EXTRA_ACCOUNT)!!
info = intent.extras!!.getSerializable(EXTRA_COLLECTION_INFO) as CollectionInfo
refresh()
diff --git a/app/src/main/java/com/etesync/syncadapter/ui/CreateCollectionFragment.kt b/app/src/main/java/com/etesync/syncadapter/ui/CreateCollectionFragment.kt
index d61e4ba9..a1f1768c 100644
--- a/app/src/main/java/com/etesync/syncadapter/ui/CreateCollectionFragment.kt
+++ b/app/src/main/java/com/etesync/syncadapter/ui/CreateCollectionFragment.kt
@@ -37,7 +37,7 @@ class CreateCollectionFragment : DialogFragment(), LoaderManager.LoaderCallbacks
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
- account = arguments!!.getParcelable(ARG_ACCOUNT)
+ account = arguments!!.getParcelable(ARG_ACCOUNT)!!
info = arguments!!.getSerializable(ARG_COLLECTION_INFO) as CollectionInfo
loaderManager.initLoader(0, null, this)
diff --git a/app/src/main/java/com/etesync/syncadapter/ui/DebugInfoActivity.kt b/app/src/main/java/com/etesync/syncadapter/ui/DebugInfoActivity.kt
index 4705a0c7..33fd9c72 100644
--- a/app/src/main/java/com/etesync/syncadapter/ui/DebugInfoActivity.kt
+++ b/app/src/main/java/com/etesync/syncadapter/ui/DebugInfoActivity.kt
@@ -27,9 +27,9 @@ import android.widget.TextView
import androidx.core.content.ContextCompat
import at.bitfire.ical4android.TaskProvider.ProviderName
import at.bitfire.vcard4android.ContactsStorageException
+import com.etesync.journalmanager.Exceptions.HttpException
import com.etesync.syncadapter.*
import com.etesync.syncadapter.Constants.KEY_ACCOUNT
-import com.etesync.journalmanager.Exceptions.HttpException
import com.etesync.syncadapter.log.Logger
import com.etesync.syncadapter.model.EntryEntity
import com.etesync.syncadapter.model.JournalEntity
@@ -66,7 +66,11 @@ class DebugInfoActivity : BaseActivity(), LoaderManager.LoaderCallbacks
fun onShare(item: MenuItem) {
ACRA.getErrorReporter().putCustomData("debug_info", report)
- ACRA.getErrorReporter().handleSilentException(null)
+ val account: Account? = intent.extras?.getParcelable(KEY_ACCOUNT)
+ if (account != null) {
+ ACRA.getErrorReporter().putCustomData("username", account.name)
+ }
+ ACRA.getErrorReporter().handleException(intent.extras?.getSerializable(KEY_THROWABLE) as Throwable?)
ACRA.getErrorReporter().removeCustomData("debug_info")
}
diff --git a/app/src/main/java/com/etesync/syncadapter/ui/JournalItemActivity.kt b/app/src/main/java/com/etesync/syncadapter/ui/JournalItemActivity.kt
index 2f4a18bc..23e62fcc 100644
--- a/app/src/main/java/com/etesync/syncadapter/ui/JournalItemActivity.kt
+++ b/app/src/main/java/com/etesync/syncadapter/ui/JournalItemActivity.kt
@@ -264,7 +264,13 @@ class JournalItemActivity : BaseActivity(), Refreshable {
setTextViewText(view, R.id.title, event.summary)
- setTextViewText(view, R.id.when_datetime, getDisplayedDatetime(event.dtStart?.date?.time!!, event.dtEnd?.date!!.time, event.isAllDay(), context))
+ val dtStart = event.dtStart?.date?.time
+ val dtEnd = event.dtEnd?.date?.time
+ if ((dtStart == null) || (dtEnd == null)) {
+ setTextViewText(view, R.id.when_datetime, getString(R.string.loading_error_title))
+ } else {
+ setTextViewText(view, R.id.when_datetime, getDisplayedDatetime(dtStart, dtEnd, event.isAllDay(), context))
+ }
setTextViewText(view, R.id.where, event.location)
diff --git a/app/src/main/java/com/etesync/syncadapter/ui/MigrateV2Activity.kt b/app/src/main/java/com/etesync/syncadapter/ui/MigrateV2Activity.kt
new file mode 100644
index 00000000..9f40ba82
--- /dev/null
+++ b/app/src/main/java/com/etesync/syncadapter/ui/MigrateV2Activity.kt
@@ -0,0 +1,821 @@
+package com.etesync.syncadapter.ui
+
+import android.accounts.Account
+import android.accounts.AccountManager
+import android.app.Dialog
+import android.app.ProgressDialog
+import android.content.ContentResolver
+import android.content.Context
+import android.content.Intent
+import android.os.Bundle
+import android.provider.ContactsContract
+import android.text.TextUtils
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.*
+import androidx.activity.viewModels
+import androidx.appcompat.app.AlertDialog
+import androidx.fragment.app.*
+import at.bitfire.ical4android.Event
+import at.bitfire.ical4android.Task
+import at.bitfire.vcard4android.Contact
+import at.bitfire.vcard4android.ContactsStorageException
+import com.etebase.client.Account as EtebaseAccount
+import com.etebase.client.Client
+import com.etebase.client.Item
+import com.etebase.client.ItemMetadata
+import com.etesync.journalmanager.model.SyncEntry
+import com.etesync.syncadapter.*
+import com.etesync.syncadapter.model.*
+import com.etesync.syncadapter.resource.LocalAddressBook
+import com.etesync.syncadapter.resource.LocalCalendar
+import com.etesync.syncadapter.ui.etebase.*
+import com.etesync.syncadapter.ui.setup.CreateAccountFragment
+import com.etesync.syncadapter.ui.setup.LoginCredentials
+import com.google.android.material.textfield.TextInputEditText
+import com.google.android.material.textfield.TextInputLayout
+import net.cachapa.expandablelayout.ExpandableLayout
+import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
+import okhttp3.Request
+import okhttp3.RequestBody
+import org.jetbrains.anko.doAsync
+import org.jetbrains.anko.uiThread
+import java.io.StringReader
+import java.net.URI
+import java.util.*
+import kotlin.collections.HashMap
+
+class MigrateV2Activity : BaseActivity() {
+ private lateinit var accountV1: Account
+ private val model: AccountViewModel by viewModels()
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ accountV1 = intent.extras!!.getParcelable(EXTRA_ACCOUNT)!!
+
+ setContentView(R.layout.etebase_fragment_activity)
+
+ if (savedInstanceState == null) {
+ setTitle(R.string.migrate_v2_wizard_welcome_title)
+ supportFragmentManager.commit {
+ replace(R.id.fragment_container, WizardWelcomeFragment.newInstance(accountV1))
+ }
+ }
+ }
+
+ companion object {
+ private val EXTRA_ACCOUNT = "account"
+
+ fun newIntent(context: Context, account: Account): Intent {
+ val intent = Intent(context, MigrateV2Activity::class.java)
+ intent.putExtra(EXTRA_ACCOUNT, account)
+ return intent
+ }
+ }
+}
+
+
+fun reportErrorHelper(context: Context, e: Throwable) {
+ AlertDialog.Builder(context)
+ .setIcon(R.drawable.ic_info_dark)
+ .setTitle(R.string.exception)
+ .setMessage(e.localizedMessage)
+ .setPositiveButton(android.R.string.yes) { _, _ -> }.show()
+}
+
+class WizardWelcomeFragment : Fragment() {
+ internal lateinit var accountV1: Account
+
+ override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
+ val ret = inflater.inflate(R.layout.migrate_v2_wizard_welcome, container, false)
+
+ if (savedInstanceState == null) {
+ if (container != null) {
+ initUi(inflater, ret)
+ }
+ }
+
+ return ret
+ }
+
+ private fun initUi(inflater: LayoutInflater, v: View) {
+ v.findViewById