mirror of
https://github.com/etesync/android
synced 2024-11-22 07:58:09 +00:00
Merge branch 'master' into patch-1
This commit is contained in:
commit
a3d6d4f3d4
2
.github/FUNDING.yml
vendored
Normal file
2
.github/FUNDING.yml
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
github: etesync
|
||||
custom: https://www.etesync.com/contribute/#donate
|
44
ChangeLog.md
44
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.
|
||||
|
||||
|
@ -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"
|
||||
|
@ -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.
|
||||
|
@ -52,6 +52,7 @@
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:theme="@style/AppTheme"
|
||||
android:resizeableActivity="true"
|
||||
tools:ignore="UnusedAttribute">
|
||||
|
||||
<receiver
|
||||
@ -245,6 +246,10 @@
|
||||
android:name=".ui.etebase.InvitationsActivity"
|
||||
android:exported="false"
|
||||
/>
|
||||
<activity
|
||||
android:name=".ui.MigrateV2Activity"
|
||||
android:exported="false"
|
||||
/>
|
||||
<activity
|
||||
android:name=".ui.ViewCollectionActivity"
|
||||
android:exported="false"
|
||||
|
@ -3,11 +3,10 @@ package com.etesync.syncadapter;
|
||||
import android.content.Context;
|
||||
|
||||
import org.acra.config.CoreConfigurationBuilder;
|
||||
import org.acra.config.MailSenderConfigurationBuilder;
|
||||
import org.acra.config.NotificationConfigurationBuilder;
|
||||
import org.acra.config.DialogConfigurationBuilder;
|
||||
import org.acra.config.HttpSenderConfigurationBuilder;
|
||||
import org.acra.data.StringFormat;
|
||||
|
||||
import static com.etesync.syncadapter.utils.EventEmailInvitationKt.emailSupportsAttachments;
|
||||
import org.acra.sender.HttpSender;
|
||||
|
||||
public class AcraConfiguration {
|
||||
public static CoreConfigurationBuilder getConfig(Context context) {
|
||||
@ -15,17 +14,14 @@ public class AcraConfiguration {
|
||||
.setBuildConfigClass(BuildConfig.class)
|
||||
.setLogcatArguments("-t", "500", "-v", "time")
|
||||
.setReportFormat(StringFormat.JSON);
|
||||
builder.getPluginConfigurationBuilder(MailSenderConfigurationBuilder.class)
|
||||
.setMailTo("reports@etesync.com")
|
||||
.setResSubject(R.string.crash_email_subject)
|
||||
.setReportFileName("ACRA-report.stacktrace.json")
|
||||
.setReportAsFile(emailSupportsAttachments(context))
|
||||
builder.getPluginConfigurationBuilder(HttpSenderConfigurationBuilder.class)
|
||||
.setUri(Constants.crashReportingUrl)
|
||||
.setHttpMethod(HttpSender.Method.POST)
|
||||
.setEnabled(true);
|
||||
builder.getPluginConfigurationBuilder(NotificationConfigurationBuilder.class)
|
||||
builder.getPluginConfigurationBuilder(DialogConfigurationBuilder.class)
|
||||
.setResTitle(R.string.crash_title)
|
||||
.setResText(R.string.crash_message)
|
||||
.setResChannelName(R.string.notification_channel_crash_reports)
|
||||
.setSendOnClick(true)
|
||||
.setResCommentPrompt(R.string.crash_email_body)
|
||||
.setEnabled(true);
|
||||
|
||||
return builder;
|
||||
|
@ -25,14 +25,17 @@ public class Constants {
|
||||
NOTIFICATION_PERMISSIONS = 20;
|
||||
|
||||
public static final Uri webUri = Uri.parse((DEBUG_REMOTE_URL == null) ? "https://www.etesync.com/" : DEBUG_REMOTE_URL);
|
||||
public static final Uri etebaseDashboardPrefix = Uri.parse("https://dashboard.etebase.com/user/partner/");
|
||||
public static final Uri contactUri = webUri.buildUpon().appendEncodedPath("about/#contact").build();
|
||||
public static final Uri registrationUrl = webUri.buildUpon().appendEncodedPath("accounts/signup/").build();
|
||||
public static final Uri reportIssueUri = Uri.parse("https://github.com/etesync/android/issues");
|
||||
public static final Uri feedbackUri = reportIssueUri;
|
||||
public static final Uri pricing = webUri.buildUpon().appendEncodedPath("pricing/").build();
|
||||
public static final Uri dashboard = webUri.buildUpon().appendEncodedPath("dashboard/").build();
|
||||
public static final Uri faqUri = webUri.buildUpon().appendEncodedPath("faq/").build();
|
||||
public static final Uri helpUri = webUri.buildUpon().appendEncodedPath("user-guide/android/").build();
|
||||
public static final Uri forgotPassword = faqUri.buildUpon().fragment("forgot-password").build();
|
||||
public static final String crashReportingUrl = "https://www.etesync.com/crash/android-syncadapter/report/";
|
||||
|
||||
public static final Uri serviceUrl = Uri.parse((DEBUG_REMOTE_URL == null) ? "https://api.etesync.com/" : DEBUG_REMOTE_URL);
|
||||
public static final String etebaseServiceUrl = (DEBUG_REMOTE_URL == null) ? "https://api.etebase.com/partner/etesync/" : DEBUG_REMOTE_URL;
|
||||
@ -48,4 +51,9 @@ public class Constants {
|
||||
public final static String ETEBASE_TYPE_ADDRESS_BOOK = "etebase.vcard";
|
||||
public final static String ETEBASE_TYPE_CALENDAR = "etebase.vevent";
|
||||
public final static String ETEBASE_TYPE_TASKS = "etebase.vtodo";
|
||||
public final static String[] COLLECTION_TYPES = new String[] {
|
||||
ETEBASE_TYPE_ADDRESS_BOOK,
|
||||
ETEBASE_TYPE_CALENDAR,
|
||||
ETEBASE_TYPE_TASKS
|
||||
};
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ import android.content.Context
|
||||
import com.etebase.client.*
|
||||
import com.etebase.client.Collection
|
||||
import com.etebase.client.exceptions.EtebaseException
|
||||
import com.etebase.client.exceptions.UrlParseException
|
||||
import okhttp3.OkHttpClient
|
||||
import java.io.File
|
||||
import java.util.*
|
||||
@ -43,13 +44,13 @@ class EtebaseLocalCache private constructor(context: Context, username: String)
|
||||
return fsCache._unstable_collectionList(colMgr).filter {
|
||||
withDeleted || !it.isDeleted
|
||||
}.map{
|
||||
CachedCollection(it, it.meta)
|
||||
CachedCollection(it, it.meta, it.collectionType)
|
||||
}
|
||||
}
|
||||
|
||||
fun collectionGet(colMgr: CollectionManager, colUid: String): CachedCollection {
|
||||
return fsCache.collectionGet(colMgr, colUid).let {
|
||||
CachedCollection(it, it.meta)
|
||||
CachedCollection(it, it.meta, it.collectionType)
|
||||
}
|
||||
}
|
||||
|
||||
@ -58,7 +59,11 @@ class EtebaseLocalCache private constructor(context: Context, username: String)
|
||||
}
|
||||
|
||||
fun collectionUnset(colMgr: CollectionManager, colUid: String) {
|
||||
fsCache.collectionUnset(colMgr, colUid)
|
||||
try {
|
||||
fsCache.collectionUnset(colMgr, colUid)
|
||||
} catch (e: UrlParseException) {
|
||||
// Ignore, as it just means the file doesn't exist
|
||||
}
|
||||
}
|
||||
|
||||
fun itemList(itemMgr: ItemManager, colUid: String, withDeleted: Boolean = false): List<CachedItem> {
|
||||
@ -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)
|
@ -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() {
|
||||
|
@ -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()
|
||||
|
@ -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()
|
||||
}
|
||||
|
@ -83,7 +83,7 @@ class LocalEvent : AndroidEvent, LocalResource<Event> {
|
||||
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<Event> {
|
||||
.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<Event> {
|
||||
|
||||
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)
|
||||
|
@ -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)
|
||||
))
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
|
@ -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> {
|
||||
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<Task> {
|
||||
|
||||
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)
|
||||
|
@ -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) {
|
||||
|
@ -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)
|
||||
|
@ -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) {
|
||||
|
@ -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) {
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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<Item> {
|
||||
val ret = LinkedList<Item>()
|
||||
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)
|
||||
|
||||
|
@ -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
|
||||
|
@ -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) {
|
||||
|
@ -100,7 +100,7 @@ class AboutActivity : BaseActivity() {
|
||||
}
|
||||
|
||||
override fun onCreateLoader(id: Int, args: Bundle?): Loader<Spanned> {
|
||||
return LicenseLoader(context!!, args!!.getString(KEY_FILE_NAME))
|
||||
return LicenseLoader(requireContext(), args!!.getString(KEY_FILE_NAME)!!)
|
||||
}
|
||||
|
||||
override fun onLoadFinished(loader: Loader<Spanned>, license: Spanned) {
|
||||
|
@ -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<AccountActivity.AccountInfo>, 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<AccountInfo> {
|
||||
return AccountLoader(this, account)
|
||||
}
|
||||
|
||||
override fun refresh() {
|
||||
loaderManager.restartLoader(0, intent.extras, this)
|
||||
model.loadAccount()
|
||||
}
|
||||
|
||||
override fun onLoadFinished(loader: Loader<AccountInfo>, 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<AccountInfo>) {
|
||||
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<AccountInfo>(context), AccountUpdateService.RefreshingStatusListener, ServiceConnection, SyncStatusObserver {
|
||||
class AccountInfoViewModel : ViewModel(), AccountUpdateService.RefreshingStatusListener, ServiceConnection, SyncStatusObserver {
|
||||
private val holder = MutableLiveData<AccountActivity.AccountInfo>()
|
||||
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<CollectionListItemInfo> {
|
||||
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"
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -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<AccountSettings> {
|
||||
return AccountSettingsLoader(context!!, args!!.getParcelable(KEY_ACCOUNT) as Account)
|
||||
return AccountSettingsLoader(requireContext(), (args!!.getParcelable(KEY_ACCOUNT) as Account?)!!)
|
||||
}
|
||||
|
||||
override fun onLoadFinished(loader: Loader<AccountSettings>, 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<AccountSettings> {
|
||||
return AccountSettingsLoader(context!!, args!!.getParcelable(KEY_ACCOUNT) as Account)
|
||||
return AccountSettingsLoader(context!!, (args!!.getParcelable(KEY_ACCOUNT) as? Account)!!)
|
||||
}
|
||||
|
||||
override fun onLoadFinished(loader: Loader<AccountSettings>, settings: AccountSettings?) {
|
||||
|
@ -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()
|
||||
|
@ -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)
|
||||
|
@ -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<String>
|
||||
|
||||
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")
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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<Button>(R.id.signup).setOnClickListener {
|
||||
parentFragmentManager.commit {
|
||||
replace(R.id.fragment_container, SignupFragment.newInstance(accountV1))
|
||||
addToBackStack(null)
|
||||
}
|
||||
}
|
||||
|
||||
v.findViewById<Button>(R.id.login).setOnClickListener {
|
||||
parentFragmentManager.commit {
|
||||
replace(R.id.fragment_container, LoginFragment.newInstance(accountV1))
|
||||
addToBackStack(null)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun newInstance(accountV1: Account): WizardWelcomeFragment {
|
||||
val ret = WizardWelcomeFragment()
|
||||
ret.accountV1 = accountV1
|
||||
return ret
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class SignupFragment : Fragment() {
|
||||
internal lateinit var editUserName: TextInputLayout
|
||||
internal lateinit var editEmail: TextInputLayout
|
||||
internal lateinit var editPassword: TextInputLayout
|
||||
|
||||
internal lateinit var showAdvanced: CheckedTextView
|
||||
internal lateinit var customServer: TextInputEditText
|
||||
|
||||
internal lateinit var accountV1: Account
|
||||
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||
val v = inflater.inflate(R.layout.signup_fragment, container, false)
|
||||
|
||||
editUserName = v.findViewById(R.id.user_name)
|
||||
editEmail = v.findViewById(R.id.email)
|
||||
editPassword = v.findViewById(R.id.url_password)
|
||||
showAdvanced = v.findViewById(R.id.show_advanced)
|
||||
customServer = v.findViewById(R.id.custom_server)
|
||||
|
||||
// Hide stuff we don't need for the migration tool
|
||||
v.findViewById<View>(R.id.trial_notice).visibility = View.GONE
|
||||
editEmail.visibility = View.GONE
|
||||
editEmail.editText?.setText(accountV1.name)
|
||||
|
||||
val login = v.findViewById<Button>(R.id.login)
|
||||
login.visibility = View.GONE
|
||||
|
||||
val createAccount = v.findViewById<Button>(R.id.create_account)
|
||||
createAccount.setOnClickListener {
|
||||
val credentials = validateData()
|
||||
if (credentials != null) {
|
||||
SignupDoFragment.newInstance(accountV1, credentials).show(requireFragmentManager(), null)
|
||||
}
|
||||
}
|
||||
|
||||
val advancedLayout = v.findViewById<View>(R.id.advanced_layout) as ExpandableLayout
|
||||
|
||||
showAdvanced.setOnClickListener {
|
||||
if (showAdvanced.isChecked) {
|
||||
showAdvanced.isChecked = false
|
||||
advancedLayout.collapse()
|
||||
} else {
|
||||
showAdvanced.isChecked = true
|
||||
advancedLayout.expand()
|
||||
}
|
||||
}
|
||||
|
||||
return v
|
||||
}
|
||||
|
||||
protected fun validateData(): SignupCredentials? {
|
||||
var valid = true
|
||||
|
||||
val userName = editUserName.editText?.text.toString()
|
||||
// FIXME: this validation should only be done in the server, we are doing it here until the Java library supports field errors
|
||||
if ((userName.length < 6) || (!userName.matches(Regex("""^[\w.-]+$""")))) {
|
||||
editUserName.error = getString(R.string.login_username_error)
|
||||
valid = false
|
||||
} else {
|
||||
editUserName.error = null
|
||||
}
|
||||
|
||||
val email = editEmail.editText?.text.toString()
|
||||
if (email.isEmpty()) {
|
||||
editEmail.error = getString(R.string.login_email_address_error)
|
||||
valid = false
|
||||
} else {
|
||||
editEmail.error = null
|
||||
}
|
||||
|
||||
val password = editPassword.editText?.text.toString()
|
||||
if (password.length < 8) {
|
||||
editPassword.error = getString(R.string.signup_password_restrictions)
|
||||
valid = false
|
||||
} else {
|
||||
editPassword.error = null
|
||||
}
|
||||
|
||||
var uri: URI? = null
|
||||
if (showAdvanced.isChecked) {
|
||||
val server = customServer.text.toString()
|
||||
// If this field is null, just use the default
|
||||
if (!server.isEmpty()) {
|
||||
val url = server.toHttpUrlOrNull()
|
||||
if (url != null) {
|
||||
uri = url.toUri()
|
||||
customServer.error = null
|
||||
} else {
|
||||
customServer.error = getString(R.string.login_custom_server_error)
|
||||
valid = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return if (valid) SignupCredentials(uri, userName, email, password) else null
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun newInstance(accountV1: Account): SignupFragment {
|
||||
val ret = SignupFragment()
|
||||
ret.accountV1 = accountV1
|
||||
return ret
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class SignupDoFragment : DialogFragment() {
|
||||
private val model: ConfigurationViewModel by activityViewModels()
|
||||
|
||||
internal lateinit var accountV1: Account
|
||||
internal lateinit var signupCredentials: SignupCredentials
|
||||
|
||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||
val progress = ProgressDialog(activity)
|
||||
progress.setTitle(R.string.setting_up_encryption)
|
||||
progress.setMessage(getString(R.string.setting_up_encryption_content))
|
||||
progress.isIndeterminate = true
|
||||
progress.setCanceledOnTouchOutside(false)
|
||||
isCancelable = false
|
||||
return progress
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
if (savedInstanceState == null) {
|
||||
val settings = AccountSettings(requireContext(), accountV1)
|
||||
// Mark the etesync v1 account as wanting migration
|
||||
doAsync {
|
||||
val httpClient = HttpClient.Builder(context, settings).setForeground(true).build().okHttpClient
|
||||
val remote = settings.uri!!.toHttpUrlOrNull()!!.newBuilder()
|
||||
.addPathSegments("etesync-v2/confirm-migration/")
|
||||
.build()
|
||||
|
||||
val body = RequestBody.create(null, byteArrayOf())
|
||||
|
||||
val request = Request.Builder()
|
||||
.post(body)
|
||||
.url(remote)
|
||||
.build()
|
||||
|
||||
val response = httpClient.newCall(request).execute()
|
||||
uiThread {
|
||||
if (context == null) {
|
||||
dismissAllowingStateLoss()
|
||||
return@uiThread
|
||||
}
|
||||
if (response.isSuccessful) {
|
||||
model.signup(requireContext(), signupCredentials)
|
||||
} else {
|
||||
if (response.code == 400) {
|
||||
reportErrorHelper(requireContext(), Error("User already migrated. Please login instead."))
|
||||
} else {
|
||||
reportErrorHelper(requireContext(), Error("Failed preparing account for migration"))
|
||||
}
|
||||
dismissAllowingStateLoss()
|
||||
}
|
||||
}
|
||||
}
|
||||
model.observe(this) {
|
||||
if (it.isFailed) {
|
||||
reportErrorHelper(requireContext(), it.error!!)
|
||||
dismissAllowingStateLoss()
|
||||
} else {
|
||||
doAsync {
|
||||
val httpClient = HttpClient.Builder(context).setForeground(true).build().okHttpClient
|
||||
val client = Client.create(httpClient, it.url.toString())
|
||||
val etebase = EtebaseAccount.restore(client, it.etebaseSession!!, null)
|
||||
uiThread {
|
||||
fragmentManager?.commit {
|
||||
replace(R.id.fragment_container, WizardCollectionsFragment.newInstance(accountV1, etebase))
|
||||
addToBackStack(null)
|
||||
}
|
||||
dismissAllowingStateLoss()
|
||||
}
|
||||
}
|
||||
} }
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun newInstance(accountV1: Account, signupCredentials: SignupCredentials): SignupDoFragment {
|
||||
val ret = SignupDoFragment()
|
||||
ret.accountV1 = accountV1
|
||||
ret.signupCredentials = signupCredentials
|
||||
return ret
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class LoginFragment() : Fragment() {
|
||||
internal lateinit var editUserName: EditText
|
||||
internal lateinit var editUrlPassword: TextInputLayout
|
||||
|
||||
internal lateinit var showAdvanced: CheckedTextView
|
||||
internal lateinit var customServer: EditText
|
||||
|
||||
internal lateinit var accountV1: Account
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||
val v = inflater.inflate(R.layout.login_credentials_fragment, container, false)
|
||||
|
||||
editUserName = v.findViewById<TextInputEditText>(R.id.user_name)
|
||||
editUrlPassword = v.findViewById<TextInputLayout>(R.id.url_password)
|
||||
showAdvanced = v.findViewById<CheckedTextView>(R.id.show_advanced)
|
||||
customServer = v.findViewById<TextInputEditText>(R.id.custom_server)
|
||||
|
||||
v.findViewById<View>(R.id.create_account).visibility = View.GONE
|
||||
|
||||
val login = v.findViewById<View>(R.id.login) as Button
|
||||
login.setOnClickListener {
|
||||
val credentials = validateLoginData()
|
||||
if (credentials != null)
|
||||
LoginDoFragment.newInstance(accountV1, credentials).show(fragmentManager!!, null)
|
||||
}
|
||||
|
||||
val forgotPassword = v.findViewById<View>(R.id.forgot_password) as TextView
|
||||
forgotPassword.setOnClickListener { WebViewActivity.openUrl(context!!, Constants.forgotPassword) }
|
||||
|
||||
val advancedLayout = v.findViewById<View>(R.id.advanced_layout) as ExpandableLayout
|
||||
|
||||
showAdvanced.setOnClickListener {
|
||||
if (showAdvanced.isChecked) {
|
||||
showAdvanced.isChecked = false
|
||||
advancedLayout.collapse()
|
||||
} else {
|
||||
showAdvanced.isChecked = true
|
||||
advancedLayout.expand()
|
||||
}
|
||||
}
|
||||
|
||||
return v
|
||||
}
|
||||
|
||||
protected fun validateLoginData(): LoginCredentials? {
|
||||
var valid = true
|
||||
|
||||
val userName = editUserName.text.toString()
|
||||
if (userName.isEmpty()) {
|
||||
editUserName.error = getString(R.string.login_email_address_error)
|
||||
valid = false
|
||||
} else {
|
||||
editUserName.error = null
|
||||
}
|
||||
|
||||
val password = editUrlPassword.editText?.text.toString()
|
||||
if (password.isEmpty()) {
|
||||
editUrlPassword.error = getString(R.string.login_password_required)
|
||||
valid = false
|
||||
} else {
|
||||
editUrlPassword.error = null
|
||||
}
|
||||
|
||||
var uri: URI? = null
|
||||
if (showAdvanced.isChecked) {
|
||||
val server = customServer.text.toString()
|
||||
// If this field is null, just use the default
|
||||
if (!server.isEmpty()) {
|
||||
val url = server.toHttpUrlOrNull()
|
||||
if (url != null) {
|
||||
uri = url.toUri()
|
||||
customServer.error = null
|
||||
} else {
|
||||
customServer.error = getString(R.string.login_custom_server_error)
|
||||
valid = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return if (valid) LoginCredentials(uri, userName, password) else null
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun newInstance(accountV1: Account): LoginFragment {
|
||||
val ret = LoginFragment()
|
||||
ret.accountV1 = accountV1
|
||||
return ret
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class LoginDoFragment() : DialogFragment() {
|
||||
private val model: ConfigurationViewModel by activityViewModels()
|
||||
|
||||
internal lateinit var accountV1: Account
|
||||
internal lateinit var loginCredentials: LoginCredentials
|
||||
|
||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||
val progress = ProgressDialog(activity)
|
||||
progress.setTitle(R.string.setting_up_encryption)
|
||||
progress.setMessage(getString(R.string.setting_up_encryption_content))
|
||||
progress.isIndeterminate = true
|
||||
progress.setCanceledOnTouchOutside(false)
|
||||
isCancelable = false
|
||||
return progress
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
if (savedInstanceState == null) {
|
||||
val settings = AccountSettings(requireContext(), accountV1)
|
||||
model.login(requireContext(), loginCredentials)
|
||||
model.observe(this) {
|
||||
if (it.isFailed) {
|
||||
reportErrorHelper(requireContext(), it.error!!)
|
||||
dismissAllowingStateLoss()
|
||||
} else {
|
||||
doAsync {
|
||||
val httpClient = HttpClient.Builder(context).setForeground(true).build().okHttpClient
|
||||
val client = Client.create(httpClient, it.url.toString())
|
||||
val etebase = EtebaseAccount.restore(client, it.etebaseSession!!, null)
|
||||
uiThread {
|
||||
fragmentManager?.commit {
|
||||
replace(R.id.fragment_container, WizardCollectionsFragment.newInstance(accountV1, etebase))
|
||||
addToBackStack(null)
|
||||
}
|
||||
dismissAllowingStateLoss()
|
||||
}
|
||||
}
|
||||
} }
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun newInstance(accountV1: Account, loginCredentials: LoginCredentials): LoginDoFragment {
|
||||
val ret = LoginDoFragment()
|
||||
ret.accountV1 = accountV1
|
||||
ret.loginCredentials = loginCredentials
|
||||
return ret
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class WizardCollectionsFragment() : Fragment() {
|
||||
private val loadingModel: LoadingViewModel by viewModels()
|
||||
private lateinit var info: AccountActivity.AccountInfo
|
||||
private val migrateJournals = HashMap<String, AccountActivity.CollectionListItemInfo>()
|
||||
|
||||
internal lateinit var accountV1: Account
|
||||
internal lateinit var etebase: EtebaseAccount
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||
val ret = inflater.inflate(R.layout.migrate_v2_collections, container, false)
|
||||
|
||||
if (savedInstanceState == null) {
|
||||
if (container != null) {
|
||||
initUi(inflater, ret)
|
||||
}
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
private fun initUi(inflater: LayoutInflater, v: View) {
|
||||
v.findViewById<Button>(R.id.button_create).setOnClickListener {
|
||||
MigrateCollectionsDoFragment.newInstance(etebase, this.migrateJournals).show(parentFragmentManager, null)
|
||||
}
|
||||
|
||||
v.findViewById<Button>(R.id.button_skip).setOnClickListener {
|
||||
activity?.finish()
|
||||
}
|
||||
|
||||
loadAccount(v)
|
||||
}
|
||||
|
||||
private fun loadAccount(v: View) {
|
||||
val account = accountV1
|
||||
info = AccountActivity.AccountInfo()
|
||||
val data = (requireContext().applicationContext as App).data
|
||||
|
||||
loadingModel.setLoading(true)
|
||||
doAsync {
|
||||
try {
|
||||
for (serviceEntity in data.select(ServiceEntity::class.java).where(ServiceEntity.ACCOUNT.eq(account.name)).get()) {
|
||||
val service = serviceEntity.type!!
|
||||
when (service) {
|
||||
CollectionInfo.Type.ADDRESS_BOOK -> {
|
||||
info.carddav = AccountActivity.AccountInfo.ServiceInfo()
|
||||
info.carddav!!.infos = getLegacyJournals(data, serviceEntity)
|
||||
|
||||
val accountManager = AccountManager.get(context)
|
||||
for (addrBookAccount in accountManager.getAccountsByType(App.addressBookAccountType)) {
|
||||
val addressBook = LocalAddressBook(requireContext(), addrBookAccount, null)
|
||||
try {
|
||||
if (account == addressBook.mainAccount)
|
||||
info.carddav!!.refreshing = info.carddav!!.refreshing or ContentResolver.isSyncActive(addrBookAccount, ContactsContract.AUTHORITY)
|
||||
} catch (e: ContactsStorageException) {
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
CollectionInfo.Type.CALENDAR -> {
|
||||
info.caldav = AccountActivity.AccountInfo.ServiceInfo()
|
||||
info.caldav!!.infos = getLegacyJournals(data, serviceEntity)
|
||||
}
|
||||
CollectionInfo.Type.TASKS -> {
|
||||
info.taskdav = AccountActivity.AccountInfo.ServiceInfo()
|
||||
info.taskdav!!.infos = getLegacyJournals(data, serviceEntity)
|
||||
}
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
uiThread {
|
||||
loadingModel.setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
uiThread {
|
||||
if (info.carddav != null) {
|
||||
val infos = info.carddav!!.infos!!
|
||||
val listCardDAV = v.findViewById<View>(R.id.address_books) as ListView
|
||||
val adapter = CollectionListAdapter(requireContext(), account)
|
||||
adapter.addAll(infos)
|
||||
listCardDAV.adapter = adapter
|
||||
listCardDAV.setOnItemClickListener { adapterView, view, i, l ->
|
||||
val infoItem = infos.get(i)
|
||||
if (this@WizardCollectionsFragment.migrateJournals.contains(infoItem.uid)) {
|
||||
this@WizardCollectionsFragment.migrateJournals.remove(infoItem.uid)
|
||||
} else {
|
||||
this@WizardCollectionsFragment.migrateJournals.set(infoItem.uid, infoItem)
|
||||
}
|
||||
view.findViewById<CheckBox>(R.id.sync).isChecked = this@WizardCollectionsFragment.migrateJournals.contains(infoItem.uid)
|
||||
}
|
||||
}
|
||||
|
||||
if (info.caldav != null) {
|
||||
val infos = info.caldav!!.infos!!
|
||||
val listCalDAV = v.findViewById<View>(R.id.calendars) as ListView
|
||||
val adapter = CollectionListAdapter(requireContext(), account)
|
||||
adapter.addAll(infos)
|
||||
listCalDAV.adapter = adapter
|
||||
listCalDAV.setOnItemClickListener { adapterView, view, i, l ->
|
||||
val infoItem = infos.get(i)
|
||||
if (this@WizardCollectionsFragment.migrateJournals.contains(infoItem.uid)) {
|
||||
this@WizardCollectionsFragment.migrateJournals.remove(infoItem.uid)
|
||||
} else {
|
||||
this@WizardCollectionsFragment.migrateJournals.set(infoItem.uid, infoItem)
|
||||
}
|
||||
view.findViewById<CheckBox>(R.id.sync).isChecked = this@WizardCollectionsFragment.migrateJournals.contains(infoItem.uid)
|
||||
}
|
||||
}
|
||||
|
||||
if (info.taskdav != null) {
|
||||
val infos = info.taskdav!!.infos!!
|
||||
val listTaskDAV = v.findViewById<View>(R.id.tasklists) as ListView
|
||||
val adapter = CollectionListAdapter(requireContext(), account)
|
||||
adapter.addAll(infos)
|
||||
listTaskDAV.adapter = adapter
|
||||
listTaskDAV.setOnItemClickListener { adapterView, view, i, l ->
|
||||
val infoItem = infos.get(i)
|
||||
if (this@WizardCollectionsFragment.migrateJournals.contains(infoItem.uid)) {
|
||||
this@WizardCollectionsFragment.migrateJournals.remove(infoItem.uid)
|
||||
} else {
|
||||
this@WizardCollectionsFragment.migrateJournals.set(infoItem.uid, infoItem)
|
||||
}
|
||||
view.findViewById<CheckBox>(R.id.sync).isChecked = this@WizardCollectionsFragment.migrateJournals.contains(infoItem.uid)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getLegacyJournals(data: MyEntityDataStore, serviceEntity: ServiceEntity): List<AccountActivity.CollectionListItemInfo> {
|
||||
return JournalEntity.getJournals(data, serviceEntity).map {
|
||||
val info = it.info
|
||||
val isAdmin = it.isOwner(accountV1.name)
|
||||
AccountActivity.CollectionListItemInfo(it.uid, info.enumType!!, info.displayName!!, info.description
|
||||
?: "", info.color, it.isReadOnly, isAdmin, info)
|
||||
}
|
||||
}
|
||||
|
||||
class CollectionListAdapter(context: Context, private val account: Account) : ArrayAdapter<AccountActivity.CollectionListItemInfo>(context, R.layout.account_collection_item) {
|
||||
override fun getView(position: Int, _v: View?, parent: ViewGroup): View {
|
||||
var v = _v
|
||||
if (v == null)
|
||||
v = LayoutInflater.from(context).inflate(R.layout.account_collection_item, parent, false)
|
||||
|
||||
v!!.findViewById<View>(R.id.sync).visibility = View.VISIBLE
|
||||
val info = getItem(position)!!
|
||||
|
||||
var tv = v!!.findViewById<View>(R.id.title) as TextView
|
||||
tv.text = if (TextUtils.isEmpty(info.displayName)) info.uid else info.displayName
|
||||
|
||||
tv = v.findViewById<View>(R.id.description) as TextView
|
||||
if (TextUtils.isEmpty(info.description))
|
||||
tv.visibility = View.GONE
|
||||
else {
|
||||
tv.visibility = View.VISIBLE
|
||||
tv.text = info.description
|
||||
}
|
||||
|
||||
val vColor = v.findViewById<View>(R.id.color)
|
||||
if (info.enumType == CollectionInfo.Type.ADDRESS_BOOK) {
|
||||
vColor.visibility = View.GONE
|
||||
} else {
|
||||
vColor.setBackgroundColor(info.color ?: LocalCalendar.defaultColor)
|
||||
}
|
||||
|
||||
val readOnly = v.findViewById<View>(R.id.read_only)
|
||||
readOnly.visibility = if (info.isReadOnly) View.VISIBLE else View.GONE
|
||||
|
||||
val shared = v.findViewById<View>(R.id.shared)
|
||||
val isOwner = info.isAdmin
|
||||
shared.visibility = if (isOwner) View.GONE else View.VISIBLE
|
||||
|
||||
return v
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun newInstance(accountV1: Account, etebase: EtebaseAccount): WizardCollectionsFragment {
|
||||
val ret = WizardCollectionsFragment()
|
||||
ret.accountV1 = accountV1
|
||||
ret.etebase = etebase
|
||||
return ret
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class MigrateCollectionsDoFragment : DialogFragment() {
|
||||
private val configurationModel: ConfigurationViewModel by activityViewModels()
|
||||
private lateinit var progress: ProgressDialog
|
||||
private val CHUNK_SIZE = 20
|
||||
|
||||
internal lateinit var etebase: EtebaseAccount
|
||||
internal lateinit var migrateJournals: HashMap<String, AccountActivity.CollectionListItemInfo>
|
||||
|
||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||
progress = ProgressDialog(activity)
|
||||
progress.setTitle(R.string.migrate_v2_wizard_migrate_title)
|
||||
progress.setMessage(getString(R.string.migrate_v2_wizard_migrate_title))
|
||||
progress.isIndeterminate = true
|
||||
progress.setCanceledOnTouchOutside(false)
|
||||
isCancelable = false
|
||||
return progress
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
if (savedInstanceState == null) {
|
||||
migrate()
|
||||
}
|
||||
}
|
||||
|
||||
private fun migrate() {
|
||||
val data = (requireContext().applicationContext as App).data
|
||||
doAsync {
|
||||
try {
|
||||
val total = migrateJournals.size
|
||||
var malformed = 0
|
||||
var badMtime = 0
|
||||
var i = 1
|
||||
val colMgr = etebase.collectionManager
|
||||
for (itemInfo in migrateJournals.values) {
|
||||
uiThread {
|
||||
progress.setMessage(getString(R.string.migrate_v2_wizard_migrate_progress, i, total))
|
||||
}
|
||||
val colType = when (itemInfo.enumType) {
|
||||
CollectionInfo.Type.ADDRESS_BOOK -> Constants.ETEBASE_TYPE_ADDRESS_BOOK
|
||||
CollectionInfo.Type.CALENDAR -> Constants.ETEBASE_TYPE_CALENDAR
|
||||
CollectionInfo.Type.TASKS -> Constants.ETEBASE_TYPE_TASKS
|
||||
}
|
||||
|
||||
val colMeta = ItemMetadata()
|
||||
colMeta.name = itemInfo.displayName
|
||||
colMeta.description = itemInfo.description
|
||||
if (itemInfo.color != null) {
|
||||
colMeta.color = String.format("#%06X", 0xFFFFFF and itemInfo.color)
|
||||
}
|
||||
colMeta.mtime = System.currentTimeMillis()
|
||||
val collection = colMgr.create(colType, colMeta, "")
|
||||
colMgr.upload(collection)
|
||||
|
||||
val migratedItems = HashMap<String, Item>()
|
||||
val journalEntity = JournalModel.Journal.fetch(data, itemInfo.legacyInfo!!.getServiceEntity(data), itemInfo.uid)
|
||||
val entries = data.select(EntryEntity::class.java).where(EntryEntity.JOURNAL.eq(journalEntity)).orderBy(EntryEntity.ID.asc()).get().toList()
|
||||
val itemMgr = colMgr.getItemManager(collection)
|
||||
var itemDone = 0
|
||||
val toPush = LinkedList<Item>()
|
||||
for (entry in entries) {
|
||||
itemDone++
|
||||
|
||||
val inputReader = StringReader(entry.content.content)
|
||||
val uid: String?
|
||||
var lastModified: Long?
|
||||
when (itemInfo.enumType) {
|
||||
CollectionInfo.Type.ADDRESS_BOOK -> {
|
||||
val contact = Contact.fromReader(inputReader, null)[0]
|
||||
uid = contact.uid
|
||||
lastModified = null
|
||||
}
|
||||
CollectionInfo.Type.CALENDAR -> {
|
||||
val event = Event.eventsFromReader(inputReader)[0]
|
||||
uid = event.uid
|
||||
lastModified = event.lastModified?.dateTime?.time
|
||||
}
|
||||
CollectionInfo.Type.TASKS -> {
|
||||
val task = Task.tasksFromReader(inputReader)[0]
|
||||
uid = task.uid
|
||||
lastModified = task.lastModified
|
||||
}
|
||||
}
|
||||
|
||||
if (uid == null) {
|
||||
malformed++
|
||||
continue
|
||||
}
|
||||
if (lastModified == null) {
|
||||
// When we can't set mtime, set to the item's position in the change log so we at least maintain EteSync 1.0 ordering.
|
||||
lastModified = System.currentTimeMillis() + itemDone
|
||||
badMtime++
|
||||
}
|
||||
|
||||
val item: Item
|
||||
if (migratedItems.containsKey(uid)) {
|
||||
val tmp = migratedItems.get(uid)!!
|
||||
// We need to clone the item so we can push multiple versions at once
|
||||
item = itemMgr.cacheLoad(itemMgr.cacheSaveWithContent(tmp))
|
||||
item.setContent(entry.content.content)
|
||||
val meta = item.meta
|
||||
meta.mtime = lastModified
|
||||
item.meta = meta
|
||||
} else {
|
||||
val meta = ItemMetadata()
|
||||
meta.mtime = lastModified
|
||||
meta.name = uid
|
||||
item = itemMgr.create(meta, entry.content.content)
|
||||
migratedItems.set(uid, item)
|
||||
}
|
||||
if (entry.content.isAction(SyncEntry.Actions.DELETE)) {
|
||||
item.delete()
|
||||
}
|
||||
toPush.add(item)
|
||||
|
||||
if (toPush.size == CHUNK_SIZE) {
|
||||
uiThread {
|
||||
progress.setMessage(context?.getString(R.string.migrate_v2_wizard_migrate_progress, i, total) + "\n" +
|
||||
getString(R.string.migrate_v2_wizard_migrate_progress_entries, itemDone, entries.size))
|
||||
}
|
||||
itemMgr.batch(toPush.toTypedArray())
|
||||
toPush.clear()
|
||||
}
|
||||
}
|
||||
if (toPush.size > 0) {
|
||||
itemMgr.batch(toPush.toTypedArray())
|
||||
}
|
||||
|
||||
i++;
|
||||
}
|
||||
|
||||
uiThread {
|
||||
var message = getString(R.string.migrate_v2_wizard_migrate_progress_done)
|
||||
if (malformed > 0) {
|
||||
message += "\n\n" + getString(R.string.migrate_v2_wizard_migrate_progress_done_malformed, malformed)
|
||||
}
|
||||
AlertDialog.Builder(requireContext())
|
||||
.setIcon(R.drawable.ic_info_dark)
|
||||
.setTitle(R.string.migrate_v2_wizard_migrate_title)
|
||||
.setMessage(message)
|
||||
.setPositiveButton(android.R.string.yes) { _, _ -> }
|
||||
.setOnDismissListener {
|
||||
requireFragmentManager().commit {
|
||||
replace(android.R.id.content, CreateAccountFragment.newInstance(configurationModel.account.value!!))
|
||||
addToBackStack(null)
|
||||
}
|
||||
dismissAllowingStateLoss()
|
||||
}
|
||||
.show()
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
uiThread {
|
||||
reportErrorHelper(requireContext(), e)
|
||||
dismissAllowingStateLoss()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
companion object {
|
||||
fun newInstance(etebase: EtebaseAccount,
|
||||
migrateJournals: HashMap<String, AccountActivity.CollectionListItemInfo>): MigrateCollectionsDoFragment {
|
||||
val ret = MigrateCollectionsDoFragment()
|
||||
ret.etebase = etebase
|
||||
ret.migrateJournals = migrateJournals
|
||||
return ret
|
||||
}
|
||||
}
|
||||
}
|
@ -94,7 +94,7 @@ class StartupDialogFragment : DialogFragment() {
|
||||
|
||||
// Vendor specific bugs
|
||||
val manu = Build.MANUFACTURER
|
||||
if (!HintManager.getHintSeen(context, HINT_BATTERY_OPTIMIZATIONS) && (manu.equals("Xiaomi", ignoreCase = true) || manu.equals("Huawei", ignoreCase = true)) && !Build.DISPLAY.contains("lineage")) {
|
||||
if (!HintManager.getHintSeen(context, HINT_VENDOR_SPECIFIC_BUGS) && (manu.equals("Xiaomi", ignoreCase = true) || manu.equals("Huawei", ignoreCase = true)) && !Build.DISPLAY.contains("lineage")) {
|
||||
dialogs.add(StartupDialogFragment.instantiate(Mode.VENDOR_SPECIFIC_BUGS))
|
||||
}
|
||||
|
||||
|
@ -31,7 +31,7 @@ class WebViewActivity : BaseActivity() {
|
||||
mToolbar = supportActionBar
|
||||
mToolbar!!.setDisplayHomeAsUpEnabled(true)
|
||||
|
||||
var uri = intent.getParcelableExtra<Uri>(KEY_URL)
|
||||
var uri = intent.getParcelableExtra<Uri>(KEY_URL)!!
|
||||
uri = addQueryParams(uri)
|
||||
mWebView = findViewById<View>(R.id.webView) as WebView
|
||||
mProgressBar = findViewById<View>(R.id.progressBar) as ProgressBar
|
||||
@ -166,7 +166,7 @@ class WebViewActivity : BaseActivity() {
|
||||
fun openUrl(context: Context, uri: Uri) {
|
||||
if (isAllowedUrl(uri)) {
|
||||
val intent = Intent(context, WebViewActivity::class.java)
|
||||
intent.putExtra(WebViewActivity.KEY_URL, uri)
|
||||
intent.putExtra(KEY_URL, uri)
|
||||
context.startActivity(intent)
|
||||
} else {
|
||||
try {
|
||||
@ -191,10 +191,22 @@ class WebViewActivity : BaseActivity() {
|
||||
}
|
||||
|
||||
private fun isAllowedUrl(uri: Uri): Boolean {
|
||||
val allowedUris = arrayOf(Constants.faqUri, Constants.helpUri, Constants.registrationUrl, Constants.dashboard, Constants.webUri.buildUpon().appendEncodedPath("tos/").build(), Constants.webUri.buildUpon().appendEncodedPath("about/").build())
|
||||
val allowedUris = arrayOf(
|
||||
Constants.faqUri,
|
||||
Constants.helpUri,
|
||||
Constants.registrationUrl,
|
||||
Constants.dashboard,
|
||||
Constants.webUri.buildUpon().appendEncodedPath("tos/").build(),
|
||||
Constants.webUri.buildUpon().appendEncodedPath("about/").build(),
|
||||
Constants.pricing,
|
||||
)
|
||||
val accountsUri = Constants.webUri.buildUpon().appendEncodedPath("accounts/").build()
|
||||
|
||||
return allowedUris(allowedUris, uri) || uri.host == accountsUri.host && uri.path!!.startsWith(accountsUri.path!!)
|
||||
return allowedUris(allowedUris, uri) || (
|
||||
uri.host == accountsUri.host && uri.path!!.startsWith(accountsUri.path!!)
|
||||
) || (
|
||||
uri.host == Constants.etebaseDashboardPrefix.host && uri.path!!.startsWith(Constants.etebaseDashboardPrefix.path!!)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -11,7 +11,7 @@ import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.observe
|
||||
import com.etebase.client.CollectionManager
|
||||
import com.etebase.client.CollectionMetadata
|
||||
import com.etebase.client.ItemMetadata
|
||||
import com.etesync.syncadapter.*
|
||||
import com.etesync.syncadapter.ui.BaseActivity
|
||||
import org.jetbrains.anko.doAsync
|
||||
@ -30,7 +30,7 @@ class CollectionActivity() : BaseActivity() {
|
||||
val colUid = intent.extras!!.getString(EXTRA_COLLECTION_UID)
|
||||
val colType = intent.extras!!.getString(EXTRA_COLLECTION_TYPE)
|
||||
|
||||
setContentView(R.layout.etebase_collection_activity)
|
||||
setContentView(R.layout.etebase_fragment_activity)
|
||||
|
||||
if (savedInstanceState == null) {
|
||||
model.loadAccount(this, account)
|
||||
@ -47,11 +47,12 @@ class CollectionActivity() : BaseActivity() {
|
||||
} else if (colType != null) {
|
||||
model.observe(this) {
|
||||
doAsync {
|
||||
val meta = CollectionMetadata(colType, "")
|
||||
val cachedCollection = CachedCollection(it.colMgr.create(meta, ""), meta)
|
||||
val meta = ItemMetadata()
|
||||
meta.name = ""
|
||||
val cachedCollection = CachedCollection(it.colMgr.create(colType, meta, ""), meta, colType)
|
||||
uiThread {
|
||||
supportFragmentManager.commit {
|
||||
replace(R.id.fragment_container, EditCollectionFragment(cachedCollection, true))
|
||||
replace(R.id.fragment_container, EditCollectionFragment.newInstance(cachedCollection, true))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -37,10 +37,12 @@ import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
import java.util.concurrent.Future
|
||||
|
||||
class CollectionItemFragment(private val cachedItem: CachedItem) : Fragment() {
|
||||
class CollectionItemFragment : Fragment() {
|
||||
private val model: AccountViewModel by activityViewModels()
|
||||
private val collectionModel: CollectionViewModel by activityViewModels()
|
||||
|
||||
private lateinit var cachedItem: CachedItem
|
||||
|
||||
private var emailInvitationEvent: Event? = null
|
||||
private var emailInvitationEventString: String? = null
|
||||
|
||||
@ -101,7 +103,7 @@ class CollectionItemFragment(private val cachedItem: CachedItem) : Fragment() {
|
||||
val context = requireContext()
|
||||
val account = accountHolder.account
|
||||
val cachedCol = collectionModel.value!!
|
||||
when (cachedCol.meta.collectionType) {
|
||||
when (cachedCol.collectionType) {
|
||||
Constants.ETEBASE_TYPE_CALENDAR -> {
|
||||
val provider = context.contentResolver.acquireContentProviderClient(CalendarContract.CONTENT_URI)!!
|
||||
val localCalendar = LocalCalendar.findByName(account, provider, LocalCalendar.Factory, cachedCol.col.uid)!!
|
||||
@ -156,6 +158,14 @@ class CollectionItemFragment(private val cachedItem: CachedItem) : Fragment() {
|
||||
.create()
|
||||
dialog.show()
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun newInstance(cachedItem: CachedItem): CollectionItemFragment {
|
||||
val ret = CollectionItemFragment()
|
||||
ret.cachedItem = cachedItem
|
||||
return ret
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class TabsAdapter(fm: FragmentManager, private val mainFragment: CollectionItemFragment, private val context: Context, private val cachedCollection: CachedCollection, private val cachedItem: CachedItem) : FragmentPagerAdapter(fm) {
|
||||
@ -177,17 +187,18 @@ private class TabsAdapter(fm: FragmentManager, private val mainFragment: Collect
|
||||
|
||||
override fun getItem(position: Int): Fragment {
|
||||
return if (position == 0) {
|
||||
PrettyFragment(mainFragment, cachedCollection, cachedItem.content)
|
||||
PrettyFragment.newInstance(mainFragment, cachedCollection, cachedItem.content)
|
||||
} else if (position == 1) {
|
||||
TextFragment(cachedItem.content)
|
||||
TextFragment.newInstance(cachedItem.content)
|
||||
} else {
|
||||
ItemRevisionsListFragment(cachedCollection, cachedItem)
|
||||
ItemRevisionsListFragment.newInstance(cachedCollection, cachedItem)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class TextFragment(private val content: String) : Fragment() {
|
||||
class TextFragment : Fragment() {
|
||||
private lateinit var content: String
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||
val v = inflater.inflate(R.layout.text_fragment, container, false)
|
||||
|
||||
@ -197,15 +208,26 @@ class TextFragment(private val content: String) : Fragment() {
|
||||
|
||||
return v
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun newInstance(content: String): TextFragment {
|
||||
val ret = TextFragment()
|
||||
ret.content = content
|
||||
return ret
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class PrettyFragment(private val mainFragment: CollectionItemFragment, private val cachedCollection: CachedCollection, private val content: String) : Fragment() {
|
||||
class PrettyFragment : Fragment() {
|
||||
private var asyncTask: Future<Unit>? = null
|
||||
private lateinit var mainFragment: CollectionItemFragment
|
||||
private lateinit var cachedCollection: CachedCollection
|
||||
private lateinit var content: String
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||
var v: View? = null
|
||||
|
||||
when (cachedCollection.meta.collectionType) {
|
||||
when (cachedCollection.collectionType) {
|
||||
Constants.ETEBASE_TYPE_ADDRESS_BOOK -> {
|
||||
v = inflater.inflate(R.layout.contact_info, container, false)
|
||||
asyncTask = loadContactTask(v)
|
||||
@ -251,7 +273,13 @@ class PrettyFragment(private val mainFragment: CollectionItemFragment, private v
|
||||
|
||||
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)
|
||||
|
||||
@ -476,6 +504,14 @@ class PrettyFragment(private val mainFragment: CollectionItemFragment, private v
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun newInstance(mainFragment: CollectionItemFragment, cachedCollection: CachedCollection, content: String): PrettyFragment {
|
||||
val ret = PrettyFragment()
|
||||
ret.mainFragment= mainFragment
|
||||
ret.cachedCollection = cachedCollection
|
||||
ret.content = content
|
||||
return ret
|
||||
}
|
||||
|
||||
private fun addInfoItem(context: Context, parent: ViewGroup, type: String, label: String?, value: String?): View {
|
||||
val layout = parent.findViewById<View>(R.id.container) as ViewGroup
|
||||
val infoItem = LayoutInflater.from(context).inflate(R.layout.contact_info_item, layout, false)
|
||||
|
@ -54,9 +54,10 @@ class CollectionMembersFragment : Fragment() {
|
||||
|
||||
private fun initUi(inflater: LayoutInflater, v: View, cachedCollection: CachedCollection) {
|
||||
val meta = cachedCollection.meta
|
||||
val collectionType = cachedCollection.collectionType
|
||||
val colorSquare = v.findViewById<View>(R.id.color)
|
||||
val color = LocalCalendar.parseColor(meta.color)
|
||||
when (meta.collectionType) {
|
||||
when (collectionType) {
|
||||
Constants.ETEBASE_TYPE_CALENDAR -> {
|
||||
colorSquare.setBackgroundColor(color)
|
||||
}
|
||||
@ -104,7 +105,7 @@ class CollectionMembersFragment : Fragment() {
|
||||
val username = view.findViewById<EditText>(R.id.username).text.toString()
|
||||
val readOnly = view.findViewById<CheckBox>(R.id.read_only).isChecked
|
||||
|
||||
val frag = AddMemberFragment(model.value!!, collectionModel.value!!, username, if (readOnly) CollectionAccessLevel.ReadOnly else CollectionAccessLevel.ReadWrite)
|
||||
val frag = AddMemberFragment.newInstance(model.value!!, collectionModel.value!!, username, if (readOnly) CollectionAccessLevel.ReadOnly else CollectionAccessLevel.ReadWrite)
|
||||
frag.show(childFragmentManager, null)
|
||||
}
|
||||
.setNegativeButton(android.R.string.no) { _, _ -> }
|
||||
@ -113,7 +114,12 @@ class CollectionMembersFragment : Fragment() {
|
||||
}
|
||||
}
|
||||
|
||||
class AddMemberFragment(private val accountHolder: AccountHolder, private val cachedCollection: CachedCollection, private val username: String, private val accessLevel: CollectionAccessLevel) : DialogFragment() {
|
||||
class AddMemberFragment : DialogFragment() {
|
||||
private lateinit var accountHolder: AccountHolder
|
||||
private lateinit var cachedCollection: CachedCollection
|
||||
private lateinit var username: String
|
||||
private lateinit var accessLevel: CollectionAccessLevel
|
||||
|
||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||
val progress = ProgressDialog(context)
|
||||
progress.setTitle(R.string.collection_members_adding)
|
||||
@ -173,4 +179,16 @@ class AddMemberFragment(private val accountHolder: AccountHolder, private val ca
|
||||
.setPositiveButton(android.R.string.yes) { _, _ -> }.show()
|
||||
dismiss()
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun newInstance(accountHolder: AccountHolder, cachedCollection: CachedCollection,
|
||||
username: String, accessLevel: CollectionAccessLevel): AddMemberFragment {
|
||||
val ret = AddMemberFragment()
|
||||
ret.accountHolder = accountHolder
|
||||
ret.cachedCollection = cachedCollection
|
||||
ret.username = username
|
||||
ret.accessLevel = accessLevel
|
||||
return ret
|
||||
}
|
||||
}
|
||||
}
|
@ -15,6 +15,7 @@ import com.etebase.client.exceptions.EtebaseException
|
||||
import com.etesync.syncadapter.CachedCollection
|
||||
import com.etesync.syncadapter.Constants
|
||||
import com.etesync.syncadapter.R
|
||||
import com.etesync.syncadapter.log.Logger
|
||||
import com.etesync.syncadapter.resource.LocalCalendar
|
||||
import com.etesync.syncadapter.syncadapter.requestSync
|
||||
import com.etesync.syncadapter.ui.BaseActivity
|
||||
@ -23,12 +24,15 @@ import org.jetbrains.anko.doAsync
|
||||
import org.jetbrains.anko.uiThread
|
||||
import yuku.ambilwarna.AmbilWarnaDialog
|
||||
|
||||
class EditCollectionFragment(private val cachedCollection: CachedCollection, private val isCreating: Boolean = false) : Fragment() {
|
||||
class EditCollectionFragment : Fragment() {
|
||||
private val model: AccountViewModel by activityViewModels()
|
||||
private val collectionModel: CollectionViewModel by activityViewModels()
|
||||
private val itemsModel: ItemsViewModel by activityViewModels()
|
||||
private val loadingModel: LoadingViewModel by viewModels()
|
||||
|
||||
private lateinit var cachedCollection: CachedCollection
|
||||
private var isCreating: Boolean = false
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||
val ret = inflater.inflate(R.layout.activity_create_collection, container, false)
|
||||
setHasOptionsMenu(true)
|
||||
@ -47,7 +51,7 @@ class EditCollectionFragment(private val cachedCollection: CachedCollection, pri
|
||||
cachedCollection.let {
|
||||
var titleId: Int = R.string.create_calendar
|
||||
if (isCreating) {
|
||||
when (cachedCollection.meta.collectionType) {
|
||||
when (cachedCollection.collectionType) {
|
||||
Constants.ETEBASE_TYPE_CALENDAR -> {
|
||||
titleId = R.string.create_calendar
|
||||
}
|
||||
@ -75,7 +79,7 @@ class EditCollectionFragment(private val cachedCollection: CachedCollection, pri
|
||||
desc.setText(meta.description)
|
||||
|
||||
val colorSquare = v.findViewById<View>(R.id.color)
|
||||
when (cachedCollection.meta.collectionType) {
|
||||
when (cachedCollection.collectionType) {
|
||||
Constants.ETEBASE_TYPE_CALENDAR -> {
|
||||
title.setHint(R.string.create_calendar_display_name_hint)
|
||||
|
||||
@ -167,11 +171,14 @@ class EditCollectionFragment(private val cachedCollection: CachedCollection, pri
|
||||
activity?.finish()
|
||||
} catch (e: EtebaseException) {
|
||||
uiThread {
|
||||
AlertDialog.Builder(requireContext())
|
||||
.setIcon(R.drawable.ic_info_dark)
|
||||
.setTitle(R.string.exception)
|
||||
.setMessage(e.localizedMessage)
|
||||
.setPositiveButton(android.R.string.yes) { _, _ -> }.show()
|
||||
Logger.log.warning(e.localizedMessage)
|
||||
context?.let { context ->
|
||||
AlertDialog.Builder(context)
|
||||
.setIcon(R.drawable.ic_info_dark)
|
||||
.setTitle(R.string.exception)
|
||||
.setMessage(e.localizedMessage)
|
||||
.setPositiveButton(android.R.string.yes) { _, _ -> }.show()
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
uiThread {
|
||||
@ -200,7 +207,7 @@ class EditCollectionFragment(private val cachedCollection: CachedCollection, pri
|
||||
meta.mtime = System.currentTimeMillis()
|
||||
|
||||
if (ok) {
|
||||
when (meta.collectionType) {
|
||||
when (cachedCollection.collectionType) {
|
||||
Constants.ETEBASE_TYPE_CALENDAR, Constants.ETEBASE_TYPE_TASKS -> {
|
||||
val view = v.findViewById<View>(R.id.color)
|
||||
val color = (view.background as ColorDrawable).color
|
||||
@ -231,11 +238,14 @@ class EditCollectionFragment(private val cachedCollection: CachedCollection, pri
|
||||
}
|
||||
} catch (e: EtebaseException) {
|
||||
uiThread {
|
||||
AlertDialog.Builder(requireContext())
|
||||
.setIcon(R.drawable.ic_info_dark)
|
||||
.setTitle(R.string.exception)
|
||||
.setMessage(e.localizedMessage)
|
||||
.setPositiveButton(android.R.string.yes) { _, _ -> }.show()
|
||||
val context = context
|
||||
if (context != null) {
|
||||
AlertDialog.Builder(requireContext())
|
||||
.setIcon(R.drawable.ic_info_dark)
|
||||
.setTitle(R.string.exception)
|
||||
.setMessage(e.localizedMessage)
|
||||
.setPositiveButton(android.R.string.yes) { _, _ -> }.show()
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
uiThread {
|
||||
@ -256,4 +266,13 @@ class EditCollectionFragment(private val cachedCollection: CachedCollection, pri
|
||||
}
|
||||
collectionModel.loadCollection(model.value!!, col.uid)
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun newInstance(cachedCollection: CachedCollection, isCreating: Boolean = false): EditCollectionFragment {
|
||||
val ret = EditCollectionFragment()
|
||||
ret.cachedCollection = cachedCollection
|
||||
ret.isCreating = isCreating
|
||||
return ret
|
||||
}
|
||||
}
|
||||
}
|
@ -57,14 +57,14 @@ class ImportCollectionFragment : Fragment() {
|
||||
img.setImageResource(R.drawable.ic_account_circle_white)
|
||||
text.setText(R.string.import_button_local)
|
||||
card.setOnClickListener {
|
||||
if (cachedCollection.meta.collectionType == Constants.ETEBASE_TYPE_CALENDAR) {
|
||||
if (cachedCollection.collectionType == Constants.ETEBASE_TYPE_CALENDAR) {
|
||||
parentFragmentManager.commit {
|
||||
replace(R.id.fragment_container, LocalCalendarImportFragment(accountHolder.account, cachedCollection.col.uid))
|
||||
replace(R.id.fragment_container, LocalCalendarImportFragment.newInstance(accountHolder.account, cachedCollection.col.uid))
|
||||
addToBackStack(null)
|
||||
}
|
||||
} else if (cachedCollection.meta.collectionType == Constants.ETEBASE_TYPE_ADDRESS_BOOK) {
|
||||
} else if (cachedCollection.collectionType == Constants.ETEBASE_TYPE_ADDRESS_BOOK) {
|
||||
parentFragmentManager.commit {
|
||||
replace(R.id.fragment_container, LocalContactImportFragment(accountHolder.account, cachedCollection.col.uid))
|
||||
replace(R.id.fragment_container, LocalContactImportFragment.newInstance(accountHolder.account, cachedCollection.col.uid))
|
||||
addToBackStack(null)
|
||||
}
|
||||
}
|
||||
@ -72,7 +72,7 @@ class ImportCollectionFragment : Fragment() {
|
||||
(activity as? BaseActivity?)?.supportActionBar?.setTitle(R.string.import_select_account)
|
||||
}
|
||||
|
||||
if (collectionModel.value!!.meta.collectionType == Constants.ETEBASE_TYPE_TASKS) {
|
||||
if (collectionModel.value!!.collectionType == Constants.ETEBASE_TYPE_TASKS) {
|
||||
card.visibility = View.GONE
|
||||
}
|
||||
}
|
||||
|
@ -18,7 +18,7 @@ class InvitationsActivity : BaseActivity() {
|
||||
|
||||
account = intent.extras!!.getParcelable(EXTRA_ACCOUNT)!!
|
||||
|
||||
setContentView(R.layout.etebase_collection_activity)
|
||||
setContentView(R.layout.etebase_fragment_activity)
|
||||
|
||||
if (savedInstanceState == null) {
|
||||
model.loadAccount(this, account)
|
||||
|
@ -5,7 +5,9 @@ import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.*
|
||||
import android.widget.AdapterView
|
||||
import android.widget.ArrayAdapter
|
||||
import android.widget.TextView
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.fragment.app.ListFragment
|
||||
import androidx.fragment.app.activityViewModels
|
||||
@ -19,6 +21,7 @@ import com.etebase.client.FetchOptions
|
||||
import com.etebase.client.SignedInvitation
|
||||
import com.etebase.client.Utils
|
||||
import com.etesync.syncadapter.R
|
||||
import com.etesync.syncadapter.syncadapter.requestSync
|
||||
import org.jetbrains.anko.doAsync
|
||||
import org.jetbrains.anko.uiThread
|
||||
import java.util.*
|
||||
@ -88,6 +91,10 @@ class InvitationsListFragment : ListFragment(), AdapterView.OnItemClickListener
|
||||
}
|
||||
.setPositiveButton(R.string.invitations_accept) { dialogInterface, i ->
|
||||
invitationsModel.accept(model.value!!, invitation)
|
||||
val applicationContext = activity?.applicationContext
|
||||
if (applicationContext != null) {
|
||||
requestSync(applicationContext, model.value!!.account)
|
||||
}
|
||||
}
|
||||
.show()
|
||||
return
|
||||
|
@ -28,11 +28,14 @@ import java.util.*
|
||||
import java.util.concurrent.Future
|
||||
|
||||
|
||||
class ItemRevisionsListFragment(private val cachedCollection: CachedCollection, private val cachedItem: CachedItem) : ListFragment(), AdapterView.OnItemClickListener {
|
||||
class ItemRevisionsListFragment : ListFragment(), AdapterView.OnItemClickListener {
|
||||
private val model: AccountViewModel by activityViewModels()
|
||||
private val revisionsModel: RevisionsViewModel by viewModels()
|
||||
private var state: Parcelable? = null
|
||||
|
||||
private lateinit var cachedCollection: CachedCollection
|
||||
private lateinit var cachedItem: CachedItem
|
||||
|
||||
private var emptyTextView: TextView? = null
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||
@ -83,7 +86,7 @@ class ItemRevisionsListFragment(private val cachedCollection: CachedCollection,
|
||||
override fun onItemClick(parent: AdapterView<*>, view: View, position: Int, id: Long) {
|
||||
val item = listAdapter?.getItem(position) as CachedItem
|
||||
activity?.supportFragmentManager?.commit {
|
||||
replace(R.id.fragment_container, CollectionItemFragment(item))
|
||||
replace(R.id.fragment_container, CollectionItemFragment.newInstance(item))
|
||||
addToBackStack(null)
|
||||
}
|
||||
}
|
||||
@ -95,9 +98,9 @@ class ItemRevisionsListFragment(private val cachedCollection: CachedCollection,
|
||||
if (v == null)
|
||||
v = LayoutInflater.from(context).inflate(R.layout.journal_viewer_list_item, parent, false)!!
|
||||
|
||||
val item = getItem(position)
|
||||
val item = getItem(position)!!
|
||||
|
||||
setItemView(v, cachedCollection.meta.collectionType, item)
|
||||
setItemView(v, cachedCollection.collectionType, item)
|
||||
|
||||
/* FIXME: handle entry error:
|
||||
val entryError = data.select(EntryErrorEntity::class.java).where(EntryErrorEntity.ENTRY.eq(entryEntity)).limit(1).get().firstOrNull()
|
||||
@ -110,6 +113,15 @@ class ItemRevisionsListFragment(private val cachedCollection: CachedCollection,
|
||||
return v
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun newInstance(cachedCollection: CachedCollection, cachedItem: CachedItem): ItemRevisionsListFragment {
|
||||
val ret = ItemRevisionsListFragment()
|
||||
ret.cachedCollection = cachedCollection
|
||||
ret.cachedItem = cachedItem
|
||||
return ret
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -71,7 +71,7 @@ class ListEntriesFragment : ListFragment(), AdapterView.OnItemClickListener {
|
||||
override fun onItemClick(parent: AdapterView<*>, view: View, position: Int, id: Long) {
|
||||
val item = listAdapter?.getItem(position) as CachedItem
|
||||
activity?.supportFragmentManager?.commit {
|
||||
replace(R.id.fragment_container, CollectionItemFragment(item))
|
||||
replace(R.id.fragment_container, CollectionItemFragment.newInstance(item))
|
||||
addToBackStack(EditCollectionFragment::class.java.name)
|
||||
}
|
||||
}
|
||||
@ -83,9 +83,9 @@ class ListEntriesFragment : ListFragment(), AdapterView.OnItemClickListener {
|
||||
if (v == null)
|
||||
v = LayoutInflater.from(context).inflate(R.layout.journal_viewer_list_item, parent, false)!!
|
||||
|
||||
val item = getItem(position)
|
||||
val item = getItem(position)!!
|
||||
|
||||
setItemView(v, cachedCollection.meta.collectionType, item)
|
||||
setItemView(v, cachedCollection.collectionType, item)
|
||||
|
||||
/* FIXME: handle entry error:
|
||||
val entryError = data.select(EntryErrorEntity::class.java).where(EntryErrorEntity.ENTRY.eq(entryEntity)).limit(1).get().firstOrNull()
|
||||
|
@ -16,9 +16,10 @@ import androidx.fragment.app.activityViewModels
|
||||
import androidx.fragment.app.commit
|
||||
import androidx.fragment.app.viewModels
|
||||
import com.etebase.client.Collection
|
||||
import com.etebase.client.CollectionMetadata
|
||||
import com.etebase.client.FetchOptions
|
||||
import com.etebase.client.ItemMetadata
|
||||
import com.etebase.client.exceptions.EtebaseException
|
||||
import com.etesync.syncadapter.Constants.*
|
||||
import com.etesync.syncadapter.R
|
||||
import com.etesync.syncadapter.syncadapter.requestSync
|
||||
import com.etesync.syncadapter.ui.BaseActivity
|
||||
@ -34,7 +35,7 @@ class NewAccountWizardActivity : BaseActivity() {
|
||||
|
||||
account = intent.extras!!.getParcelable(EXTRA_ACCOUNT)!!
|
||||
|
||||
setContentView(R.layout.etebase_collection_activity)
|
||||
setContentView(R.layout.etebase_fragment_activity)
|
||||
|
||||
if (savedInstanceState == null) {
|
||||
setTitle(R.string.account_wizard_collections_title)
|
||||
@ -106,7 +107,7 @@ class WizardCheckFragment : Fragment() {
|
||||
loadingModel.setLoading(true)
|
||||
doAsync {
|
||||
try {
|
||||
val collections = colMgr.list(FetchOptions().limit(1))
|
||||
val collections = colMgr.list(COLLECTION_TYPES, FetchOptions().limit(1))
|
||||
uiThread {
|
||||
if (collections.data.size > 0) {
|
||||
activity?.finish()
|
||||
@ -119,9 +120,6 @@ class WizardCheckFragment : Fragment() {
|
||||
} catch (e: Exception) {
|
||||
uiThread {
|
||||
reportErrorHelper(requireContext(), e)
|
||||
}
|
||||
} finally {
|
||||
uiThread {
|
||||
loadingModel.setLoading(false)
|
||||
}
|
||||
}
|
||||
@ -175,16 +173,17 @@ class WizardFragment : Fragment() {
|
||||
doAsync {
|
||||
try {
|
||||
val baseMeta = listOf(
|
||||
Pair("etebase.vcard", "My Contacts"),
|
||||
Pair("etebase.vevent", "My Calendar"),
|
||||
Pair("etebase.vtodo", "My Tasks"),
|
||||
Pair(ETEBASE_TYPE_ADDRESS_BOOK, "My Contacts"),
|
||||
Pair(ETEBASE_TYPE_CALENDAR, "My Calendar"),
|
||||
Pair(ETEBASE_TYPE_TASKS, "My Tasks"),
|
||||
)
|
||||
|
||||
baseMeta.forEach {
|
||||
val meta = CollectionMetadata(it.first, it.second)
|
||||
val meta = ItemMetadata()
|
||||
meta.name = it.second
|
||||
meta.mtime = System.currentTimeMillis()
|
||||
|
||||
val col = colMgr.create(meta, "")
|
||||
val col = colMgr.create(it.first, meta, "")
|
||||
uploadCollection(accountHolder, col)
|
||||
}
|
||||
requestSync(requireContext(), accountHolder.account)
|
||||
|
@ -17,6 +17,7 @@ import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.Button
|
||||
import android.widget.CheckedTextView
|
||||
import android.widget.TextView
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.commit
|
||||
@ -32,10 +33,8 @@ import com.etebase.client.exceptions.EtebaseException
|
||||
import com.etesync.syncadapter.Constants
|
||||
import com.etesync.syncadapter.HttpClient
|
||||
import com.etesync.syncadapter.R
|
||||
import com.etesync.syncadapter.ui.setup.BaseConfigurationFinder
|
||||
import com.etesync.syncadapter.ui.setup.CreateAccountFragment
|
||||
import com.etesync.syncadapter.ui.setup.DetectConfigurationFragment
|
||||
import com.etesync.syncadapter.ui.setup.LoginCredentialsFragment
|
||||
import com.etesync.syncadapter.ui.WebViewActivity
|
||||
import com.etesync.syncadapter.ui.setup.*
|
||||
import com.google.android.material.textfield.TextInputEditText
|
||||
import com.google.android.material.textfield.TextInputLayout
|
||||
import net.cachapa.expandablelayout.ExpandableLayout
|
||||
@ -45,7 +44,9 @@ import org.jetbrains.anko.uiThread
|
||||
import java.net.URI
|
||||
import java.util.concurrent.Future
|
||||
|
||||
class SignupFragment(private val initialUsername: String?, private val initialPassword: String?) : Fragment() {
|
||||
class SignupFragment : Fragment() {
|
||||
internal var initialUsername: String? = null
|
||||
internal var initialPassword: String? = null
|
||||
internal lateinit var editUserName: TextInputLayout
|
||||
internal lateinit var editEmail: TextInputLayout
|
||||
internal lateinit var editPassword: TextInputLayout
|
||||
@ -62,6 +63,9 @@ class SignupFragment(private val initialUsername: String?, private val initialPa
|
||||
editPassword = v.findViewById(R.id.url_password)
|
||||
showAdvanced = v.findViewById(R.id.show_advanced)
|
||||
customServer = v.findViewById(R.id.custom_server)
|
||||
v.findViewById<TextView>(R.id.trial_notice).setOnClickListener {
|
||||
WebViewActivity.openUrl(requireContext(), Constants.pricing)
|
||||
}
|
||||
|
||||
if (savedInstanceState == null) {
|
||||
editUserName.editText?.setText(initialUsername ?: "")
|
||||
@ -71,7 +75,7 @@ class SignupFragment(private val initialUsername: String?, private val initialPa
|
||||
val login = v.findViewById<Button>(R.id.login)
|
||||
login.setOnClickListener {
|
||||
parentFragmentManager.commit {
|
||||
replace(android.R.id.content, LoginCredentialsFragment(editUserName.editText?.text.toString(), editPassword.editText?.text.toString()))
|
||||
replace(android.R.id.content, LoginCredentialsFragment.newInstance(editUserName.editText?.text.toString(), editPassword.editText?.text.toString()))
|
||||
}
|
||||
}
|
||||
|
||||
@ -79,7 +83,7 @@ class SignupFragment(private val initialUsername: String?, private val initialPa
|
||||
createAccount.setOnClickListener {
|
||||
val credentials = validateData()
|
||||
if (credentials != null) {
|
||||
SignupDoFragment(credentials).show(requireFragmentManager(), null)
|
||||
SignupDoFragment.newInstance(credentials).show(requireFragmentManager(), null)
|
||||
}
|
||||
}
|
||||
|
||||
@ -144,13 +148,24 @@ class SignupFragment(private val initialUsername: String?, private val initialPa
|
||||
|
||||
return if (valid) SignupCredentials(uri, userName, email, password) else null
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun newInstance(initialUsername: String?, initialPassword: String?): SignupFragment {
|
||||
val ret = SignupFragment()
|
||||
ret.initialUsername = initialUsername
|
||||
ret.initialPassword = initialPassword
|
||||
return ret
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
class SignupDoFragment(private val signupCredentials: SignupCredentials) : DialogFragment() {
|
||||
class SignupDoFragment: DialogFragment() {
|
||||
private val model: ConfigurationViewModel by viewModels()
|
||||
|
||||
private lateinit var signupCredentials: SignupCredentials
|
||||
|
||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||
val progress = ProgressDialog(activity)
|
||||
progress.setTitle(R.string.setting_up_encryption)
|
||||
@ -182,10 +197,18 @@ class SignupDoFragment(private val signupCredentials: SignupCredentials) : Dialo
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun newInstance(signupCredentials: SignupCredentials): SignupDoFragment {
|
||||
val ret = SignupDoFragment()
|
||||
ret.signupCredentials = signupCredentials
|
||||
return ret
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class ConfigurationViewModel : ViewModel() {
|
||||
private val account = MutableLiveData<BaseConfigurationFinder.Configuration>()
|
||||
val account = MutableLiveData<BaseConfigurationFinder.Configuration>()
|
||||
private var asyncTask: Future<Unit>? = null
|
||||
|
||||
fun signup(context: Context, credentials: SignupCredentials) {
|
||||
@ -216,6 +239,34 @@ class ConfigurationViewModel : ViewModel() {
|
||||
}
|
||||
}
|
||||
|
||||
// We just need it for the migration - maybe merge it with login later on
|
||||
fun login(context: Context, credentials: LoginCredentials) {
|
||||
asyncTask = doAsync {
|
||||
val httpClient = HttpClient.Builder(context).build().okHttpClient
|
||||
val uri = credentials.uri ?: URI(Constants.etebaseServiceUrl)
|
||||
var etebaseSession: String? = null
|
||||
var exception: Throwable? = null
|
||||
try {
|
||||
val client = Client.create(httpClient, uri.toString())
|
||||
val etebase = Account.login(client, credentials.userName, credentials.password)
|
||||
etebaseSession = etebase.save(null)
|
||||
} catch (e: EtebaseException) {
|
||||
exception = e
|
||||
}
|
||||
|
||||
uiThread {
|
||||
account.value = BaseConfigurationFinder.Configuration(
|
||||
uri,
|
||||
credentials.userName,
|
||||
etebaseSession,
|
||||
null,
|
||||
null,
|
||||
exception
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun cancelLoad() {
|
||||
asyncTask?.cancel(true)
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ import android.content.DialogInterface
|
||||
import android.os.Bundle
|
||||
import android.view.*
|
||||
import android.widget.TextView
|
||||
import android.widget.Toast
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.activityViewModels
|
||||
@ -68,7 +69,7 @@ class ViewCollectionFragment : Fragment() {
|
||||
|
||||
val colorSquare = container.findViewById<View>(R.id.color)
|
||||
val color = LocalCalendar.parseColor(meta.color)
|
||||
when (meta.collectionType) {
|
||||
when (cachedCollection.collectionType) {
|
||||
Constants.ETEBASE_TYPE_CALENDAR -> {
|
||||
colorSquare.setBackgroundColor(color)
|
||||
}
|
||||
@ -108,13 +109,17 @@ class ViewCollectionFragment : Fragment() {
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
val cachedCollection = collectionModel.value!!
|
||||
val cachedCollection = collectionModel.value
|
||||
if (cachedCollection == null) {
|
||||
Toast.makeText(context, R.string.loading_error_title, Toast.LENGTH_LONG).show()
|
||||
return super.onOptionsItemSelected(item)
|
||||
}
|
||||
|
||||
when (item.itemId) {
|
||||
R.id.on_edit -> {
|
||||
if (cachedCollection.col.accessLevel == CollectionAccessLevel.Admin) {
|
||||
parentFragmentManager.commit {
|
||||
replace(R.id.fragment_container, EditCollectionFragment(cachedCollection))
|
||||
replace(R.id.fragment_container, EditCollectionFragment.newInstance(cachedCollection))
|
||||
addToBackStack(EditCollectionFragment::class.java.name)
|
||||
}
|
||||
} else {
|
||||
|
@ -46,13 +46,13 @@ class ImportActivity : BaseActivity(), SelectImportMethod, DialogInterface {
|
||||
if (info.enumType == CollectionInfo.Type.CALENDAR) {
|
||||
supportFragmentManager.beginTransaction()
|
||||
.replace(android.R.id.content,
|
||||
LocalCalendarImportFragment.newInstance(account, info))
|
||||
LocalCalendarImportFragment.newInstance(account, info.uid!!))
|
||||
.addToBackStack(LocalCalendarImportFragment::class.java.name)
|
||||
.commit()
|
||||
} else if (info.enumType == CollectionInfo.Type.ADDRESS_BOOK) {
|
||||
supportFragmentManager.beginTransaction()
|
||||
.replace(android.R.id.content,
|
||||
LocalContactImportFragment.newInstance(account, info))
|
||||
LocalContactImportFragment.newInstance(account, info.uid!!))
|
||||
.addToBackStack(LocalContactImportFragment::class.java.name)
|
||||
.commit()
|
||||
}
|
||||
|
@ -35,7 +35,10 @@ import java.io.InputStream
|
||||
import java.io.InputStreamReader
|
||||
|
||||
|
||||
class ImportFragment(private val account: Account, private val uid: String, private val enumType: CollectionInfo.Type) : DialogFragment() {
|
||||
class ImportFragment : DialogFragment() {
|
||||
private lateinit var account: Account
|
||||
private lateinit var uid: String
|
||||
private lateinit var enumType: CollectionInfo.Type
|
||||
|
||||
private var inputStream: InputStream? = null
|
||||
|
||||
@ -234,9 +237,10 @@ class ImportFragment(private val account: Account, private val uid: String, priv
|
||||
|
||||
val localCalendar: LocalCalendar?
|
||||
try {
|
||||
localCalendar = LocalCalendar.findByName(account, provider, LocalCalendar.Factory, uid!!)
|
||||
localCalendar = LocalCalendar.findByName(account, provider, LocalCalendar.Factory, uid)
|
||||
if (localCalendar == null) {
|
||||
throw FileNotFoundException("Failed to load local resource.")
|
||||
result.e = FileNotFoundException("Failed to load local resource.")
|
||||
return result
|
||||
}
|
||||
} catch (e: CalendarStorageException) {
|
||||
Logger.log.info("Fail" + e.localizedMessage)
|
||||
@ -292,9 +296,10 @@ class ImportFragment(private val account: Account, private val uid: String, priv
|
||||
provider?.let {
|
||||
val localTaskList: LocalTaskList?
|
||||
try {
|
||||
localTaskList = LocalTaskList.findByName(account, it, LocalTaskList.Factory, uid!!)
|
||||
localTaskList = LocalTaskList.findByName(account, it, LocalTaskList.Factory, uid)
|
||||
if (localTaskList == null) {
|
||||
throw FileNotFoundException("Failed to load local resource.")
|
||||
result.e = FileNotFoundException("Failed to load local resource.")
|
||||
return result
|
||||
}
|
||||
} catch (e: FileNotFoundException) {
|
||||
Logger.log.info("Fail" + e.localizedMessage)
|
||||
@ -341,9 +346,10 @@ class ImportFragment(private val account: Account, private val uid: String, priv
|
||||
return result
|
||||
}
|
||||
|
||||
val localAddressBook = LocalAddressBook.findByUid(context, provider, account, uid!!)
|
||||
val localAddressBook = LocalAddressBook.findByUid(context, provider, account, uid)
|
||||
if (localAddressBook == null) {
|
||||
throw FileNotFoundException("Failed to load local address book.")
|
||||
result.e = FileNotFoundException("Failed to load local address book.")
|
||||
return result
|
||||
}
|
||||
|
||||
for (contact in contacts.filter { contact -> !contact.group }) {
|
||||
@ -432,17 +438,25 @@ class ImportFragment(private val account: Account, private val uid: String, priv
|
||||
private val TAG_PROGRESS_MAX = "progressMax"
|
||||
|
||||
fun newInstance(account: Account, info: CollectionInfo): ImportFragment {
|
||||
return ImportFragment(account, info.uid!!, info.enumType!!)
|
||||
val ret = ImportFragment()
|
||||
ret.account = account
|
||||
ret.uid = info.uid!!
|
||||
ret.enumType = info.enumType!!
|
||||
return ret
|
||||
}
|
||||
|
||||
fun newInstance(account: Account, cachedCollection: CachedCollection): ImportFragment {
|
||||
val enumType = when (cachedCollection.meta.collectionType) {
|
||||
val enumType = when (cachedCollection.collectionType) {
|
||||
ETEBASE_TYPE_CALENDAR -> CollectionInfo.Type.CALENDAR
|
||||
ETEBASE_TYPE_TASKS -> CollectionInfo.Type.TASKS
|
||||
ETEBASE_TYPE_ADDRESS_BOOK -> CollectionInfo.Type.ADDRESS_BOOK
|
||||
else -> throw Exception("Got unsupported collection type")
|
||||
}
|
||||
return ImportFragment(account, cachedCollection.col.uid, enumType)
|
||||
val ret = ImportFragment()
|
||||
ret.account = account
|
||||
ret.uid = cachedCollection.col.uid
|
||||
ret.enumType = enumType
|
||||
return ret
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -23,7 +23,10 @@ import com.etesync.syncadapter.resource.LocalCalendar
|
||||
import com.etesync.syncadapter.resource.LocalEvent
|
||||
|
||||
|
||||
class LocalCalendarImportFragment(private val account: Account, private val uid: String) : ListFragment() {
|
||||
class LocalCalendarImportFragment : ListFragment() {
|
||||
private lateinit var account: Account
|
||||
private lateinit var uid: String
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
retainInstance = true
|
||||
@ -249,8 +252,11 @@ class LocalCalendarImportFragment(private val account: Account, private val uid:
|
||||
|
||||
companion object {
|
||||
|
||||
fun newInstance(account: Account, info: CollectionInfo): LocalCalendarImportFragment {
|
||||
return LocalCalendarImportFragment(account, info.uid!!)
|
||||
fun newInstance(account: Account, uid: String): LocalCalendarImportFragment {
|
||||
val ret = LocalCalendarImportFragment()
|
||||
ret.account = account
|
||||
ret.uid = uid
|
||||
return ret
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -31,7 +31,10 @@ import com.etesync.syncadapter.resource.LocalGroup
|
||||
import java.util.*
|
||||
|
||||
|
||||
class LocalContactImportFragment(private val account: Account, private val uid: String) : Fragment() {
|
||||
class LocalContactImportFragment : Fragment() {
|
||||
private lateinit var account: Account
|
||||
private lateinit var uid: String
|
||||
|
||||
private var recyclerView: RecyclerView? = null
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
@ -316,8 +319,11 @@ class LocalContactImportFragment(private val account: Account, private val uid:
|
||||
|
||||
companion object {
|
||||
|
||||
fun newInstance(account: Account, info: CollectionInfo): LocalContactImportFragment {
|
||||
return LocalContactImportFragment(account, info.uid!!)
|
||||
fun newInstance(account: Account, uid: String): LocalContactImportFragment {
|
||||
val ret = LocalContactImportFragment()
|
||||
ret.account = account
|
||||
ret.uid = uid
|
||||
return ret
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -40,7 +40,7 @@ class ResultFragment : DialogFragment() {
|
||||
// dismiss
|
||||
}
|
||||
.setPositiveButton(android.R.string.yes) { dialog, which ->
|
||||
ACRA.getErrorReporter().handleSilentException(result!!.e)
|
||||
ACRA.getErrorReporter().handleException(result!!.e)
|
||||
}
|
||||
.create()
|
||||
} else {
|
||||
|
@ -23,8 +23,10 @@ import com.etesync.syncadapter.R
|
||||
import com.etesync.syncadapter.log.Logger
|
||||
import com.etesync.syncadapter.ui.DebugInfoActivity
|
||||
import com.etesync.syncadapter.ui.setup.BaseConfigurationFinder.Configuration
|
||||
import org.jetbrains.anko.doAsync
|
||||
import org.jetbrains.anko.uiThread
|
||||
|
||||
class DetectConfigurationFragment : DialogFragment(), LoaderManager.LoaderCallbacks<Configuration> {
|
||||
class DetectConfigurationFragment : DialogFragment() {
|
||||
|
||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||
val progress = ProgressDialog(activity)
|
||||
@ -40,14 +42,22 @@ class DetectConfigurationFragment : DialogFragment(), LoaderManager.LoaderCallba
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
Logger.log.fine("DetectConfigurationFragment: loading")
|
||||
loaderManager.initLoader(0, arguments, this)
|
||||
|
||||
if (savedInstanceState == null) {
|
||||
findConfiguration(requireArguments().getParcelable(ARG_LOGIN_CREDENTIALS)!!)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateLoader(id: Int, args: Bundle?): Loader<Configuration> {
|
||||
return ServerConfigurationLoader(requireContext(), args!!.getParcelable(ARG_LOGIN_CREDENTIALS) as LoginCredentials)
|
||||
private fun findConfiguration(credentials: LoginCredentials) {
|
||||
doAsync {
|
||||
val data = BaseConfigurationFinder(requireContext(), credentials).findInitialConfiguration()
|
||||
uiThread {
|
||||
onLoadFinished(data)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onLoadFinished(loader: Loader<Configuration>, data: Configuration?) {
|
||||
private fun onLoadFinished(data: Configuration?) {
|
||||
if (data != null) {
|
||||
if (data.isFailed) {
|
||||
Logger.log.warning("Failed login configuration ${data.error?.localizedMessage}")
|
||||
@ -75,13 +85,10 @@ class DetectConfigurationFragment : DialogFragment(), LoaderManager.LoaderCallba
|
||||
dismissAllowingStateLoss()
|
||||
}
|
||||
|
||||
override fun onLoaderReset(loader: Loader<Configuration>) {}
|
||||
|
||||
|
||||
class NothingDetectedFragment : DialogFragment() {
|
||||
|
||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||
return AlertDialog.Builder(activity!!)
|
||||
return AlertDialog.Builder(requireActivity())
|
||||
.setTitle(R.string.setting_up_encryption)
|
||||
.setIcon(R.drawable.ic_error_dark)
|
||||
.setMessage(requireArguments().getString(KEY_LOGS))
|
||||
@ -104,17 +111,6 @@ class DetectConfigurationFragment : DialogFragment(), LoaderManager.LoaderCallba
|
||||
}
|
||||
}
|
||||
|
||||
internal class ServerConfigurationLoader(context: Context, val credentials: LoginCredentials) : AsyncTaskLoader<Configuration>(context) {
|
||||
|
||||
override fun onStartLoading() {
|
||||
forceLoad()
|
||||
}
|
||||
|
||||
override fun loadInBackground(): Configuration? {
|
||||
return BaseConfigurationFinder(context, credentials).findInitialConfiguration()
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
protected val ARG_LOGIN_CREDENTIALS = "credentials"
|
||||
|
||||
|
@ -30,7 +30,7 @@ class LoginActivity : BaseActivity() {
|
||||
if (savedInstanceState == null)
|
||||
// first call, add fragment
|
||||
supportFragmentManager.beginTransaction()
|
||||
.replace(android.R.id.content, LoginCredentialsFragment(null, null))
|
||||
.replace(android.R.id.content, LoginCredentialsFragment())
|
||||
.commit()
|
||||
|
||||
}
|
||||
|
@ -47,7 +47,7 @@ class LoginCredentialsChangeFragment : DialogFragment(), LoaderManager.LoaderCal
|
||||
}
|
||||
|
||||
override fun onCreateLoader(id: Int, args: Bundle?): Loader<Configuration> {
|
||||
return ServerConfigurationLoader(context!!, args!!.getParcelable(ARG_LOGIN_CREDENTIALS) as LoginCredentials)
|
||||
return ServerConfigurationLoader(requireContext(), (args!!.getParcelable(ARG_LOGIN_CREDENTIALS) as LoginCredentials?)!!)
|
||||
}
|
||||
|
||||
override fun onLoadFinished(loader: Loader<Configuration>, data: Configuration?) {
|
||||
|
@ -29,13 +29,16 @@ import net.cachapa.expandablelayout.ExpandableLayout
|
||||
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
|
||||
import java.net.URI
|
||||
|
||||
class LoginCredentialsFragment(private val initialUsername: String?, private val initialPassword: String?) : Fragment() {
|
||||
class LoginCredentialsFragment : Fragment() {
|
||||
internal lateinit var editUserName: EditText
|
||||
internal lateinit var editUrlPassword: TextInputLayout
|
||||
|
||||
internal lateinit var showAdvanced: CheckedTextView
|
||||
internal lateinit var customServer: EditText
|
||||
|
||||
internal var initialUsername: String? = null
|
||||
internal var initialPassword: String? = null
|
||||
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||
val v = inflater.inflate(R.layout.login_credentials_fragment, container, false)
|
||||
@ -53,7 +56,7 @@ class LoginCredentialsFragment(private val initialUsername: String?, private val
|
||||
val createAccount = v.findViewById<View>(R.id.create_account) as Button
|
||||
createAccount.setOnClickListener {
|
||||
parentFragmentManager.commit {
|
||||
replace(android.R.id.content, SignupFragment(editUserName.text.toString(), editUrlPassword.editText?.text.toString()))
|
||||
replace(android.R.id.content, SignupFragment.newInstance(editUserName.text.toString(), editUrlPassword.editText?.text.toString()))
|
||||
}
|
||||
}
|
||||
|
||||
@ -119,4 +122,13 @@ class LoginCredentialsFragment(private val initialUsername: String?, private val
|
||||
|
||||
return if (valid) LoginCredentials(uri, userName, password) else null
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun newInstance(initialUsername: String?, initialPassword: String?): LoginCredentialsFragment {
|
||||
val ret = LoginCredentialsFragment()
|
||||
ret.initialUsername = initialUsername
|
||||
ret.initialPassword = initialPassword
|
||||
return ret
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -15,15 +15,6 @@ import java.io.IOException
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
|
||||
fun emailSupportsAttachments(context: Context): Boolean {
|
||||
return !arrayOf(
|
||||
"ch.protonmail.android",
|
||||
"de.tutao.tutanota"
|
||||
).any{
|
||||
packageInstalled(context, it)
|
||||
}
|
||||
}
|
||||
|
||||
class EventEmailInvitation constructor(val context: Context, val account: Account) {
|
||||
fun createIntent(event: Event, icsContent: String): Intent? {
|
||||
val intent = Intent(Intent.ACTION_SEND)
|
||||
|
@ -17,6 +17,16 @@
|
||||
android:paddingLeft="12dp"
|
||||
android:paddingRight="12dp">
|
||||
|
||||
<CheckBox
|
||||
android:id="@+id/sync"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:focusable="false"
|
||||
android:focusableInTouchMode="false"
|
||||
android:clickable="false"
|
||||
android:layout_marginRight="4dp"
|
||||
android:visibility="gone"/>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
|
194
app/src/main/res/layout/migrate_v2_collections.xml
Normal file
194
app/src/main/res/layout/migrate_v2_collections.xml
Normal file
@ -0,0 +1,194 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
~ Copyright © 2013 – 2016 Ricki Hirner (bitfire web engineering).
|
||||
~ All rights reserved. This program and the accompanying materials
|
||||
~ are made available under the terms of the GNU Public License v3.0
|
||||
~ which accompanies this distribution, and is available at
|
||||
~ http://www.gnu.org/licenses/gpl.html
|
||||
-->
|
||||
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<ScrollView
|
||||
android:id="@+id/parent"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="@dimen/activity_margin">
|
||||
|
||||
<androidx.cardview.widget.CardView
|
||||
android:id="@+id/carddav"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="8dp"
|
||||
app:cardElevation="8dp"
|
||||
app:cardUseCompatPadding="true">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<androidx.appcompat.widget.Toolbar
|
||||
android:id="@+id/carddav_menu"
|
||||
style="@style/toolbar_style"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:elevation="2dp"
|
||||
android:theme="@style/toolbar_theme"
|
||||
app:navigationIcon="@drawable/ic_people_light"
|
||||
app:title="@string/settings_carddav"
|
||||
tools:ignore="UnusedAttribute" />
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/carddav_refreshing"
|
||||
style="@style/Widget.AppCompat.ProgressBar.Horizontal"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:indeterminate="true"
|
||||
android:visibility="gone" />
|
||||
|
||||
<com.etesync.syncadapter.ui.widget.MaximizedListView
|
||||
android:id="@+id/address_books"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:choiceMode="singleChoice" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</androidx.cardview.widget.CardView>
|
||||
|
||||
<androidx.cardview.widget.CardView
|
||||
android:id="@+id/caldav"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:cardElevation="8dp"
|
||||
app:cardUseCompatPadding="true">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<androidx.appcompat.widget.Toolbar
|
||||
android:id="@+id/caldav_menu"
|
||||
style="@style/toolbar_style"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:elevation="2dp"
|
||||
android:theme="@style/toolbar_theme"
|
||||
app:navigationIcon="@drawable/ic_event_light"
|
||||
app:title="@string/settings_caldav"
|
||||
tools:ignore="UnusedAttribute" />
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/caldav_refreshing"
|
||||
style="@style/Widget.AppCompat.ProgressBar.Horizontal"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:indeterminate="true"
|
||||
android:visibility="gone" />
|
||||
|
||||
<com.etesync.syncadapter.ui.widget.MaximizedListView
|
||||
android:id="@+id/calendars"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:choiceMode="multipleChoice"
|
||||
android:descendantFocusability="beforeDescendants" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</androidx.cardview.widget.CardView>
|
||||
|
||||
<androidx.cardview.widget.CardView
|
||||
android:id="@+id/taskdav"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:cardElevation="8dp"
|
||||
app:cardUseCompatPadding="true">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<androidx.appcompat.widget.Toolbar
|
||||
android:id="@+id/taskdav_menu"
|
||||
style="@style/toolbar_style"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:elevation="2dp"
|
||||
android:theme="@style/toolbar_theme"
|
||||
app:navigationIcon="@drawable/ic_task_light"
|
||||
app:title="@string/settings_taskdav"
|
||||
tools:ignore="UnusedAttribute" />
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/taskdav_refreshing"
|
||||
style="@style/Widget.AppCompat.ProgressBar.Horizontal"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:indeterminate="true"
|
||||
android:visibility="gone" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/taskdav_opentasks_warning"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingLeft="12dp"
|
||||
android:paddingTop="8dp"
|
||||
android:paddingRight="12dp"
|
||||
android:paddingBottom="8dp"
|
||||
android:text="@string/account_tasks_not_showing"
|
||||
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||
android:visibility="gone" />
|
||||
|
||||
<com.etesync.syncadapter.ui.widget.MaximizedListView
|
||||
android:id="@+id/tasklists"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:choiceMode="multipleChoice"
|
||||
android:descendantFocusability="beforeDescendants" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</androidx.cardview.widget.CardView>
|
||||
</LinearLayout>
|
||||
</ScrollView>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/buttons_holder"
|
||||
style="@style/stepper_nav_bar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<Button
|
||||
android:id="@+id/button_skip"
|
||||
style="@style/stepper_nav_button"
|
||||
android:layout_width="0dp"
|
||||
android:layout_weight="1"
|
||||
android:text="@string/skip" />
|
||||
|
||||
<Space
|
||||
style="@style/stepper_nav_button"
|
||||
android:layout_width="0dp"
|
||||
android:layout_weight="1" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/button_create"
|
||||
style="@style/stepper_nav_button"
|
||||
android:layout_width="0dp"
|
||||
android:layout_weight="1"
|
||||
android:text="@string/create" />
|
||||
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
73
app/src/main/res/layout/migrate_v2_wizard_welcome.xml
Normal file
73
app/src/main/res/layout/migrate_v2_wizard_welcome.xml
Normal file
@ -0,0 +1,73 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
~ Copyright © 2013 – 2016 Ricki Hirner (bitfire web engineering).
|
||||
~ All rights reserved. This program and the accompanying materials
|
||||
~ are made available under the terms of the GNU Public License v3.0
|
||||
~ which accompanies this distribution, and is available at
|
||||
~ http://www.gnu.org/licenses/gpl.html
|
||||
-->
|
||||
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical">
|
||||
|
||||
<Space
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="16dp"
|
||||
android:gravity="center"
|
||||
android:text="@string/migrate_v2_wizard_welcome_title"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Large" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="16dp"
|
||||
android:gravity="center"
|
||||
android:text="@string/migrate_v2_wizard_welcome_body" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginHorizontal="16dp"
|
||||
android:gravity="center"
|
||||
android:text="@string/migrate_v2_wizard_welcome_body_extra" />
|
||||
|
||||
<Space
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
style="@style/stepper_nav_bar">
|
||||
|
||||
<Button
|
||||
android:id="@+id/signup"
|
||||
android:layout_width="0dp"
|
||||
android:layout_weight="1"
|
||||
android:text="@string/login_signup"
|
||||
style="@style/stepper_nav_button"/>
|
||||
|
||||
<Space
|
||||
android:layout_width="0dp"
|
||||
android:layout_weight="1"
|
||||
style="@style/stepper_nav_button"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/login"
|
||||
android:layout_width="0dp"
|
||||
android:layout_weight="1"
|
||||
android:text="@string/login_login"
|
||||
style="@style/stepper_nav_button"/>
|
||||
|
||||
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
@ -29,6 +29,15 @@
|
||||
android:text="@string/signup_title"
|
||||
android:layout_marginBottom="14dp"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/trial_notice"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="You are signing up for a free trial. Click here for pricing information."
|
||||
android:background="@color/infoColor"
|
||||
android:padding="14dp"
|
||||
android:layout_marginBottom="14dp"/>
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/user_name"
|
||||
android:layout_width="match_parent"
|
||||
|
@ -29,6 +29,11 @@
|
||||
android:title="@string/invitations_title"
|
||||
app:showAsAction="never"/>
|
||||
|
||||
<item android:id="@+id/migration_v2"
|
||||
android:title="@string/migrate_v2_wizard_welcome_title"
|
||||
android:visible="false"
|
||||
app:showAsAction="never"/>
|
||||
|
||||
<item android:id="@+id/delete_account"
|
||||
android:title="@string/account_delete"
|
||||
app:showAsAction="never"/>
|
||||
|
@ -89,7 +89,7 @@
|
||||
<string name="login_encryption_password">Verschlüsselungs-Passwort</string>
|
||||
<string name="login_encryption_check_password">* Bitte überprüfen Sie das Passwort mehrfach, da es nicht geändert werden kann, sollte es falsch sein.</string>
|
||||
<string name="login_password_required">Passwort benötigt</string>
|
||||
<string name="login_login">Anmeldenm</string>
|
||||
<string name="login_login">Anmelden</string>
|
||||
<string name="login_signup">Registrieren</string>
|
||||
<string name="login_finish">Fertigstellen</string>
|
||||
<string name="login_back">Zurück</string>
|
||||
|
@ -166,7 +166,7 @@
|
||||
<string name="app_settings_force_language">Forcer la langue</string>
|
||||
<string name="sync_calendar_attendees_email_content" formatted="false">Vous avez été invité·e à l\'évènement suivant :
|
||||
\n
|
||||
\n% s
|
||||
\n%s
|
||||
\nQuand : %s
|
||||
\nOù : %s
|
||||
\nParticipants : %s
|
||||
@ -192,4 +192,7 @@
|
||||
<string name="notification_channel_debugging">Débogage</string>
|
||||
<string name="notification_channel_crash_reports">Rapports de plantage</string>
|
||||
<string name="app_name">EteSync</string>
|
||||
<string name="retry">Réessayer</string>
|
||||
<string name="skip">Passer</string>
|
||||
<string name="create">Créer</string>
|
||||
</resources>
|
39
app/src/main/res/values-id/strings.xml
Normal file
39
app/src/main/res/values-id/strings.xml
Normal file
@ -0,0 +1,39 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="about_license_info_no_warranty">Program ini dilengkapi dengan BENAR-BENAR TIDAK ADA GARANSI. Ini adalah perangkat lunak gratis, dan Anda dipersilakan untuk mendistribusikannya dalam kondisi tertentu.</string>
|
||||
<string name="about_license_terms">Persyaratan lisensi</string>
|
||||
<string name="startup_vendor_specific_bugs_open_faq">Buka FAQ</string>
|
||||
<string name="startup_vendor_specific_bugs_message">EteSync telah mendeteksi Anda menggunakan versi Android yang mungkin berisi bug khusus vendor.
|
||||
\nSilakan lihat FAQ untuk informasi lebih lanjut.</string>
|
||||
<string name="startup_vendor_specific_bugs">Potensi Kesalahan dari Vendor</string>
|
||||
<string name="startup_development_version_give_feedback">Berikan umpan balik</string>
|
||||
<string name="startup_development_version_message">Ini adalah versi pengembangan dari EteSync. Ketahuilah bahwa hal-hal yang mungkin tidak bekerja seperti yang diharapkan. Tolong beri kami umpan balik yang konstruktif untuk meningkatkan EteSync.</string>
|
||||
<string name="startup_development_version">Rilis Pratinjau EteSync</string>
|
||||
<string name="startup_dont_show_again">Jangan tampilkan lagi</string>
|
||||
<string name="startup_battery_optimization_disable">Matikan untuk EteSync</string>
|
||||
<string name="startup_battery_optimization">Pengoptimalan Baterai</string>
|
||||
<string name="startup_battery_optimization_message">Android dapat menonaktifkan/mengurangi sinkronisasi EteSync setelah beberapa hari. Untuk mencegah hal ini, matikan pengoptimalan baterai.</string>
|
||||
<string name="tourguide_title">Tahukah kamu\?</string>
|
||||
<string name="crash_email_body">Jika memungkinkan, harap sertakan informasi lain yang relevan seperti apa yang Anda lakukan yang memicu kesalahan ini.</string>
|
||||
<string name="crash_title">EteSync telah rusak!</string>
|
||||
<string name="notification_channel_sync_status_desc">Pesan status informasi seperti ringkasan perubahan sinkronisasi</string>
|
||||
<string name="notification_channel_sync_status">Pesan status</string>
|
||||
<string name="notification_channel_sync_warnings_desc">Masalah sinkronisasi non-fatal seperti perubahan koleksi baca-saja yang diberhentikan</string>
|
||||
<string name="notification_channel_sync_warnings">Peringatan sinkronisasi</string>
|
||||
<string name="notification_channel_sync_errors_desc">Kesalahan penting yang menghentikan sinkronisasi seperti balasan server yang tidak terduga</string>
|
||||
<string name="notification_channel_sync_errors">Kesalahan sinkronisasi</string>
|
||||
<string name="notification_channel_sync">Sinkronisasi</string>
|
||||
<string name="notification_channel_general">Pesan penting lainnya</string>
|
||||
<string name="notification_channel_debugging">Debugging</string>
|
||||
<string name="notification_channel_crash_reports">Laporan Kerusakan</string>
|
||||
<string name="retry">Mencoba kembali</string>
|
||||
<string name="skip">Lewati</string>
|
||||
<string name="create">Buat</string>
|
||||
<string name="send">Kirim</string>
|
||||
<string name="please_wait">Tunggu sebentar…</string>
|
||||
<string name="manage_accounts">Kelola akun</string>
|
||||
<string name="help">Bantuan</string>
|
||||
<string name="address_books_authority_title">Buku alamat</string>
|
||||
<string name="account_title_address_book">Buku alamat EteSync</string>
|
||||
<string name="app_name">EteSync</string>
|
||||
</resources>
|
@ -14,7 +14,7 @@
|
||||
<string name="manage_accounts">Behandle kontoer</string>
|
||||
<string name="please_wait">Vent …</string>
|
||||
<string name="send">Send</string>
|
||||
<string name="notification_channel_crash_reports">Kræsjrapporter</string>
|
||||
<string name="notification_channel_crash_reports">Krasjrapporter</string>
|
||||
<string name="notification_channel_debugging">Avlusing</string>
|
||||
<string name="notification_channel_general">Andre viktige meldinger</string>
|
||||
<string name="notification_channel_sync">Synkronisering</string>
|
||||
@ -25,7 +25,7 @@
|
||||
<string name="notification_channel_sync_status">Statusmeldinger</string>
|
||||
<string name="notification_channel_sync_status_desc">Informative statusmeldinger, som sammendrag av synkroniseringsendringer</string>
|
||||
<!-- Crash -->
|
||||
<string name="crash_title">EteSync har kræsjet!</string>
|
||||
<string name="crash_title">EteSync har krasjet!</string>
|
||||
<string name="crash_message">Send feilmeldingen til utvikleren.</string>
|
||||
<string name="crash_email_subject">EteSync-feilsporingsinfo</string>
|
||||
<string name="crash_email_body">Hvis mulig, ta med annen relevant info, som f.eks. hva du gjorde for å utløse feilen.</string>
|
||||
@ -126,9 +126,9 @@
|
||||
<string name="collection_members_title">Medlemmer</string>
|
||||
<string name="collection_members_list_loading">Laster inn medlemmer …</string>
|
||||
<string name="collection_members_list_empty">Ingen medlemmer</string>
|
||||
<string name="collection_members_add">Legg til medlem</string>
|
||||
<string name="collection_members_add">Inviter bruker</string>
|
||||
<string name="collection_members_add_error">Feil oppstod under tillegging av medlem</string>
|
||||
<string name="collection_members_adding">Legger til medlem</string>
|
||||
<string name="collection_members_adding">Inviterer brukre</string>
|
||||
<string name="trust_fingerprint_title">Kontroller sikkerhetsfingeravtrykk</string>
|
||||
<string name="trust_fingerprint_body">Kontroller %s sitt fingeravtrykk for å bekrefte at krypteringen er sikker.</string>
|
||||
<string name="collection_members_error_user_not_found">Brukeren (%s) finnes ikke</string>
|
||||
@ -179,7 +179,7 @@
|
||||
<string name="login_custom_server_error">Ugyldig nettadresse finnet, glemte du å ta med https://\?</string>
|
||||
<string name="login_toggle_advanced">Vis avanserte innstillinger</string>
|
||||
<string name="login_encryption_password">Krypteringspassord</string>
|
||||
<string name="login_encryption_check_password">* Dobbeltsjekk passordet, siden det ikke kan gjenopprettes hvis det er feil!</string>
|
||||
<string name="login_encryption_check_password">* Husk passordet ditt, det kan ikke gjenopprettes hvis du mister det!</string>
|
||||
<string name="login_encryption_extra_info">Dette passordet bruker til å kryptere dataen din, i motsetning til det forrige, som brukes til å logge inn på tjenesten.
|
||||
\nDu bes om å sette et eget krypteringspassord av sikkerhetsgrunner. For mer info, sjekk ofte stilte spørsmål her: %s</string>
|
||||
<string name="login_password_required">Passord kreves</string>
|
||||
@ -279,7 +279,7 @@
|
||||
<string name="delete_collection_deleting_collection">Sletter samling</string>
|
||||
<!-- JournalViewer -->
|
||||
<string name="journal_entries_list_empty">Samlingen er tom.
|
||||
\n(Kanskje den fortsatt synkroniserer\?)</string>
|
||||
\nKanskje den fortsatt synkroniserer\?</string>
|
||||
<string name="journal_entries_loading">Laster endringsloggsoppføringer …</string>
|
||||
<!-- ExceptionInfoFragment -->
|
||||
<string name="exception">En feil har oppstått.</string>
|
||||
@ -288,7 +288,7 @@
|
||||
<string name="exception_show_details">Vis detaljer</string>
|
||||
<!-- sync errors and DebugInfoActivity -->
|
||||
<string name="debug_info_title">Feilsporingsinfo</string>
|
||||
<string name="debug_info_more_data_shared">Når du klikker på del vil e-postprogrammet åpnes med dataene under, i tillegg til noe ekstra feilsporingsinfo, vedlagt. Det kan kanskje inneholde sensitiv info, så sjekk alt før du sender.</string>
|
||||
<string name="debug_info_more_data_shared">Når du klikker på «Del» vil dataene under bli sendt til utviklerne, i tillegg til noe ekstra feilsporingsinfo, vedlagt. Merk at forsendelsen kan inneholde sensitiv info.</string>
|
||||
<string name="sync_error_permissions">EteSync-tillatelser</string>
|
||||
<string name="sync_error_permissions_text">Ekstra tillatelser kreves</string>
|
||||
<string name="sync_error_calendar">Kalendersynkronisering mislyktes (%s)</string>
|
||||
@ -365,7 +365,7 @@
|
||||
<string name="sync_error_generic">Kunne ikke synkronisere (%s)</string>
|
||||
<string name="signup_password_restrictions">Passordet må være minst 8 tegn langt</string>
|
||||
<string name="signup_title">Skriv inn innloggingsdetaljer</string>
|
||||
<string name="login_username_error">Et gyldig brukernavn kreves</string>
|
||||
<string name="login_username_error">Brukernavnet må være minst 6 tegn, og bestå av kun bokstaver, tall og ./-/_.</string>
|
||||
<string name="login_username">Bruikernavn</string>
|
||||
<string name="journal_item_tab_revisions">Revisjoner</string>
|
||||
<string name="invitations_reject">Avslå</string>
|
||||
@ -378,4 +378,21 @@
|
||||
<string name="collection_members_no_access">Kun administratorer kan håndtere samlingsmedlemskap. Ønsker du å forlate denne samlingen\?</string>
|
||||
<string name="collection_members_remove_admin">Fjerning av tilganger for administratorer støttes ikke.</string>
|
||||
<string name="edit_owner_only_anon">Kun eiere av denne samlingen kan redigere den.</string>
|
||||
</resources>
|
||||
<string name="collection_members_add_success">Invitasjon sendt. Brukeren vil bli lagt til når invitasjonen har blitt godkjent.</string>
|
||||
<string name="collection_members_add_read_only">Skrivebeskyttet</string>
|
||||
<string name="account_wizard_collections_body">For å kunne bruke EteSync må du opprette samlinger å lagre dataen din i. Klikk «Opprett» for å opprette en forvalgt kalender, adressebok og gjøremålsliste.</string>
|
||||
<string name="account_wizard_collections_title">Velkommen til EteSync.</string>
|
||||
<string name="retry">Prøv igjen</string>
|
||||
<string name="skip">Hopp over</string>
|
||||
<string name="create">Opprett</string>
|
||||
<string name="migrate_v2_wizard_migrate_progress_entries">Migrerer oppføring %d/%d</string>
|
||||
<string name="migrate_v2_wizard_welcome_body_extra">Forsikre deg om at du har god tilknytning til Internett, og nok batteri, siden dette kan ta sin tid.
|
||||
\nFor å fortsette, velg hvorvidt du ønsker å registrere en ny EteSync 2.0-konto, eller logge inn med en eksisterende konto.</string>
|
||||
<string name="migrate_v2_wizard_migrate_progress_done_malformed">Ignorerte %d feilformaterte oppføringer (antagelig trygt å se bort fra).</string>
|
||||
<string name="migrate_v2_wizard_migrate_progress_done">Kontoen din har blitt migrert.
|
||||
\nBekreft at alt ble flyttet over på riktig vis, og fjern din gamle konto når du er ferdig.</string>
|
||||
<string name="migrate_v2_wizard_migrate_progress">Migrerer samling %d/%d</string>
|
||||
<string name="migrate_v2_wizard_migrate_title">Migrerer …</string>
|
||||
<string name="migrate_v2_wizard_welcome_body">Dette verktøyet hjelper deg å migrere din data til EteSync 2.0. Migrasjonen sletter ingen data, den kopierer kun data over til den nye EteSync 2.0-tjeneren. Dette betyr at det ikke er muligheter for datatap under migrasjonen.</string>
|
||||
<string name="migrate_v2_wizard_welcome_title">EteSync 2.0-migrasjon</string>
|
||||
</resources>
|
@ -52,8 +52,46 @@
|
||||
<string name="notification_channel_sync">Синхронизация</string>
|
||||
<string name="notification_channel_general">Другие важные сообщения</string>
|
||||
<string name="notification_channel_debugging">Отладка</string>
|
||||
<string name="please_wait">Пожалуйста, подождите …</string>
|
||||
<string name="please_wait">Пожалуйста, подождите…</string>
|
||||
<string name="manage_accounts">Управление аккаунтами</string>
|
||||
<string name="address_books_authority_title">Адресные книги</string>
|
||||
<string name="app_name">EteSync</string>
|
||||
<string name="accounts_global_sync_enable">Включить</string>
|
||||
<string name="accounts_global_sync_disabled">Общесистемная автоматическая синхронизация отключена</string>
|
||||
<string name="account_list_empty">Добро пожаловать в EteSync!</string>
|
||||
<string name="navigation_drawer_contact">Связаться с нами</string>
|
||||
<string name="navigation_drawer_report_issue">Сообщить о проблеме</string>
|
||||
<string name="navigation_drawer_guide">Инструкция пользователя</string>
|
||||
<string name="navigation_drawer_faq">FAQ</string>
|
||||
<string name="navigation_drawer_website">Веб-сайт</string>
|
||||
<string name="navigation_drawer_external_links">Полезные ссылки</string>
|
||||
<string name="navigation_drawer_settings">Настройки</string>
|
||||
<string name="navigation_drawer_about">О / Лицензия</string>
|
||||
<string name="navigation_drawer_subtitle">Адаптер безопасной синхронизации</string>
|
||||
<string name="navigation_drawer_close">Закрыть панель навигации</string>
|
||||
<string name="logging_couldnt_create_file">Невозможно создать внешний файл лога: %s</string>
|
||||
<string name="logging_to_external_storage_warning">Выключение удалит логи</string>
|
||||
<string name="logging_to_external_storage">Логирование на внешнее хранилище: %s</string>
|
||||
<string name="logging_davdroid_file_logging">Логирование файлов EteSync включено</string>
|
||||
<string name="about_license_info_no_warranty">Эта программа поставляется СОВЕРШЕННО БЕЗ ГАРАНТИЙ. Это бесплатное программное обеспечение, и вы можете распространять его при определенных условиях.</string>
|
||||
<string name="startup_vendor_specific_bugs_message">EteSync обнаружил, что вы используете версию Android, которая может содержать ошибки производителя.
|
||||
\nПожалуйста, посмотрите FAQ для получения дополнительной информации.</string>
|
||||
<string name="startup_vendor_specific_bugs">Возможные ошибки поставщика</string>
|
||||
<string name="startup_development_version_message">Это разрабатываемая версия EteSync. Имейте в виду, что все может работать не так, как ожидалось. Пожалуйста, дайте нам конструктивный отзыв для улучшения EteSync.</string>
|
||||
<string name="startup_battery_optimization_disable">Выключить для EteSync</string>
|
||||
<string name="startup_battery_optimization_message">Android может отключить / уменьшить синхронизацию EteSync через несколько дней. Чтобы этого не произошло, отключите оптимизацию батареи.</string>
|
||||
<string name="crash_email_body">Если возможно, укажите любую другую соответствующую информацию, например, что вы сделали, чтобы вызвать эту ошибку.</string>
|
||||
<string name="crash_email_subject">Отладочная информация EteSync</string>
|
||||
<string name="crash_message">Пожалуйста, отправьте трассировку стека разработчикам.</string>
|
||||
<string name="crash_title">EteSync сломался!</string>
|
||||
<string name="notification_channel_sync_status_desc">Информационные сообщения о состоянии, такие как сводка изменений синхронизации</string>
|
||||
<string name="notification_channel_sync_status">Статус сообщений</string>
|
||||
<string name="notification_channel_sync_warnings_desc">Не критические проблемы синхронизации, такие как отклоненные изменения коллекций, доступных только для чтения</string>
|
||||
<string name="notification_channel_sync_warnings">Предупреждения синхронизации</string>
|
||||
<string name="notification_channel_sync_errors_desc">Важные ошибки, которые останавливают синхронизацию, например, неожиданные ответы сервера</string>
|
||||
<string name="notification_channel_crash_reports">Отчеты о падениях</string>
|
||||
<string name="retry">Повторить</string>
|
||||
<string name="skip">Пропустить</string>
|
||||
<string name="create">Создать</string>
|
||||
<string name="account_title_address_book">Адресная книга EteSync</string>
|
||||
</resources>
|
21
app/src/main/res/values-sk/strings.xml
Normal file
21
app/src/main/res/values-sk/strings.xml
Normal file
@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="notification_channel_sync_status_desc">Informačné správy o stave ako napríklad sumár zmien pri synchronizácii</string>
|
||||
<string name="notification_channel_sync_status">Správy o stave</string>
|
||||
<string name="notification_channel_sync_warnings">Varovania k synchronizácii</string>
|
||||
<string name="notification_channel_sync_errors_desc">Dôležité chyby zastavujúce synchronizáciu, ako napríklad neočakávaná odpoveď servera</string>
|
||||
<string name="notification_channel_sync_errors">Chyby pri synchronizácii</string>
|
||||
<string name="notification_channel_sync">Synchronizácia</string>
|
||||
<string name="notification_channel_general">Iné dôležité správy</string>
|
||||
<string name="notification_channel_debugging">Hľadanie chýb</string>
|
||||
<string name="retry">Skúsiť znovu</string>
|
||||
<string name="skip">Preskočiť</string>
|
||||
<string name="create">Vytvoriť</string>
|
||||
<string name="send">Odoslať</string>
|
||||
<string name="please_wait">Čakajte prosím…</string>
|
||||
<string name="manage_accounts">Spravovať účty</string>
|
||||
<string name="help">Pomoc</string>
|
||||
<string name="address_books_authority_title">Zoznamy kontaktov</string>
|
||||
<string name="account_title_address_book">EteSync Kontakty</string>
|
||||
<string name="app_name">EteSync</string>
|
||||
</resources>
|
@ -176,7 +176,7 @@
|
||||
<string name="sync_error_contacts">Kişileri eşzamanlama başarısız oldu (%s)</string>
|
||||
<string name="sync_error_permissions_text">Ek izinler gerekli</string>
|
||||
<string name="sync_error_permissions">EteSync izinleri</string>
|
||||
<string name="debug_info_more_data_shared">Paylaş düğmesine tıklamak, aşağıdaki verilerin yanı sıra bazı ek hata ayıklama bilgilerinin ekli olduğu e-posta uygulamasını açacaktır. Bazı hassas bilgiler içerebilir, bu nedenle lütfen göndermeden önce inceleyin.</string>
|
||||
<string name="debug_info_more_data_shared">Paylaş düğmesine tıklamak, bazı ek hata ayıklama bilgilerinin yanı sıra aşağıdaki verileri geliştiricilere gönderecektir. Lütfen bunun bazı özel bilgiler içerebileceğini unutmayın.</string>
|
||||
<string name="journal_entries_loading">Değişiklik günlüğü girdileri yükleniyor...</string>
|
||||
<string name="journal_entries_list_empty">Koleksiyon boş.
|
||||
\nBelki hala eşzamanlanıyordur\?</string>
|
||||
@ -384,4 +384,14 @@
|
||||
<string name="skip">Atla</string>
|
||||
<string name="create">Oluştur</string>
|
||||
<string name="collection_members_add_read_only">Salt okunur</string>
|
||||
<string name="migrate_v2_wizard_migrate_progress_done">Hesabınız taşındı.
|
||||
\nLütfen her şeyin doğru bir şekilde kopyalandığını doğrulayın ve işiniz bittiğinde eski hesabınızı kaldırın.</string>
|
||||
<string name="migrate_v2_wizard_migrate_progress_entries">Taşınan girdiler %d/%d</string>
|
||||
<string name="migrate_v2_wizard_migrate_progress">Koleksiyon taşınıyor %d/%d</string>
|
||||
<string name="migrate_v2_wizard_migrate_title">Taşınıyor…</string>
|
||||
<string name="migrate_v2_wizard_welcome_body_extra">Bu biraz zaman alabileceğinden lütfen iyi bir internet bağlantınız ve yeterli piliniz olduğundan emin olun.
|
||||
\nDevam etmek için, lütfen aşağıdan yeni bir EteSync 2.0 hesabına kaydolmak veya mevcut bir hesapla oturum açmak seçeneklerinden birini seçin.</string>
|
||||
<string name="migrate_v2_wizard_welcome_body">Bu araç verilerinizi EteSync 2.0\'a taşımanıza yardımcı olacaktır. Taşıma herhangi bir veriyi silmez. Verilerinizi yalnızca yeni EteSync 2.0 sunucusuna kopyalar. Bu, taşıma esnasında veri kaybı riski olmadığı anlamına gelir.</string>
|
||||
<string name="migrate_v2_wizard_welcome_title">EteSync 2.0\'a Taşıma</string>
|
||||
<string name="migrate_v2_wizard_migrate_progress_done_malformed">%d hatalı biçimlendirilmiş girdi yok sayıldı (yok sayılmaları muhtemelen güvenli).</string>
|
||||
</resources>
|
@ -1,127 +1,127 @@
|
||||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||
<!--common strings-->
|
||||
<string name="help">帮助</string>
|
||||
<string name="manage_accounts">管理账户</string>
|
||||
<string name="please_wait">请稍等...</string>
|
||||
<string name="send">发送</string>
|
||||
<!--startup dialogs-->
|
||||
<string name="startup_battery_optimization">电池优化</string>
|
||||
<string name="startup_dont_show_again">不再显示</string>
|
||||
<!--AboutActivity-->
|
||||
<string name="about_license_terms">许可协议</string>
|
||||
<string name="about_license_info_no_warranty">本程序不附带任何担保。这是一款自由软件,你可以有条件地传播它。</string>
|
||||
<!--global settings-->
|
||||
<string name="logging_to_external_storage">记录日志到外部存储 %s</string>
|
||||
<string name="logging_to_external_storage_warning">请尽快删除日志!</string>
|
||||
<string name="logging_couldnt_create_file">无法创建外部日志文件 %s</string>
|
||||
<string name="logging_no_external_storage">找不到外部存储</string>
|
||||
<!--AccountsActivity-->
|
||||
<string name="navigation_drawer_open">打开导航抽屉</string>
|
||||
<string name="navigation_drawer_close">关闭导航抽屉</string>
|
||||
<string name="navigation_drawer_about">关于 / 许可</string>
|
||||
<string name="navigation_drawer_settings">设置</string>
|
||||
<string name="navigation_drawer_external_links">外部链接</string>
|
||||
<string name="navigation_drawer_website">应用网站</string>
|
||||
<string name="navigation_drawer_faq">常见问题</string>
|
||||
<!--AccountUpdateService-->
|
||||
<!--AppSettingsActivity-->
|
||||
<string name="app_settings">设置</string>
|
||||
<string name="app_settings_user_interface">用户界面</string>
|
||||
<string name="app_settings_reset_hints">重设提示</string>
|
||||
<string name="app_settings_reset_hints_summary">重新显示之前忽略过的提示</string>
|
||||
<string name="app_settings_reset_hints_success">所有提示将会再次显示</string>
|
||||
<string name="app_settings_connection">连接</string>
|
||||
<string name="app_settings_override_proxy">覆盖代理设置</string>
|
||||
<string name="app_settings_override_proxy_on">使用自定义代理设置</string>
|
||||
<string name="app_settings_override_proxy_off">使用系统默认代理设置</string>
|
||||
<string name="app_settings_override_proxy_host">HTTP 代理主机名</string>
|
||||
<string name="app_settings_override_proxy_port">HTTP 代理端口</string>
|
||||
<string name="app_settings_security">安全</string>
|
||||
<string name="app_settings_distrust_system_certs">不信任系统证书</string>
|
||||
<string name="app_settings_distrust_system_certs_on">系统和用户增加的发布者不会被信任</string>
|
||||
<string name="app_settings_distrust_system_certs_off">系统和用户增加的发布者会被信任(推荐)</string>
|
||||
<string name="app_settings_reset_certificates">重设证书信任状态</string>
|
||||
<string name="app_settings_reset_certificates_summary">重设所有自定义证书的信任状态</string>
|
||||
<string name="app_settings_reset_certificates_success">所有自定义证书已清除</string>
|
||||
<string name="app_settings_debug">调试</string>
|
||||
<string name="app_settings_log_to_external_storage">外部文件日志</string>
|
||||
<string name="app_settings_log_to_external_storage_on">记录日志到外部存储(如果可用)</string>
|
||||
<string name="app_settings_log_to_external_storage_off">外部文件日志已禁用</string>
|
||||
<string name="app_settings_show_debug_info">显示调试信息</string>
|
||||
<string name="app_settings_show_debug_info_details">查看软件和配置信息</string>
|
||||
<!--AccountActivity-->
|
||||
<string name="account_synchronize_now"> 立即同步</string>
|
||||
<string name="account_synchronizing_now">正在同步</string>
|
||||
<string name="account_settings">账户设置</string>
|
||||
<string name="account_delete">删除账户</string>
|
||||
<string name="account_delete_confirmation_title">真的要删除账户吗?</string>
|
||||
<string name="account_delete_confirmation_text">所有通讯录、日历和任务列表的本机存储将被删除。</string>
|
||||
<!--PermissionsActivity-->
|
||||
<string name="permissions_calendar">日历权限</string>
|
||||
<string name="permissions_calendar_request">请求日历权限</string>
|
||||
<string name="permissions_contacts">通讯录权限</string>
|
||||
<string name="permissions_contacts_request">请求通讯录权限</string>
|
||||
<string name="permissions_opentasks">OpenTasks 权限</string>
|
||||
<string name="permissions_opentasks_request">请求 OpenTasks 权限</string>
|
||||
<!--AddAccountActivity-->
|
||||
<string name="login_title">增加账户</string>
|
||||
<string name="login_email_address">Email 地址</string>
|
||||
<string name="login_email_address_error">请输入有效 Email 地址</string>
|
||||
<string name="login_password">密码</string>
|
||||
<string name="login_password_required">请输入密码</string>
|
||||
<string name="login_back">返回</string>
|
||||
<string name="login_configuration_detection">正在配置</string>
|
||||
<string name="login_querying_server">正在与服务器通信,请稍等...</string>
|
||||
<string name="login_view_logs">查看日志</string>
|
||||
<!--AccountSettingsActivity-->
|
||||
<string name="settings_title">设置:%s</string>
|
||||
<string name="settings_authentication">认证</string>
|
||||
<string name="settings_password">密码</string>
|
||||
<string name="settings_enter_password">输入密码</string>
|
||||
<string name="settings_sync">同步</string>
|
||||
<string name="settings_sync_interval_contacts">通讯录自动同步间隔</string>
|
||||
<string name="settings_sync_summary_manually">手动同步</string>
|
||||
<string name="settings_sync_summary_periodically" tools:ignore="PluralsCandidate">%s 钟或本地修改后</string>
|
||||
<string name="settings_sync_summary_not_available">不可用</string>
|
||||
<string name="settings_sync_interval_calendars">日历自动同步间隔</string>
|
||||
<string-array name="settings_sync_interval_names">
|
||||
<item>手动同步</item>
|
||||
<item>每 15 分钟</item>
|
||||
<item>每 1 小时</item>
|
||||
<item>每 2 小时</item>
|
||||
<item>每 4 小时</item>
|
||||
<item>每 24 小时</item>
|
||||
</string-array>
|
||||
<string name="settings_sync_wifi_only">只在 WiFi 下同步</string>
|
||||
<string name="settings_sync_wifi_only_on">同步只在 WiFi 连接下进行</string>
|
||||
<string name="settings_sync_wifi_only_off">同步不受数据连接类型限制</string>
|
||||
<string name="settings_sync_wifi_only_ssid">WiFi SSID 限制</string>
|
||||
<string name="settings_sync_wifi_only_ssid_on">同步只在 %s 网络下进行</string>
|
||||
<string name="settings_sync_wifi_only_ssid_off">任何 WiFi 网络下均会同步</string>
|
||||
<string name="settings_sync_wifi_only_ssid_message">输入 WiFi 网络的名称 (SSID) ,即可限制同步只在此网络下进行。留空则不限制。</string>
|
||||
<!--collection management-->
|
||||
<string name="create_addressbook">创建通讯录</string>
|
||||
<string name="create_addressbook_display_name_hint">我的通讯录</string>
|
||||
<string name="create_collection_creating">正在创建集合</string>
|
||||
<string name="create_collection_display_name">集合显示名称(标题)</string>
|
||||
<string name="create_collection_display_name_required">请输入标题</string>
|
||||
<string name="create_collection_description">简介(可选)</string>
|
||||
<string name="create_collection_create">创建</string>
|
||||
<string name="delete_collection">删除集合</string>
|
||||
<string name="delete_collection_confirm_title">你确定吗?</string>
|
||||
<string name="delete_collection_confirm_warning">这个集合 %s 及其所有数据将会从服务器删除。</string>
|
||||
<string name="delete_collection_deleting_collection">正在删除集合</string>
|
||||
<!--ExceptionInfoFragment-->
|
||||
<string name="exception">出现错误</string>
|
||||
<string name="exception_httpexception">出现 HTTP 错误</string>
|
||||
<string name="exception_ioexception">出现 I/O 错误</string>
|
||||
<string name="exception_show_details">显示详情</string>
|
||||
<!--sync errors and DebugInfoActivity-->
|
||||
<string name="debug_info_title">调试信息</string>
|
||||
<string name="sync_error_calendar">日历同步失败(%s)</string>
|
||||
<string name="sync_error">%s时错误</string>
|
||||
<string name="sync_error_http_dav">%s时服务器错误</string>
|
||||
<string name="sync_error_local_storage">%s时数据库错误</string>
|
||||
<!--cert4android-->
|
||||
</resources>
|
||||
<!--common strings-->
|
||||
<string name="help">帮助</string>
|
||||
<string name="manage_accounts">管理账户</string>
|
||||
<string name="please_wait">请稍等…</string>
|
||||
<string name="send">发送</string>
|
||||
<!--startup dialogs-->
|
||||
<string name="startup_battery_optimization">电池优化</string>
|
||||
<string name="startup_dont_show_again">不再显示</string>
|
||||
<!--AboutActivity-->
|
||||
<string name="about_license_terms">许可协议</string>
|
||||
<string name="about_license_info_no_warranty">本程序不附带任何担保。这是一款自由软件,你可以有条件地传播它。</string>
|
||||
<!--global settings-->
|
||||
<string name="logging_to_external_storage">记录日志到外部存储 %s</string>
|
||||
<string name="logging_to_external_storage_warning">禁用会删除日志</string>
|
||||
<string name="logging_couldnt_create_file">无法创建外部日志文件 %s</string>
|
||||
<string name="logging_no_external_storage">找不到外部存储</string>
|
||||
<!--AccountsActivity-->
|
||||
<string name="navigation_drawer_open">打开导航抽屉</string>
|
||||
<string name="navigation_drawer_close">关闭导航抽屉</string>
|
||||
<string name="navigation_drawer_about">关于 / 许可</string>
|
||||
<string name="navigation_drawer_settings">设置</string>
|
||||
<string name="navigation_drawer_external_links">外部链接</string>
|
||||
<string name="navigation_drawer_website">应用网站</string>
|
||||
<string name="navigation_drawer_faq">常见问题</string>
|
||||
<!--AccountUpdateService-->
|
||||
<!--AppSettingsActivity-->
|
||||
<string name="app_settings">设置</string>
|
||||
<string name="app_settings_user_interface">用户界面</string>
|
||||
<string name="app_settings_reset_hints">重设提示</string>
|
||||
<string name="app_settings_reset_hints_summary">重新显示之前忽略过的提示</string>
|
||||
<string name="app_settings_reset_hints_success">所有提示将会再次显示</string>
|
||||
<string name="app_settings_connection">连接</string>
|
||||
<string name="app_settings_override_proxy">覆盖代理设置</string>
|
||||
<string name="app_settings_override_proxy_on">使用自定义代理设置</string>
|
||||
<string name="app_settings_override_proxy_off">使用系统默认代理设置</string>
|
||||
<string name="app_settings_override_proxy_host">HTTP 代理主机名</string>
|
||||
<string name="app_settings_override_proxy_port">HTTP 代理端口</string>
|
||||
<string name="app_settings_security">安全</string>
|
||||
<string name="app_settings_distrust_system_certs">不信任系统证书</string>
|
||||
<string name="app_settings_distrust_system_certs_on">系统和用户增加的发布者不会被信任</string>
|
||||
<string name="app_settings_distrust_system_certs_off">系统和用户增加的发布者会被信任(推荐)</string>
|
||||
<string name="app_settings_reset_certificates">重设证书信任状态</string>
|
||||
<string name="app_settings_reset_certificates_summary">重设所有自定义证书的信任状态</string>
|
||||
<string name="app_settings_reset_certificates_success">所有自定义证书已清除</string>
|
||||
<string name="app_settings_debug">调试</string>
|
||||
<string name="app_settings_log_to_external_storage">外部文件日志</string>
|
||||
<string name="app_settings_log_to_external_storage_on">记录日志到外部存储(如果可用)</string>
|
||||
<string name="app_settings_log_to_external_storage_off">外部文件日志已禁用</string>
|
||||
<string name="app_settings_show_debug_info">显示调试信息</string>
|
||||
<string name="app_settings_show_debug_info_details">查看软件和配置信息</string>
|
||||
<!--AccountActivity-->
|
||||
<string name="account_synchronize_now"> 立即同步</string>
|
||||
<string name="account_synchronizing_now">正在同步</string>
|
||||
<string name="account_settings">账户设置</string>
|
||||
<string name="account_delete">删除账户</string>
|
||||
<string name="account_delete_confirmation_title">真的要删除账户吗?</string>
|
||||
<string name="account_delete_confirmation_text">所有通讯录、日历和任务列表的本机存储将被删除。</string>
|
||||
<!--PermissionsActivity-->
|
||||
<string name="permissions_calendar">日历权限</string>
|
||||
<string name="permissions_calendar_request">请求日历权限</string>
|
||||
<string name="permissions_contacts">通讯录权限</string>
|
||||
<string name="permissions_contacts_request">请求通讯录权限</string>
|
||||
<string name="permissions_opentasks">OpenTasks 权限</string>
|
||||
<string name="permissions_opentasks_request">请求 OpenTasks 权限</string>
|
||||
<!--AddAccountActivity-->
|
||||
<string name="login_title">增加账户</string>
|
||||
<string name="login_email_address">Email 地址</string>
|
||||
<string name="login_email_address_error">需要有效的 Email 地址</string>
|
||||
<string name="login_password">密码</string>
|
||||
<string name="login_password_required">请输入密码</string>
|
||||
<string name="login_back">返回</string>
|
||||
<string name="login_configuration_detection">正在配置</string>
|
||||
<string name="login_querying_server">正在与服务器通信,请稍等...</string>
|
||||
<string name="login_view_logs">查看日志</string>
|
||||
<!--AccountSettingsActivity-->
|
||||
<string name="settings_title">设置:%s</string>
|
||||
<string name="settings_authentication">认证</string>
|
||||
<string name="settings_password">密码</string>
|
||||
<string name="settings_enter_password">输入你的密码:</string>
|
||||
<string name="settings_sync">同步</string>
|
||||
<string name="settings_sync_interval_contacts">通讯录自动同步间隔</string>
|
||||
<string name="settings_sync_summary_manually">手动同步</string>
|
||||
<string name="settings_sync_summary_periodically" tools:ignore="PluralsCandidate">%s 钟或本地修改后</string>
|
||||
<string name="settings_sync_summary_not_available">不可用</string>
|
||||
<string name="settings_sync_interval_calendars">日历自动同步间隔</string>
|
||||
<string-array name="settings_sync_interval_names">
|
||||
<item>手动同步</item>
|
||||
<item>每 15 分钟</item>
|
||||
<item>每 1 小时</item>
|
||||
<item>每 2 小时</item>
|
||||
<item>每 4 小时</item>
|
||||
<item>每 24 小时</item>
|
||||
</string-array>
|
||||
<string name="settings_sync_wifi_only">只在 WiFi 下同步</string>
|
||||
<string name="settings_sync_wifi_only_on">同步只在 WiFi 连接下进行</string>
|
||||
<string name="settings_sync_wifi_only_off">同步不受数据连接类型限制</string>
|
||||
<string name="settings_sync_wifi_only_ssid">WiFi SSID 限制</string>
|
||||
<string name="settings_sync_wifi_only_ssid_on">同步只在 %s 网络下进行</string>
|
||||
<string name="settings_sync_wifi_only_ssid_off">任何 WiFi 网络下均会同步</string>
|
||||
<string name="settings_sync_wifi_only_ssid_message">输入 WiFi 网络的名称 (SSID) ,即可限制同步只在此网络下进行。留空则不限制。</string>
|
||||
<!--collection management-->
|
||||
<string name="create_addressbook">创建通讯录</string>
|
||||
<string name="create_addressbook_display_name_hint">我的通讯录</string>
|
||||
<string name="create_collection_creating">正在创建集合</string>
|
||||
<string name="create_collection_display_name">显示这个集合的名称(标题):</string>
|
||||
<string name="create_collection_display_name_required">请输入标题</string>
|
||||
<string name="create_collection_description">描述(可选):</string>
|
||||
<string name="create_collection_create">创建</string>
|
||||
<string name="delete_collection">删除集合</string>
|
||||
<string name="delete_collection_confirm_title">你确定吗?</string>
|
||||
<string name="delete_collection_confirm_warning">这个集合 %s 及其所有数据将会从服务器删除。</string>
|
||||
<string name="delete_collection_deleting_collection">正在删除集合</string>
|
||||
<!--ExceptionInfoFragment-->
|
||||
<string name="exception">发生错误。</string>
|
||||
<string name="exception_httpexception">发生 HTTP 错误。</string>
|
||||
<string name="exception_ioexception">发生 I/O 错误。</string>
|
||||
<string name="exception_show_details">显示详情</string>
|
||||
<!--sync errors and DebugInfoActivity-->
|
||||
<string name="debug_info_title">调试信息</string>
|
||||
<string name="sync_error_calendar">日历同步失败(%s)</string>
|
||||
<string name="sync_error">%s时错误</string>
|
||||
<string name="sync_error_http_dav">%s时服务器错误</string>
|
||||
<string name="sync_error_local_storage">%s时数据库错误</string>
|
||||
<!--cert4android-->
|
||||
</resources>
|
@ -38,8 +38,7 @@
|
||||
<!-- Crash -->
|
||||
<string name="crash_title">EteSync crashed!</string>
|
||||
<string name="crash_message">Please send stack trace to developers.</string>
|
||||
<string name="crash_email_subject">EteSync Debug Info</string>
|
||||
<string name="crash_email_body">If possible, please include any other relevant info, such as what you did before this happened.</string>
|
||||
<string name="crash_email_body">If possible, please include any other relevant info such as what you did before this happened.</string>
|
||||
|
||||
<!-- tourguide -->
|
||||
<string name="tourguide_title">Did you know?</string>
|
||||
@ -90,6 +89,16 @@
|
||||
<string name="account_wizard_collections_title">Welcome to EteSync!</string>
|
||||
<string name="account_wizard_collections_body">In order to start using EteSync you need to create collections to store your data. Click "Create" to create a default calendar, address book and a task list for you.</string>
|
||||
|
||||
<!-- Migrate v2 Wizard -->
|
||||
<string name="migrate_v2_wizard_welcome_title">EteSync 2.0 Migration</string>
|
||||
<string name="migrate_v2_wizard_welcome_body">This tool will help you migrate your data to EteSync 2.0. The migration doesn\'t delete any data. It only copies your data over to the new EteSync 2.0 server. This means that there is no risk of data-loss in the migration.</string>
|
||||
<string name="migrate_v2_wizard_welcome_body_extra">Please make sure you have a good internet connection and enough battery as this may take a while.\nTo continue, please choose below whether you would like to signup for a new EteSync 2.0 account, or login using an existing one.</string>
|
||||
<string name="migrate_v2_wizard_migrate_title">Migrating…</string>
|
||||
<string name="migrate_v2_wizard_migrate_progress">Migrating collection %d/%d</string>
|
||||
<string name="migrate_v2_wizard_migrate_progress_entries">Migrated entries %d/%d</string>
|
||||
<string name="migrate_v2_wizard_migrate_progress_done">Your account has been migrated.\nPlease verify everything was copied over correctly, and remove your old account once done.</string>
|
||||
<string name="migrate_v2_wizard_migrate_progress_done_malformed">Ignored %d malformed entries (probably safe to ignore).</string>
|
||||
|
||||
<!-- AccountUpdateService -->
|
||||
|
||||
<!-- AppSettingsActivity -->
|
||||
@ -368,7 +377,7 @@
|
||||
<!-- sync errors and DebugInfoActivity -->
|
||||
<string name="authority_log_provider" translatable="false">com.etesync.syncadapter.log</string>
|
||||
<string name="debug_info_title">Debug info</string>
|
||||
<string name="debug_info_more_data_shared">Clicking \"Share\" will open the e-mail app with the data below, as well as some additional debug info, attached. It may contain some sensitive info, so please review it before sending.</string>
|
||||
<string name="debug_info_more_data_shared">Clicking \"Share\" will open your e-mail app with the data below, as well as some additional debug info, attached. It may contain some private info.</string>
|
||||
<string name="sync_error_permissions">EteSync permissions</string>
|
||||
<string name="sync_error_permissions_text">Additional permissions required</string>
|
||||
<string name="sync_error_generic">Sync failed (%s)</string>
|
||||
|
@ -33,6 +33,7 @@
|
||||
<color name="primaryTextColor">#000000</color>
|
||||
|
||||
<color name="errorColor">#d32f2f</color>
|
||||
<color name="infoColor">#E8F4FD</color>
|
||||
|
||||
|
||||
<!-- app theme -->
|
||||
|
@ -8,15 +8,15 @@
|
||||
|
||||
ext {
|
||||
kotlin_version = '1.4.10'
|
||||
gradle_version = '4.0.0'
|
||||
compileSdkVersion = 28
|
||||
buildToolsVersion = '29.0.3'
|
||||
gradle_version = '4.0.1'
|
||||
compileSdkVersion = 30
|
||||
buildToolsVersion = '30.0.2'
|
||||
}
|
||||
|
||||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||
buildscript {
|
||||
ext.kotlin_version = '1.4.10'
|
||||
ext.gradle_version = '4.0.0'
|
||||
ext.gradle_version = '4.0.1'
|
||||
|
||||
repositories {
|
||||
jcenter()
|
||||
|
1
fastlane/metadata/android/en-US/changelogs/20000.txt
Normal file
1
fastlane/metadata/android/en-US/changelogs/20000.txt
Normal file
@ -0,0 +1 @@
|
||||
* EteSync 2.0 support \o/
|
1
fastlane/metadata/android/en-US/changelogs/20100.txt
Normal file
1
fastlane/metadata/android/en-US/changelogs/20100.txt
Normal file
@ -0,0 +1 @@
|
||||
* Change the crash reporting to not rely on email (use HTTP instead)
|
3
fastlane/metadata/android/en-US/changelogs/20101.txt
Normal file
3
fastlane/metadata/android/en-US/changelogs/20101.txt
Normal file
@ -0,0 +1,3 @@
|
||||
* 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.
|
2
fastlane/metadata/android/en-US/changelogs/20102.txt
Normal file
2
fastlane/metadata/android/en-US/changelogs/20102.txt
Normal file
@ -0,0 +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.
|
2
fastlane/metadata/android/en-US/changelogs/20103.txt
Normal file
2
fastlane/metadata/android/en-US/changelogs/20103.txt
Normal file
@ -0,0 +1,2 @@
|
||||
* Fix crashes on older Android devices
|
||||
* Fix crashes with some screen not loading for some users.
|
2
fastlane/metadata/android/en-US/changelogs/20104.txt
Normal file
2
fastlane/metadata/android/en-US/changelogs/20104.txt
Normal file
@ -0,0 +1,2 @@
|
||||
* Event invitations: only send invitations if we are the organizers
|
||||
* Fix rare crash when pushing changes with EteSync 1.0 accounts
|
3
fastlane/metadata/android/en-US/changelogs/20105.txt
Normal file
3
fastlane/metadata/android/en-US/changelogs/20105.txt
Normal file
@ -0,0 +1,3 @@
|
||||
* Improve error handling in sync and import
|
||||
* Update translations
|
||||
* Fix some crashes
|
5
fastlane/metadata/android/en-US/changelogs/20200.txt
Normal file
5
fastlane/metadata/android/en-US/changelogs/20200.txt
Normal file
@ -0,0 +1,5 @@
|
||||
* Support resizable activities
|
||||
* Update ical4android dep - should fix issues with duplicate tasks and events
|
||||
* Update vcard4android dep
|
||||
* Update gradle and sdk version
|
||||
* Update translations
|
1
fastlane/metadata/android/en-US/changelogs/20201.txt
Normal file
1
fastlane/metadata/android/en-US/changelogs/20201.txt
Normal file
@ -0,0 +1 @@
|
||||
* Fix crash when importing events and also when syncing legacy events
|
1
fastlane/metadata/android/en-US/changelogs/20202.txt
Normal file
1
fastlane/metadata/android/en-US/changelogs/20202.txt
Normal file
@ -0,0 +1 @@
|
||||
* Fix "potential vendor bugs" message constantly showing.
|
1
fastlane/metadata/android/en-US/changelogs/20203.txt
Normal file
1
fastlane/metadata/android/en-US/changelogs/20203.txt
Normal file
@ -0,0 +1 @@
|
||||
* Fix issues with the Tasks.org integration and subtasks (due to rewriting UIDs).
|
@ -1,4 +1,4 @@
|
||||
Secure, end-to-end encrypted, and privacy respecting sync for Android, the desktop and the web. Currently supports calendars, contacts and tasks (using OpenTasks), with more on the way.
|
||||
Secure, end-to-end encrypted, and privacy respecting sync for your contacts, calendars, and tasks (using Tasks.org and OpenTasks). For notes, please use the EteSync Notes application.
|
||||
|
||||
In order to use this application you need to have an account with EteSync (paid hosting), or run your own instance (free and open source). Check out https://www.etesync.com/ for more information.
|
||||
|
||||
|
@ -1 +1 @@
|
||||
Subproject commit 3429dd1747e076c70535e6e36fb8130b3c718df7
|
||||
Subproject commit b023c079b2a8cd2fe69360a835cf0d872b71cd53
|
@ -1 +1 @@
|
||||
Subproject commit c6d8560a2e958dcdf8b98e3f0c0e8c0a417b0962
|
||||
Subproject commit e98a3a553511f66b9bbdb914f2d2c91176108aab
|
Loading…
Reference in New Issue
Block a user