From 2f3337464939d56cf4820ce50de8205d0b6b8077 Mon Sep 17 00:00:00 2001 From: Ricki Hirner Date: Tue, 14 Jan 2020 21:06:09 +0100 Subject: [PATCH 001/783] Update dependencies (including okhttp 3.12.8) --- app/build.gradle | 4 ++-- build.gradle | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 29c069c27..3d2162844 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -115,13 +115,13 @@ dependencies { implementation 'androidx.fragment:fragment-ktx:1.1.0' implementation 'androidx.lifecycle:lifecycle-extensions:2.1.0' implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.1.0' - implementation 'androidx.paging:paging-runtime-ktx:2.1.0' + implementation 'androidx.paging:paging-runtime-ktx:2.1.1' implementation 'androidx.preference:preference:1.1.0' implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0-alpha03' implementation 'com.google.android:flexbox:1.1.0' implementation 'com.google.android.material:material:1.2.0-alpha03' - def room_version = '2.2.2' + def room_version = '2.2.3' implementation "androidx.room:room-runtime:$room_version" implementation "androidx.room:room-ktx:$room_version" kapt "androidx.room:room-compiler:$room_version" diff --git a/build.gradle b/build.gradle index 0c0ed60e8..dfba67994 100644 --- a/build.gradle +++ b/build.gradle @@ -10,7 +10,7 @@ buildscript { ext.versions = [ dokka: '0.10.0', kotlin: '1.3.61', - okhttp: '3.12.7' + okhttp: '3.12.8' ] repositories { -- GitLab From 390567957621a256f197a2a81f01020d7aa2fe1f Mon Sep 17 00:00:00 2001 From: Ricki Hirner Date: Tue, 14 Jan 2020 21:07:46 +0100 Subject: [PATCH 002/783] Version bump to 3.6.2.1 --- app/build.gradle | 5 ++--- fastlane/metadata/android/en-US/changelogs/329.txt | 1 + 2 files changed, 3 insertions(+), 3 deletions(-) create mode 100644 fastlane/metadata/android/en-US/changelogs/329.txt diff --git a/app/build.gradle b/app/build.gradle index 3d2162844..149c08365 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -19,8 +19,7 @@ android { defaultConfig { applicationId "at.bitfire.davdroid" - versionCode 328 - versionName '2.6.3' + versionCode 329 buildConfigField "long", "buildTime", System.currentTimeMillis() + "L" buildConfigField "boolean", "customCerts", "true" @@ -44,7 +43,7 @@ android { flavorDimensions "distribution" productFlavors { standard { - versionName "2.6.3-ose" + versionName "2.6.3.1-ose" } } diff --git a/fastlane/metadata/android/en-US/changelogs/329.txt b/fastlane/metadata/android/en-US/changelogs/329.txt new file mode 100644 index 000000000..79798ab3a --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/329.txt @@ -0,0 +1 @@ +Update libraries, including okhttp, to fix a crash when resource detection with HTTP/2 connections is cancelled by user -- GitLab From e236b0718421952ed1f7fa24e6679c0a9466581b Mon Sep 17 00:00:00 2001 From: Ricki Hirner Date: Thu, 16 Jan 2020 19:56:38 +0100 Subject: [PATCH 003/783] Use okhttp version defined by DAVx5 (dav4jvm version may be older) --- app/build.gradle | 1 + app/src/main/java/at/bitfire/davdroid/HttpClient.kt | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 149c08365..6b01e9015 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -27,6 +27,7 @@ android { targetSdkVersion 29 // Android 10.0 multiDexEnabled true // >64k methods for Android 4.4 + buildConfigField "String", "okhttpVersion", "\"${versions.okhttp}\"" buildConfigField "String", "userAgent", "\"DAVx5\"" // when using this, make sure that notification icons are real bitmaps diff --git a/app/src/main/java/at/bitfire/davdroid/HttpClient.kt b/app/src/main/java/at/bitfire/davdroid/HttpClient.kt index 3f1e7b4fb..8c562d409 100644 --- a/app/src/main/java/at/bitfire/davdroid/HttpClient.kt +++ b/app/src/main/java/at/bitfire/davdroid/HttpClient.kt @@ -236,7 +236,7 @@ class HttpClient private constructor( private val userAgentDateFormat = SimpleDateFormat("yyyy/MM/dd", Locale.US) private val userAgentDate = userAgentDateFormat.format(Date(BuildConfig.buildTime)) private val userAgent = "${BuildConfig.userAgent}/${BuildConfig.VERSION_NAME} ($userAgentDate; dav4jvm; " + - "okhttp/${Constants.okhttpVersion}) Android/${Build.VERSION.RELEASE}" + "okhttp/${BuildConfig.okhttpVersion}) Android/${Build.VERSION.RELEASE}" override fun intercept(chain: Interceptor.Chain): Response { val locale = Locale.getDefault() -- GitLab From 3f2090ecc7f5fb37bf5f5168dc2fe517ab464843 Mon Sep 17 00:00:00 2001 From: Ricki Hirner Date: Mon, 20 Jan 2020 22:03:55 +0100 Subject: [PATCH 004/783] Update ical4android (alarm handling) and AboutLibraries --- app/build.gradle | 2 +- ical4android | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 6b01e9015..06d0996cc 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -128,7 +128,7 @@ dependencies { implementation 'com.gitlab.bitfireAT:dav4jvm:1.0' implementation 'com.jaredrummler:colorpicker:1.1.0' - implementation('com.mikepenz:aboutlibraries:7.0.4') + implementation('com.mikepenz:aboutlibraries:7.1.0') implementation "com.squareup.okhttp3:okhttp:${versions.okhttp}" implementation "com.squareup.okhttp3:logging-interceptor:${versions.okhttp}" implementation 'commons-io:commons-io:2.6' diff --git a/ical4android b/ical4android index be6d515db..640fc4111 160000 --- a/ical4android +++ b/ical4android @@ -1 +1 @@ -Subproject commit be6d515db8721008bdc93a9a6668f51b4a79502e +Subproject commit 640fc4111999851ca282ccfa3aa02245014e7164 -- GitLab From 8325fdcf86196ecb0b99e9d592dc5a506bc79eb0 Mon Sep 17 00:00:00 2001 From: Ricki Hirner Date: Thu, 23 Jan 2020 18:36:02 +0100 Subject: [PATCH 005/783] Version bump to 2.6.4-beta1 --- app/build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 06d0996cc..2205b6c87 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -19,7 +19,7 @@ android { defaultConfig { applicationId "at.bitfire.davdroid" - versionCode 329 + versionCode 330 buildConfigField "long", "buildTime", System.currentTimeMillis() + "L" buildConfigField "boolean", "customCerts", "true" @@ -44,7 +44,7 @@ android { flavorDimensions "distribution" productFlavors { standard { - versionName "2.6.3.1-ose" + versionName "2.6.4-beta1-ose" } } -- GitLab From ac3e9fb8255349b8895e54223ba25de0f733415e Mon Sep 17 00:00:00 2001 From: Ricki Hirner Date: Thu, 23 Jan 2020 18:39:07 +0100 Subject: [PATCH 006/783] Fetch translations from Transifex --- app/src/main/res/values-da/strings.xml | 14 +++ app/src/main/res/values-el/strings.xml | 5 + app/src/main/res/values-fa/strings.xml | 108 +++++++++++---------- app/src/main/res/values-fr/strings.xml | 9 +- app/src/main/res/values-ru/strings.xml | 8 +- app/src/main/res/values-zh-rCN/strings.xml | 14 +++ 6 files changed, 102 insertions(+), 56 deletions(-) diff --git a/app/src/main/res/values-da/strings.xml b/app/src/main/res/values-da/strings.xml index 09ef96ee3..3ab32853a 100644 --- a/app/src/main/res/values-da/strings.xml +++ b/app/src/main/res/values-da/strings.xml @@ -50,6 +50,8 @@ CalDAV/CardDAV synkroniseringsadapter Om / licens Beta tilbagemelding + Installere en e-post klient + Installere en netlæser Opsætning Nyheder & opdateringer Eksterne henvisninger @@ -58,6 +60,7 @@ OSS Hjælp / fora Donation + Privatliv retningslinje Ingen internetforbindelse. Android kører ikke synkronisering. Velkommen til DAVx⁵!\n\nDu kan nu tilføje en CalDAV/CardDAV konto. Automatisk synkronisering på tværs af systemet er deaktiveret @@ -190,6 +193,13 @@ Begivenheder, der er mere end %d dage gamle, vil blive ignoreret Begivenheder, som er mere end dette antal dage gamle vil blive ignoreret (kan også være 0). Hvis feltet ikke er udfyldt, vil alle begivenheder blive synkroniseret. + Standard påmindelse + + Standard påmindelse 1 minut før hændelse + Standard påmindelse %d minutter før hændelse + + Ingen standard påmindelse oprettet + Hvis standard påmindelse skal oprettes for hændelse uden påmindelse: antal minutter før hændelse. Efterlad tom for at deaktivere standard påmindelse. Administrer farver for kalender Kalenderfarver administreres af DAVx⁵ Kalenderfarver sættes ikke fra DAVx⁵ @@ -198,6 +208,10 @@ Synkroniser ikke farver for begivenheder CardDAV Gruppering af kontakter + + Grupper er særskilte vCards + Grupper er kategorier per kontakt + Skift grupperingsmetode Opret adressebog diff --git a/app/src/main/res/values-el/strings.xml b/app/src/main/res/values-el/strings.xml index 349f09f98..254f6ff0f 100644 --- a/app/src/main/res/values-el/strings.xml +++ b/app/src/main/res/values-el/strings.xml @@ -50,6 +50,8 @@ Προσαρμογέας συγχρονισμού CalDAV/CardDAV Περί / άδεια χρήσης Ανατροφοδότηση beta + Παρακαλούμε εγκαταστήστε ένα πελάτη ηλεκτρονικού ταχυδρομείου + Παρακαλούμε εγκαταστήστε έναν περιηγητή ιστού Ρυθμίσεις Νέα & ενημερώσεις Εξωτερικοί σύνδεσμοι @@ -58,6 +60,7 @@ FAQ Βοήθεια / Φόρουμ Δωρεά + Πολιτική απορρήτου Δεν υπάρχει σύνδεση στο Διαδίκτυο. Το Android δεν θα πραγματοποιήσει συγχρονισμό. Καλώς ήρθατε στο DAVx⁵!\n\nΜπορείτε να προσθέσετε τώρα έναν λογαριασμό CalDAV/CardDAV. Ο αυτόματος συγχρονισμός σε όλο το σύστημα είναι απενεργοποιημένος @@ -190,6 +193,8 @@ Τα συμβάντα πέραν των %d ημερών θα αγνοηθούν Τα συμβάντα που υπερβαίνουν αυτόν τον αριθμό ημερών στο παρελθόν θα αγνοηθούν (μπορεί να είναι 0). Αφήστε κενό για συγχρονισμό όλων των συμβάντων. + Προεπιλεγμένη υπενθύμιση + Δεν δημιουργούνται προεπιλεγμένες υπενθυμίσεις Διαχείριση χρωμάτων ημερολογίου Τα χρώματα του ημερολογίου διαχειρίζονται από το DAVx⁵ Τα χρώματα του ημερολογίου δεν ορίστηκαν από το DAVx⁵ diff --git a/app/src/main/res/values-fa/strings.xml b/app/src/main/res/values-fa/strings.xml index 54fd82ffd..4a71d5dba 100644 --- a/app/src/main/res/values-fa/strings.xml +++ b/app/src/main/res/values-fa/strings.xml @@ -2,17 +2,17 @@ DAVx⁵ - DAVx⁵ دفترچه آدرس + دفترچه آدرس DAVx⁵ دفترچه‌های آدرس راهنما - مدیریت حساب ها + مدیریت حساب‌ها ارسال عیب‌یابی سایر پیام‌های مهم همگام‌سازی خطاهای همگام‌سازی خطاهای مهمی که ممکن است همگام سازی را متوقف کند، مثل پاسخ غیرمنتظره از سمت کارگزار - هشدارهای همگام سازی + هشدارهای همگام‌سازی مشکلات غیرمهم همگام سازی مانند بعضی فایهای نامعتبر خطاهای شبکه اتمام وقت، مشکل اتصال، غیره(اغلب موقت) @@ -50,14 +50,18 @@ CalDAV/CardDAV آداپتور همگام سازی درباره‌ی مجوز‌ها بازخورد بتا + لطفا یک کارخواه رایانامه نصب کنید + لطفا یک مرورگر وب نصب کنید تنظیمات خبر ها و بروزرسانی ها لینک های خارجی وب سایت کتابچه راهنمای FAQ - راهنمای فرم ها + راهنما / انجمن‌ها کمک مالی + سیاست حریم شخصی + ارتبط اینترنتی برقرار نیست. اندرید، همگام‌سازی را انجام نخواهد داد. به DAVx⁵ خوش آمدید! شما اکنون می توانید یک حساب CalDAV / CardDAV اضافه کنید. هماهنگ سازی خودکار سیستم در حالت غیر فعال است فعال کردن @@ -72,22 +76,22 @@ رویدادنگاری مفصل رویدادنگاری فعال است رویدادنگاری غیرفعال است - ارتباط + اتصال تنظیمات پروکسی را لغو کنید از تنظیمات پروکسی سفارشی استفاده کنید از تنظیمات پروکسی به طور پیش فرض استفاده کنید HTTP proxy host name HTTP proxy port امنیت - گواهی سیستم عدم اطمینان - سیستم و CA اضافه شده توسط کاربر قابل اعتماد نیست - سیستم و CA اضافه شده توسط کاربر قابل اعتماد خواهد بود (توصیه می شود) - دد (un) گواهی مورد اعتماد - اعتبار همه گواهینامه های سفارشی را بازنشانی می کند + عدم اطمینان به گواهی سامانه + CAهای اضافه شده توسط کاربرو سامانه، قابل اعتماد نخواهند بود + CAهای اضافه شده توسط کاربر و سامانه، قابل اعتماد خواهد بود (توصیه می‌شود) + بازنشانی گواهی (غیر)قابل اعتماد + اعتبارهمه گواهینامه‌های سفارشی را بازنشانی می‌کند همه گواهینامه های سفارشی پاک شده اند رابط کاربری تنظیمات اعلان - مدیریت کانالهای اعلان و تنظیمات + مدیریت کانال‌های اعلان و تنظیمات نکات بازنشانی مجددا امکان نکات را که قبلا رد شده اند را فعال کنید تمام نکات دوباره نشان داده می شود @@ -95,9 +99,9 @@ CardDAV CalDAV Webcal - دفترچه آدرسی موجود نیست(هنوز) - تقویمی موجود نیست(هنوز) - اشتراک تقویمی موجود نیست(هنوز) + دفترچه آدرسی (هنوز) موجود نیست. + تقویمی (هنوز) موجود نیست. + اشتراک تقویمی (هنوز) موجود نیست. صفحه را به پایین بکشید تا فهرست از کارگزار، نو شود. همگام سازی در حال همگام سازی @@ -116,7 +120,7 @@ کتاب آدرس جدید ایجاد کنید لیست تقویم را بازخوانی کنید ساخت تقویم جدید - هیچ Webcal-capable برنامه ای پیدا نشد + هیچ برنامه‌ای با پشتیبانی Webcal پیدا نشد نصب ICSx⁵ افزودن حساب @@ -128,7 +132,7 @@ با URL و نام کاربری وارد شوید URL باید شروع شود http(s):// URL باید شروع شود https:// - Host نام مورد نیاز + نام میزبان ضروری است نام کاربر نام کاربری (ضروری است) Base URL @@ -157,28 +161,28 @@ تسک همگام سازی شد. فقط به صورت دستی - هر 15 دقیقه - هر 30 دقیقه + هر ۱۵ دقیقه + هر ۳۰ دقیقه هر ساعت - هر 2 ساعت - هر 4 ساعت - یکبار در روز + هر ۲ ساعت + هر ۴ ساعت + یک‌بار در روز - فقط با wifi همگام سازی شود - همگام سازی به اتصالات WiFi محدود شده است + فقط با وای‌فای همگام‌سازی شود + همگام‌سازی به اتصالات وای‌فای محدود شده است نوع اتصال در نظر گرفته نمی شود - WiFi SSID محدودیت + محدودیت SSID وای‌فای تنها همگام سازی خواهد شد %s - فقط روی %s همگام خواهد شد.(خدمات مکان‌یابی لازم است) - تمامی اتصالات wifi استفاده خواهد شد - نامهای جدا شده از علامت (SSID) از نامهای مجاز نامهای مجاز (SSID) از شبکههای مجاز Wi-Fi (برای همه خالی) شبکههای Wi-Fi (برای همه خالی) - برای خواندن نامهای وای فای دسترسی مکان لازم است و باید خدمات مکان بطور دائم فعال باشد + فقط روی %s همگام خواهد شد. (خدمات مکان‌یابی لازم است) + تمامی اتصالات وای‌فای استفاده خواهد شد + نام‌های (SSID) شبکه‌های مجاز وای‌فای که با ویرگول از هم جدا شده‌اند (برای همه، خالی بگذارید) + برای خواندن نام‌های وای فای دسترسی مکان لازم است و باید خدمات مکان بطور دائم فعال باشد اطلاعات بیشتر(FAQ) احراز هویت نام کاربر نام کاربر را وارد کنید: رمز عبور - به روز رسانی رمز عبور بر اساس سرور شما. + به‌روزرسانی رمز عبور بر اساس سرور شما. رمز عبور خود را وارد کنید: گواهی نام مستعار کاربر CalDAV @@ -188,16 +192,22 @@ رخدادهایی که بیش از یک روز از گذشتنشان می‌گذرد نادیده گرفته می‌شوند رویدادهای بیش از٪ d روز گذشته نادیده گرفته خواهد شد - رویدادهایی که بیشتر از این تعداد روزهای گذشته هستند نادیده گرفته می شود (ممکن است 0 باشد). برای همگام سازی تمام رویدادها خالی بگذارید. - مدیریت رنگ های تقویم - رنگ های تقویم توسط DAVx⁵ مدیریت شود - رنگ های تقویم توسط DAVx⁵ مدیریت نشود - - پشتیبانی رنگرویداد + رویدادهایی که بیشتر از این تعداد روزهای گذشته هستند نادیده گرفته می شود (ممکن است صفر باشد). برای همگام سازی تمام رویدادها خالی بگذارید. + یادآوری پیش‌فرض + + یادآوری پیش‌فرض یک دقیقه پیش از رویداد + یادآوری پیش‌فرض %d دقیقه پیش از رویداد + + هیچ یادآوری پیش‌فرضی ساخته نشده است + درصورت تمایل به ایجاد یادآوریهای پیش‌فرض برای رویدادهای فاقد یادآوری: تعداد دقایق مطلوب پیش از رویداد. برای غیر فعال کردن یادآوری‌های پیش‌فرض، خالی بگذارید. + مدیریت رنگ‌های تقویم + رنگ‌های تقویم توسط DAVx⁵ مدیریت می‌شود + رنگ‌های تقویم توسط DAVx⁵ تنظیم نمی‌شود + پشتیبانی رنگ رویداد همگام کردن رنگ رویداد همگام نکردن رنگ رویداد CardDAV - روش تماس با گروه + روش گروه‌بندی مخاطبین تغییر روش گروه ها ایجاد دفترچه آدرس @@ -219,30 +229,30 @@ محل ذخیره‌سازی ایجاد شد حذف مجموعه - آیا مطمءن هستین؟ + آیا مطمئنید؟ مجموعه‌ی (%s) به همراه همه‌ی داده‌هایش برای همیشه حذف می‌شوند. این داده ها باید از کارگزار حذف شوند. حذف مجموعه - اجبار read-only + اجبار فقط خواندنی مشخصات آدرس (URL): کپی URL یک خطا رخ داده است. - رخ داده است HTTPیک خظای. - رخ داده است I/O یک خطای . + یک خظای HTTP رخ داده است. + یک خطای I/O رخ داده است. نمایش جزییات - Debug اظلاعات - رویدادها به این پیام پیوست می‌شوند(لازم است برنامک دریافت کننده از پیوست پشتیبانی کند) + اطلاعات عیب‌یابی + لاگ یا گزارش رویدادها به این پیام پیوست می‌شوند (لازم است کارهٔ دریافت کننده، از پیوست پشتیبانی کند) فقط از کتابچه آدرس ها خوانده شود - DAVx⁵ دسترسی های - اجازه برای دسترسی های اضافی + دسترسی‌های DAVx⁵ + اجازه برای دسترسی‌های اضافی OpenTasks خیلی قدیمی - ورژن مورد نیاز: %1$s (ورژن فعلی %2$s) - تاییده‌ي با شکست مواجه شد (چک کردن اعتبار نامه‌ي ورودی) - خطاهای شبکه – %s - HTTP server خطا در – %s + نسخه مورد نیاز: %1$s (نسخه فعلی %2$s) + احراز هویت با شکست مواجه شد (اعتبارنامهٔ ورود را بررسی کنید) + خطای شبکه یا I/O – %s + خطای کارگزار HTTP – %s خطا در ذخیره سازی – %s دوباره نمایش بخش ها @@ -252,5 +262,5 @@ یک یا چند منبع نادیده گرفته شد DAVx⁵: اتصال امن - DAVx⁵ با یک گواهی ناشناخته مواجه شده است. آیا می خواهید به آن اعتماد کنید؟ + DAVx⁵ با یک گواهی ناشناخته مواجه شده است. آیا می‌خواهید به آن اعتماد کنید؟ diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 734aaeae9..82f9d32ea 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -9,7 +9,7 @@ Envoyer Débogage Autres messages importants - Syncronisation + Synchronisation Erreurs de synchronisation Erreurs importantes qui bloquent la synchronisation, telles que des réponses inattendues du serveur Avertissements de synchronisation @@ -24,14 +24,14 @@ Désactiver pour DAVx⁵ Ne plus afficher Pas maintenant - Open-Source Information + Information sur l\'open-source Nous sommes heureux que vous utilisiez DAVx⁵, qui est un logiciel open-source (GPLv3). Parce que développer DAVx⁵ est un travail difficile, qui nous a pris de nombreuses heures, nous vous invitons à faire un don. Faire un don Plus tard Plus d\'informations L\'application OpenTasks n\'est pas installée Pour synchroniser les tâches, l\'application gratuite OpenTasks est nécessaire (elle ne l\'est pas pour les contacts et les événements). - Après l\'installation OpenTasks, vous devez RE-INSTALLER DAVx⁵ et ajoutez vos comptes à nouveau (bug Android). + Après l\'installation d\'OpenTasks, vous devez RÉ-INSTALLER DAVx⁵ et ajoutez vos comptes à nouveau (bug Android). Installer OpenTasks Librairies @@ -50,6 +50,8 @@ Adaptateur de synchronisation CalDAV/CardDAV À propos / Licence Commentaire pour la version Beta + Veuillez installer un client mail + Veuillez installer un navigateur Paramètres Actualités & mises à jour Liens externes @@ -58,6 +60,7 @@ Foire aux questions Aide/Forum Faire un don + Politique de confidentialité Pas de connectivité Internet. Android ne pourra pas exécuter la synchronisation. Bienvenue sur DAVx⁵!\n\nVous pouvez maintenant ajouter un compte CalDAV ou CardDAV. La synchronisation automatique globale est désactivée diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 52555d7cd..da30abb57 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -25,7 +25,7 @@ Не показывать снова Не сейчас Open-Source информация - Мы гордимся, что вы используете приложение с открытым исходным кодом DAVx⁵. Его разработка и поддержка отнимает очень много сил и времени. Мы будем счастливы, если вы поддержите наш проект. + Мы гордимся, что вы используете приложение с открытым исходным кодом DAVx⁵. Его разработка и поддержка отнимает очень много времени и сил. Мы будем счастливы, если вы поддержите наш проект. Поддержать проект Возможно, позже Дополнительная информация @@ -174,8 +174,8 @@ Ограничение WiFi SSID Будет синхронизироваться только %s Будет синхронизироваться только более %s (требуются активированные службы определения местоположения) - Все соединения WiFi будут использоваться - Названия (SSID), разделенные запятыми разрешенных сетей Wi-Fi (оставьте пустым для всех) + Будут использоваться все WiFi-подключения + Имена (SSID) разрешенных сетей WiFi, разделенные запятыми (оставьте пустым для всех) Для чтения имен WiFi требуются разрешение на геолокацию и постоянно активированные службы определения местоположения Дополнительная информация (FAQ) Аутентификация @@ -203,7 +203,7 @@ Напоминание по умолчанию за %d минут до события Напоминания по умолчанию не создаются - Если напоминания по умолчанию должны быть созданы для событий без напоминания: это желаемое количество минут до события. Оставьте пустым, чтобы отключить напоминания по умолчанию. + Для событий без напоминания введите желаемое количество минут для получения уведомления. Оставьте пустым, чтобы не использовать напоминания по умолчанию. Управление цветами календаря Цвета календаря управляются DAVx⁵ Цвета календаря не управляются DAVx⁵ diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index a59f80f25..269af924e 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -50,6 +50,8 @@ CalDAV/CardDAV 同步器 关于 / 许可 测试版反馈 + 请安装邮件客户端 + 请安装网页浏览器 设置 最新消息 外部链接 @@ -58,6 +60,8 @@ 常见问题 帮助 / 论坛 捐助 + 隐私政策 + 无网络连接。Android 将不会运行同步。 欢迎使用 DAVx⁵!\n\n请开始增加 CalDAV/CardDAV 账户。 系统全局自动同步已禁用 启用 @@ -188,6 +192,12 @@ %d 天前的日程不会被同步 超过这个数字的天数的旧日程将会被忽略(可以为 0)。留空则同步所有日程。 + 默认提醒 + + 默认事件开始前 %d 分钟提醒 + + 默认提醒未创建 + 当没有提醒的事件需增加默认提醒时,事件开始前多少分钟触发提醒。留空以禁用默认提醒。 管理日历颜色 日历颜色由 DAVx⁵ 设置 日历颜色不由 DAVx⁵ 设置 @@ -196,6 +206,10 @@ 不同步日历事件颜色 CardDAV 联系人分组方式 + + 按 VCard 文件分组 + 按联系人分类分组 + 修改分组方式 创建通讯录 -- GitLab From c9dab31067a186a6d4ca33cd742fedc8465ecf29 Mon Sep 17 00:00:00 2001 From: Ricki Hirner Date: Fri, 24 Jan 2020 20:21:25 +0100 Subject: [PATCH 007/783] Webcal calendars: use UrlUtils.equals to find matching calendar; update dependencies --- app/build.gradle | 10 +++++----- .../bitfire/davdroid/ui/account/WebcalFragment.kt | 15 +++++++++------ 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 2205b6c87..e5e04d104 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -112,21 +112,21 @@ dependencies { implementation 'androidx.appcompat:appcompat:1.1.0' implementation 'androidx.cardview:cardview:1.0.0' implementation 'androidx.constraintlayout:constraintlayout:1.1.3' - implementation 'androidx.fragment:fragment-ktx:1.1.0' - implementation 'androidx.lifecycle:lifecycle-extensions:2.1.0' - implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.1.0' + implementation 'androidx.fragment:fragment-ktx:1.2.0' + implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0' + implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0' implementation 'androidx.paging:paging-runtime-ktx:2.1.1' implementation 'androidx.preference:preference:1.1.0' implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0-alpha03' implementation 'com.google.android:flexbox:1.1.0' - implementation 'com.google.android.material:material:1.2.0-alpha03' + implementation 'com.google.android.material:material:1.2.0-alpha04' def room_version = '2.2.3' implementation "androidx.room:room-runtime:$room_version" implementation "androidx.room:room-ktx:$room_version" kapt "androidx.room:room-compiler:$room_version" - implementation 'com.gitlab.bitfireAT:dav4jvm:1.0' + implementation 'com.gitlab.bitfireAT:dav4jvm:1.0.1' implementation 'com.jaredrummler:colorpicker:1.1.0' implementation('com.mikepenz:aboutlibraries:7.1.0') implementation "com.squareup.okhttp3:okhttp:${versions.okhttp}" diff --git a/app/src/main/java/at/bitfire/davdroid/ui/account/WebcalFragment.kt b/app/src/main/java/at/bitfire/davdroid/ui/account/WebcalFragment.kt index fe980c48c..d2fccd01f 100644 --- a/app/src/main/java/at/bitfire/davdroid/ui/account/WebcalFragment.kt +++ b/app/src/main/java/at/bitfire/davdroid/ui/account/WebcalFragment.kt @@ -192,7 +192,7 @@ class WebcalFragment: CollectionsFragment() { value = null } } - val subscribedUrls = object: MediatorLiveData>() { + val subscribedUrls = object: MediatorLiveData>() { var provider: ContentProviderClient? = null var observer: ContentObserver? = null @@ -244,19 +244,19 @@ class WebcalFragment: CollectionsFragment() { @Transaction private fun queryCalendars(provider: ContentProviderClient) { // query subscribed URLs from Android calendar list - val subscriptions = mutableMapOf() + val subscriptions = mutableMapOf() provider.query(Calendars.CONTENT_URI, arrayOf(Calendars._ID, Calendars.NAME),null, null, null)?.use { cursor -> while (cursor.moveToNext()) cursor.getString(1)?.let { rawName -> HttpUrl.parse(rawName)?.let { url -> - subscriptions[url] = cursor.getLong(0) + subscriptions[cursor.getLong(0)] = url } } } // update "sync" field in database accordingly (will update UI) db.collectionDao().getByServiceAndType(serviceId, Collection.TYPE_WEBCAL).forEach { webcal -> - val newSync = subscriptions.keys + val newSync = subscriptions.values .any { webcal.source?.let { source -> UrlUtils.equals(source, it) } ?: false } if (newSync != webcal.sync) db.collectionDao().update(webcal.copy(sync = newSync)) @@ -278,8 +278,11 @@ class WebcalFragment: CollectionsFragment() { fun unsubscribe(webcal: Collection) { workerHandler.post { - subscribedUrls.value?.get(webcal.source)?.let { id -> - // delete subscription from Android calendar list + // find first matching source (Webcal) URL + subscribedUrls.value?.entries?.firstOrNull { (id, source) -> + UrlUtils.equals(source, webcal.source!!) + }?.key?.let { id -> + // delete first matching subscription from Android calendar list calendarProvider.value?.delete(Calendars.CONTENT_URI, "${Calendars._ID}=?", arrayOf(id.toString())) } -- GitLab From 53f35d5ee3a83e1c7ab95f3467c8cb78e214a2ee Mon Sep 17 00:00:00 2001 From: Ricki Hirner Date: Thu, 20 Feb 2020 17:50:51 +0100 Subject: [PATCH 008/783] Event sync: delete exceptions from events when events are mass-deleted, too --- .../davdroid/resource/LocalCalendar.kt | 23 +++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/at/bitfire/davdroid/resource/LocalCalendar.kt b/app/src/main/java/at/bitfire/davdroid/resource/LocalCalendar.kt index a8c8d2de8..c6b6ee2a7 100644 --- a/app/src/main/java/at/bitfire/davdroid/resource/LocalCalendar.kt +++ b/app/src/main/java/at/bitfire/davdroid/resource/LocalCalendar.kt @@ -142,10 +142,25 @@ class LocalCalendar private constructor( arrayOf(id.toString())) } - override fun removeNotDirtyMarked(flags: Int) = - provider.delete(eventsSyncURI(), - "${Events.CALENDAR_ID}=? AND NOT ${Events.DIRTY} AND ${Events.ORIGINAL_ID} IS NULL AND ${LocalEvent.COLUMN_FLAGS}=?", - arrayOf(id.toString(), flags.toString())) + override fun removeNotDirtyMarked(flags: Int): Int { + var deleted = 0 + // list all non-dirty events with the given flags and delete every row + its exceptions + provider.query(eventsSyncURI(), arrayOf(Events._ID), + "${Events.CALENDAR_ID}=? AND NOT ${Events.DIRTY} AND ${Events.ORIGINAL_ID} IS NULL AND ${LocalEvent.COLUMN_FLAGS}=?", + arrayOf(id.toString(), flags.toString()), null)?.use { cursor -> + val batch = BatchOperation(provider) + while (cursor.moveToNext()) { + val id = cursor.getLong(0) + // delete event and possible exceptions (content provider doesn't delete exceptions itself) + batch.enqueue(BatchOperation.Operation( + ContentProviderOperation.newDelete(eventsSyncURI()) + .withSelection("${Events._ID}=? OR ${Events.ORIGINAL_ID}=?", arrayOf(id.toString(), id.toString())) + )) + } + deleted = batch.commit() + } + return deleted + } override fun forgetETags() { val values = ContentValues(1) -- GitLab From 62c0dfbaee497500152c926dc7bfca02d3310e2b Mon Sep 17 00:00:00 2001 From: Ricki Hirner Date: Thu, 20 Feb 2020 17:56:00 +0100 Subject: [PATCH 009/783] Update support libraries --- app/build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index e5e04d104..1df432712 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -112,7 +112,7 @@ dependencies { implementation 'androidx.appcompat:appcompat:1.1.0' implementation 'androidx.cardview:cardview:1.0.0' implementation 'androidx.constraintlayout:constraintlayout:1.1.3' - implementation 'androidx.fragment:fragment-ktx:1.2.0' + implementation 'androidx.fragment:fragment-ktx:1.2.2' implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0' implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0' implementation 'androidx.paging:paging-runtime-ktx:2.1.1' @@ -121,7 +121,7 @@ dependencies { implementation 'com.google.android:flexbox:1.1.0' implementation 'com.google.android.material:material:1.2.0-alpha04' - def room_version = '2.2.3' + def room_version = '2.2.4' implementation "androidx.room:room-runtime:$room_version" implementation "androidx.room:room-ktx:$room_version" kapt "androidx.room:room-compiler:$room_version" -- GitLab From 7bdb5c5a97d56af0d01ea29a675b52ec469d7ecd Mon Sep 17 00:00:00 2001 From: Ricki Hirner Date: Thu, 20 Feb 2020 18:12:57 +0100 Subject: [PATCH 010/783] Update cert4android --- cert4android | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cert4android b/cert4android index 1488e39a6..7d0a6e9de 160000 --- a/cert4android +++ b/cert4android @@ -1 +1 @@ -Subproject commit 1488e39a66c33c764cb9bf3356c94267196fa48b +Subproject commit 7d0a6e9de29441a8603af4f366e298afb495891c -- GitLab From ae4c6e94c823332c20e089dad4590a8ae51c3686 Mon Sep 17 00:00:00 2001 From: Ricki Hirner Date: Thu, 20 Feb 2020 18:15:50 +0100 Subject: [PATCH 011/783] Fetch translations from Transifex --- app/src/main/res/values-cs/strings.xml | 180 +++++++++++++++--- app/src/main/res/values-fi/strings.xml | 29 +++ app/src/main/res/values-fr/strings.xml | 11 ++ app/src/main/res/values-it/strings.xml | 40 ++++ app/src/main/res/values-sk/strings.xml | 17 ++ app/src/main/res/values-uk/strings.xml | 18 +- .../metadata/android/cs/full_description.txt | 5 + .../metadata/android/cs/short_description.txt | 1 + 8 files changed, 272 insertions(+), 29 deletions(-) create mode 100644 fastlane/metadata/android/cs/full_description.txt create mode 100644 fastlane/metadata/android/cs/short_description.txt diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index cd511a1be..75f74b91b 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -2,42 +2,80 @@ DAVx⁵ + DAVx⁵ adresář kontaktů + Adresáře kontaktů Pomoc Spravovat účty Odeslat + Ladění + Ostatní důležité zprávy + Synchronizace + Chyby synchronizace + Důležité chyby, které zastaví synchronizaci, jako např. neočekávané odpovědi ze serveru + Varování ohledně synchronizace + Nefatální problémy při synchronizaci jako například některé neplatné soubory + Chyby sítě a vstupu/výstupu + Překročení časových limitů, problémy se spojením, atd. (často dočasné) + Automatická synchronizace + %s firmware často blokuje automatickou synchronizaci. V takovém případě umožněte automatickou synchronizaci v nastavení Android. + Naplánovaná synchronizace + Vaše zařízení omezuje DAVx⁵ synchronizaci. Pro vynucení běžných intervalů synchronizace DAVx⁵, vypněte „optimalizaci akumulátoru“. Vypnout pro DAVx⁵ Již nezobrazovat - Open Source informace + Nyní ne + Informace o open source Jsme velice rádi že používáte DAVx⁵, software s otevřeným zdrojovým kódem (GPLv3). Vývoj této aplikace je náročný a trval již několik tisíc hodin, velice nás potěší přispějete-li na jeho vývoj. Zobrazit stránku pro obdarování Možná později + Další informace OpenTasks není nainstalován + Pro synchronizaci úkolů je zapotřebí bezplatné aplikace OpenTasks (není potřebná pro kontakty/události). Po instalaci OpenTasks musíte PŘEINSTALOVAT DAVx⁵ a přidat znovu své účty (Android chyba). Nainstalovat OpenTasks - Tento program je distribuován BEZ JAKÉKOLIV ZÁRUKY. Je to volně dostupný software a lze jej za určitých podmínek dále distribuovat. + Knihovny + Verze %1$s (%2$d) + Sestaveno %s + Tuto verzi je možné šířit pouze přes Google Play. + Na tento program nejsou poskytovány ŽÁDNÉ ZÁRUKY. Jedná se o svobodný software a jeho šíření dál je vítáno, ovšem za podmínky, že stejné svobody zůstanou zachovány i všem dalším příjemcům. + Nedaří se vytvořit soubor pro záznam událostí + Zaznamenávání událostí v DAVx⁵ + Nyní jsou zaznamenávány všechny aktivity v DAVx⁵ + Záznam událostí odesílání Otevřít panel navigace Zavřít panel navigace CalDAV/CardDAV adapter synchronizace O aplikaci / Licence + Zpětná vazba k vývojové verzi + Nainstalujte si e-mailového klienta + Nainstalujte si webový prohlížeč Nastavení - Novinky & aktualizace + Novinky a aktualizace Externí odkazy Webová stránka + Ručně FAQ + Nápověda / diskuzní fóra Obdarovat + Zásady ochrany soukromí + Bez připojení k Internetu. Android synchronizaci nespustí. Vítejte v aplikaci DAVx⁵!\n\nNyní můžete přidat CalDAV/CardDAV účet. + Automatická synchronizace z celého systému je vypnutá + Zapnout - Vyhledání služby selhalo - Nelze obnovit seznam sbírky + Vyhledání služby se nezdařilo + Nedaří se znovu načíst seznam sbírky Nastavení Ladění Zobrazit ladící informace - Zobrazit/sdílet software a detaily konfigurace + Zobrazit/sdílet software a podrobnosti nastavení + Podrobnější zaznamenávání událostí + Zaznamenávání událostí je aktivní + Zaznamenávání událostí je vypnuté Připojení Přepsat proxy nastavení Použít vlastní proxy nastavení @@ -52,10 +90,19 @@ Resetovat důvěryhodnost všech vlastních certifikátů Všechny vlastní certifikáty byly resetovány Uživatelské prostředí - Resetovat nápovědu + Nastavení oznamování + Spravovat kanály oznamování a jejich nastavení + Znovu zobrazovat rady Znovu povolí vypnuté texty nápovědy Budou zobrazovány všechny texty nápovědy + CardDAV + CalDAV + Webcal + (Zatím) zde nejsou žádné adresáře kontaktů. + (Zatím) zde nejsou žádné kalendáře. + Nejsou zde žádná přihlášení k odběru kalendáře (zatím). + Seznam ze serveru načtete znovu přejetím prstem dolů. Synchronizovat nyní Probíhá synchronizace Nastavení účtu @@ -65,51 +112,79 @@ Smazat účet Opravdu smazat účet? Všechny místní kopie adresáře, kalendářů a úkolů budou smazány. - Obnovit seznam adresářů - Vytvořit nový adresář - Obnovit seznam kalendářů + synchronizovat tuto sadu + pouze pro čtení + kalendář + seznam úkolů + Znovu načíst seznam adresářů kontaktů + Vytvořit nový adresář kontaktů + Znovu načíst seznam kalednářů Vytvořit nový kalendář + Nenalezena žádná aplikace, která by umožňovala webcal + Nainstalovat ICSx⁵ Přidat účet - Přihlášení s emailovou adresou - Emailová adresa - Vyžadován platný email + Přihlášení se e-mailovou adresou + E-mailová adresa + Vyžadován platný e-mail Heslo Vyžadováno heslo Přihlášení s URL a uživatelským jménem - URL musí začínat na http(s):// - Vyžadováno hostname + Je třeba, aby URL začínalo na http(s):// + Je třeba, aby URL adresa začínala na https:// + Vyžadován název hostitele Uživatelské jméno Vyžadováno uživatelské jméno - Základní URL - Login + Základ URL + Přihlásit pomocí URL a klientského certifikátu + Vybrat certifikát + Přihlášení Zpět Vytvořit účet - Jméno účtu - Pro jméno účtu použijte svou emailovou adresu, protože Android bude brát jméno účtu jako údaj pro ORGANIZÁTORA vytvořených událostí. Nelze mít dva účty stejného jména. + Název účtu + Pro jméno účtu použijte svou e-mailovou adresu, protože Android bude brát jméno účtu jako údaj pro ORGANIZÁTORA vytvořených událostí. Nelze mít dva účty stejného jména. Metoda seskupování kontaktů: - Vyžadováno jméno účtu + Je třeba zadat název pro účet + Tento název účtu už je používán někým jiným Účet nelze vytvořit - Vyhledání konfigurace + Zjišťování nastavení Chvíli strpení, probíhá dotazování serveru… - Nelze nalézt službu CalDAV nebo CardDAV. + Nedaří se nalézt službu CalDAV nebo CardDAV. + Zobrazit podrobnosti Nastavení: %s Synchronizace Interval synchronizace kontaktů - Pouze manuálně + Pouze ručně Každých %d minut a ihned při lokálních změnách Interval synchronizace kalendáře Interval synchronizace úkolů + + Pouze ručně + Každých 15 minut + Každých 30 minut + Každou hodinu + Každé 2 hodiny + Každé 4 hodiny + Jednou za den + Synchronizovat pouze přes WiFi Synchronizace omezena na WiFi připojení Druh připojení není brán v potaz + Omezení na názvy (SSID) WiFi sítí + Bude synchronizovat pouze přes %s + Bude synchronizovat pouze přes %s (vyžaduje aktivní lokační služby) + Bude použito libovolné WiFi připojení + Čárkou oddělovaný seznam názvů (SSID) WiFi sítí, přes které synchronizovat (pokud omezovat nechcete, nevyplňujte) + Pro čtení názvů WiFi sítí je zapotřebí oprávnění k Umístění a trvale aktivované lokační služby. + Další informace (ČKD) Ověření Uživatelské jméno - Zadat uživatelské jméno + Zadejte uživatelské jméno: Heslo Aktualizovat heslo dle svého serveru. - Vložit své heslo: + Zadejte své heslo: + Alternativní název klientského certifikátu CalDAV Časový limit pro staré události Synchronizovat všechny události @@ -120,30 +195,79 @@ Ignorovat události starší než %d dnů Události z minulosti starší než vyznačený počet dnů budou ignorovány (lze zadat 0). Ponechte prázdné pro synchronizaci všech událostí. + Výchozí připomínka + + Výchozí připomínka jednu minutu před událostí + Výchozí připomínka %d minuty před událostí + Výchozí připomínka %d minut před událostí + Výchozí připomínka %d minuty před událostí + + Nejsou vytvořené žádné výchozí připomínky + Pokud mají být pro události bez připomínky vytvořeny výchozí připomínky: požadovaný počet minut před událostí. Pro vypnutí výchozích připomínek nevyplňujte. Spravovat barvy kalendářů Barvy kalendářů spravuje DAVx⁵ Barvy kalendářů nespravuje DAVx⁵ + Podpora pro barvy událostí + Synchronizovat barvy událostí + Nesynchronizovat barvy událostí CardDAV Metoda seskupování kontaktů + + Skupiny jsou zvlášt vCards vizitky + Skupiny jsou kategorie u jednotlivých kontaktů + + Změnit metodu seskupování Vytvořit adresář Můj adresář + Vytvořit kalendář + Časová zóna + Je třeba zadat časovou zónu + Možné položky kalendáře + Události + Úkoly + Poznámky / deník Kombinovaná (události a úkoly) + Barva Vytváření sbírky + Nadpis Nadpis je vyžadován + Popis + volitelné + Umístění úložiště Vytvořit Smazat sbírku - Jste si jisti? + Opravdu to chcete? + Tato sada (%s) a všechna její data budou nadobro odebrána. + Tato data by měla být smazána ze serveru. Mazání sbírky + Vynutit pouze pro čtení + Vlastnosti + Adresa (URL): + Zkopírovat URL adresu Došlo k chybě. Došlo k HTTP chybě. - Došlo k I/O chybě. - Zobrazit detaily + Došlo k chybě vstupu/výstupu. + Zobrazit podrobnosti Ladící informace + Záznamy událostí jsou připojeny k této zprávě (vyžaduje podporu příloh u aplikace, do které bude obdrženo). + Adresář kontaktů, který je pouze pro čtení DAVx⁵ oprávnění Vyžadována dodatečná oprávnění + Příliš stará verze OpenTasks + Požadovaná verze: %1$s (stávající %2$s) + Ověření se nezdařilo (zkontrolujte přihlašovací údaje) + Chyba sítě nebo vstupu/výstupu – %s + Chyba HTTP serveru – %s + Chyba místního úložiště – %s + Opakovat + Zobrazit položku + Ze serveru obdržen neplaný kontakt + Ze serveru obdržena neplatná událost + Ze serveru obdržen neplatný úkol + Ignoruje se jeden či více neplatný prostředků DAVx⁵: Zabezpečení připojení DAVx⁵ nalezl neznámý certifikát. Chcete mu důvěřovat? diff --git a/app/src/main/res/values-fi/strings.xml b/app/src/main/res/values-fi/strings.xml index 258fa4372..4a7d1bd2c 100644 --- a/app/src/main/res/values-fi/strings.xml +++ b/app/src/main/res/values-fi/strings.xml @@ -1,7 +1,36 @@ + DAVx⁵ + DAVx⁵ Osoitekirja + Osoitekirjat + Apua + Hallitse tilejä + Lähetä + Debuggaus + Muut tärkeät viestit + Synkronointi + Synkronoinnin virheet + Huomattavat virheet jotka estävät synkronoinnin kuten palvelimen odottamattomat vastaukset + Synkronoinnin varoitukset + Ei-kohtalokkaat synkronoinnin ongelmat kuten tietyt virheelliset tiedostot + Verkko ja I/O virheet + Aikakatkaisut, yhteysvirheet, yms. (usein väliaikaisia) + Automaattinen synkronointi + %s laiteohjelmisto usein estää automaattisen synkronoinnin. Tällaisessa tapauksessa, salli automaattinen synkronointi Androidin asetuksista. + Ajastettu synkronointi + Laitteesi estää DAVx⁵ synkronoinnin. Salliaksesi tavalliset DAVx⁵ synkronointivälit, sammuta \"akun optimointi\". + Sammuta DAVx⁵:lle. + Älä näytä uudelleen + Ei nyt + Avoimen lähdekoodin tiedot + Olemme iloisia että käytät DAVx⁵:ä joka on avoimen lähdekoodin ohjelma (GPLv3). Koska DAVx⁵:n kehitys on vaativaa työtä ja on vaatinut tuhansia tunteja, harkitsethan lahjoituksen antamista, kiitos. + Näytä lahjoitussivu + Ehkä myöhemmin + Lisää tietoa + OpenTasks ei ole asennettu + Synkronoidaksesi tehtävät, avoin ohjelma OpenTasks vaaditaan. (Ei vaadita yhteystiedoille/tapahtumille.) diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 82f9d32ea..6aba7921f 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -193,6 +193,13 @@ Les événements de plus de %d jours passés seront ignorés Les événements antérieurs à ce nombre de jours seront ignorés (peut être 0). Laissez vide pour synchroniser tous les événements. + Rappel par défaut + + Rappel par défaut une minute avant l\'événement + Rappel par défaut %d minutes avant les événements + + Aucun rappel par défaut + Si des rappels par défaut doivent être créé pour des événements sans rappel: le nombre de minutes avant l\'événement. Laisser blanc pour désactiver les rappels par défaut. Choisir couleur du calendrier Les couleurs de calendrier sont gérées par DAVx⁵ Les couleurs de calendrier ne sont pas gérées par DAVx⁵ @@ -201,6 +208,10 @@ Ne pas synchroniser la couleur associée aux événements CardDAV Méthode pour les contacts de type groupe + + Les groupes sont des VCards indépendantes + Les groupes sont des catégories pour chacun des contacts + Changer la méthode de groupe Créer un carnet d\'adresses diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index 80009a78e..6e10e2a39 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -50,6 +50,8 @@ CalDAV/CardDAV adattatore di sincronizzazione Informazioni / Licenza Feedback sulla beta + Installare un client email + Installare un browser Web Impostazioni Notizie & aggiornamenti Link esterni @@ -58,6 +60,8 @@ Domande Frequenti Aiuto / Forum Donazione + Politica sulla riservatezza + Nessuna connessione Internet. Android non eseguirà la sincronizzazione. Benvenuto a DAVx⁵!\n\nÈ ora possibile aggiungere account CalDAV/CardDAV. La sincronizzazione automatica dell\'intero sistema è disabilitata Attiva @@ -189,6 +193,12 @@ Eventi più vecchi di %d giorni saranno ignorati Eventi più vecchi di questo numero di giorni verranno ignorati(può anche essere 0). Lasciare in bianco per sincronizzare tutti gli eventi. + Promemoria predefinito + + Promemoria predefinito un minuto prima dell\'evento + Promemoria predefinito %d minuti prima dell\'evento + + Nessun promemoria di default creato Cambia il colore del calendario I colori dei calendari sono gestiti da DAVx⁵ I colori dei calendari non sono gestiti da DAVx⁵ @@ -197,18 +207,38 @@ Non sincronizza colori eventi CardDAV Organizzazione dei gruppi di contatto + + I gruppi sono vCards separate + I gruppi sono categorie per ogni contatto + Cambia il metodo del gruppo Crea rubrica La mia rubrica + Crea calendario + Fuso orario + Fuso orario richiesto + Eventi + Attività + Note / diario Combinato (eventi e attività) + Colore Crea una raccolta + Titolo Il titolo è richiesto + Descrizione + opzionale + Percorso di archiviazione Crea Elimina raccolta Sei sicuro? + Questa raccolta (%s) e tutti i suoi dati saranno rimossi permanentemente. + Questi dati saranno cancellati dal server. Cancellazione della raccolta Forza sola lettura + Proprietà + Indirizzo (URL): + Copia URL Si è verificato un errore. Si è verificato un errore HTTP. @@ -216,12 +246,22 @@ Mostra dettagli Informazioni di debug + I log sono allegati a questo messaggio (richiede il supporto degli allegati nell\'app ricevente) Rubrica in sola lettura Autorizzazioni DAVx⁵ Autorizzazioni addizionali richieste OpenTasks troppo vecchia Versione richiesta: %1$s (attualmente %2$s) Autenticazione fallita (controlla credenziali login) + Errore di rete o di I/O – %s + Errore server HTTP – %s + Errore di archiviazione locale – %s + Riprova + Visualizza oggetto + Contatto non valido ricevuto dal server + Evento non valido ricevuto dal server + Attività non valida ricevuta dal server + Una o più risorse non valide ignorate DAVx⁵: sicurezza della connessione DAVx⁵ ha trovato un certificato sconosciuto. Ritenerlo affidabile? diff --git a/app/src/main/res/values-sk/strings.xml b/app/src/main/res/values-sk/strings.xml index 19ac648af..80f62361d 100644 --- a/app/src/main/res/values-sk/strings.xml +++ b/app/src/main/res/values-sk/strings.xml @@ -50,6 +50,8 @@ CalDAV/CardDAV Sync Adaptér O programe / Licencia Odozva na beta-verziu + Prosím, nainštalujte e-mailového klienta + Prosím, nainštalujte webový prehliadač Nastavenia Novinky & aktualizácie Externé odkazy @@ -58,6 +60,8 @@ FAQ Pomoc / Fóra Darovať + Zásady bezpečnosti + Žiadne pripojenie do internetu. Android nestpustí synchronizáciu. Vitajte v programe DAVx⁵!\n\nTeraz môžete pridať používateľský účet pre CalDAV/CardDAV . Automatická synchronizácia platná pre celý systém je zakázaná Povoliť @@ -191,6 +195,15 @@ Udalosti staršie ako %d dní budú ignorované Udalosti ktoré sú staršie ako tento počet dní budú ignorované (0 je povolená). Ponechajte prázdne aby ste synchronizovali všetky udalosti. + Prednastavená pripomienka + + Prednastavená pripomienka 1 minútu pred udalosťou + Prednastavená pripomienka minút pred udalosťou: %d + Prednastavená pripomienka minút pred udalosťou: %d + Prednastavená pripomienka minút pred udalosťou: %d + + Neboli vytvorené prednastavené pripomienky + Ak majú byť vytvorené prednastavené pripomienky pre udalosti bez pripomienok: počet minút pred udalosťou. Ponechajte prázdne na zrušenie prednastavených pripomienok. Spravovať farby kalendára Farby kalendára sú spravované DAVx⁵ Farby kalendáre nie sú nastavované DAVx⁵ @@ -199,6 +212,10 @@ Nesynchronizovať farby udalostí CardDAV Spôsob práce so skupinami kontaktov + + Skupiny sú osobitné vKarty + Skupiny sú kategórie na kontakt + Zmena spôsobu práce so skupinami Vytvoriť adresár diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index 39de1ee24..8d7bbd30e 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -50,6 +50,8 @@ Адаптер синхронізації CalDAV/CardDAV Про / Ліцензія Beta відгук + Будь ласка, встановіть клієнт електронної пошти + Будь ласка, встановіть веб-браузер Налаштування Новини та оновлення Зовнішні посилання @@ -58,6 +60,7 @@ Питання/Відповіді Допомога / Форуми Підтримка + Політика конфіденційності Відсутнє підключення до інтернету. Android не може розпочати синхронізацію. Вітаємо у DAVx⁵!\n\nТепер можете додавати облікові записи CalDAV/CardDAV. Автоматичну синхронізацію вимкнено зі сторони системи @@ -192,7 +195,16 @@ Події старші %d днів будуть проігноровані Події старші вказаного часу будуть проігноровані (може бути 0). Залиште порожнім, аби синхронізувати всі події. - Керування кольорами календаря + Нагадування за замовчуванням + + Типово нагадувати за хвилину до події + Типово нагадувати за %d хвилини до події + Типово нагадувати за %d хвилин до події + Типово нагадувати за %d хвилин до події + + Нагадування за замовчуванням не створюються + Якщо нагадування за замовчуванням створюються для подій без нагадування: бажана кількість хвилин до події. Залиште поле порожнім, щоб вимкнути нагадування за замовчуванням. + Керування кольорами Кольори календаря керуються DAVx⁵ Кольори календаря не керуються DAVx⁵ Підтримка кольорів подій @@ -200,6 +212,10 @@ Не синхронізувати кольори подій CardDAV Метод групування контактів + + Групи-це окремі записи + Групи є в категоріями в контактах + Змінити метод групування Створити адресну книгу diff --git a/fastlane/metadata/android/cs/full_description.txt b/fastlane/metadata/android/cs/full_description.txt new file mode 100644 index 000000000..b35681112 --- /dev/null +++ b/fastlane/metadata/android/cs/full_description.txt @@ -0,0 +1,5 @@ +DAVx⁵ je aplikace pro správu a synchronizaci CalDAV/CardDAV pro Android, která se nativně propojí s aplikacemi Android kalendář/kontakty. + +Použijte ji ve spojení s vlastním serverem nebo hostingem, kterému důvěřujete a uchovejte si tak kontrolu nad svými kontakty, událostmi a úkoly. + +Další informace a seznam vyzkoušených serverů/služeb navštivte webové stránky. \ No newline at end of file diff --git a/fastlane/metadata/android/cs/short_description.txt b/fastlane/metadata/android/cs/short_description.txt new file mode 100644 index 000000000..db0518b26 --- /dev/null +++ b/fastlane/metadata/android/cs/short_description.txt @@ -0,0 +1 @@ +Synchronizace CalDAV/CardDAV a klient \ No newline at end of file -- GitLab From 5622d743c36037e41c7b33ce1f2fd4dc082c755d Mon Sep 17 00:00:00 2001 From: Ricki Hirner Date: Thu, 20 Feb 2020 18:13:25 +0100 Subject: [PATCH 012/783] Version bump to 2.6.4 --- app/build.gradle | 4 ++-- fastlane/metadata/android/en-US/changelogs/331.txt | 4 ++++ 2 files changed, 6 insertions(+), 2 deletions(-) create mode 100644 fastlane/metadata/android/en-US/changelogs/331.txt diff --git a/app/build.gradle b/app/build.gradle index 1df432712..1d95df1a8 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -19,7 +19,7 @@ android { defaultConfig { applicationId "at.bitfire.davdroid" - versionCode 330 + versionCode 331 buildConfigField "long", "buildTime", System.currentTimeMillis() + "L" buildConfigField "boolean", "customCerts", "true" @@ -44,7 +44,7 @@ android { flavorDimensions "distribution" productFlavors { standard { - versionName "2.6.4-beta1-ose" + versionName "2.6.4-ose" } } diff --git a/fastlane/metadata/android/en-US/changelogs/331.txt b/fastlane/metadata/android/en-US/changelogs/331.txt new file mode 100644 index 000000000..20cc92fe1 --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/331.txt @@ -0,0 +1,4 @@ +* calendar sync: delete exceptions from events when events are mass-deleted, to +* events/tasks: improve handling of VALARM (REL=END, VALUE=DATE-TIME) +* Webcal calendars: use UrlUtils.equals to find matching calendar +* new translation and libraries -- GitLab From b3c7f1f9ef8eabe50bc5662783bbda09df932e7f Mon Sep 17 00:00:00 2001 From: Alex Baker Date: Thu, 27 Feb 2020 08:57:07 -0600 Subject: [PATCH 013/783] Move errors from EditTexts to TextInputLayouts --- .../layout/activity_create_address_book.xml | 4 +-- .../res/layout/activity_create_calendar.xml | 4 +-- .../main/res/layout/login_account_details.xml | 4 +-- .../res/layout/login_credentials_fragment.xml | 26 +++++++++---------- 4 files changed, 19 insertions(+), 19 deletions(-) diff --git a/app/src/main/res/layout/activity_create_address_book.xml b/app/src/main/res/layout/activity_create_address_book.xml index 39520f996..25ab187f4 100644 --- a/app/src/main/res/layout/activity_create_address_book.xml +++ b/app/src/main/res/layout/activity_create_address_book.xml @@ -34,14 +34,14 @@ android:layout_height="wrap_content" style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox" android:hint="@string/create_collection_display_name" + app:error="@{model.displayNameError}" app:layout_constraintHorizontal_weight="1" app:layout_constraintTop_toTopOf="parent" app:layout_constraintStart_toStartOf="parent"> + android:text="@={model.displayName}" /> + android:text="@={model.displayName}" /> + android:hint="@string/login_account_name" + app:error="@{details.nameError}"> diff --git a/app/src/main/res/layout/login_credentials_fragment.xml b/app/src/main/res/layout/login_credentials_fragment.xml index eb9893832..a29656915 100644 --- a/app/src/main/res/layout/login_credentials_fragment.xml +++ b/app/src/main/res/layout/login_credentials_fragment.xml @@ -62,13 +62,13 @@ android:layout_width="match_parent" android:layout_height="wrap_content" style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox" - android:hint="@string/login_email_address"> + android:hint="@string/login_email_address" + app:error="@{model.usernameError}"> @@ -77,13 +77,13 @@ android:layout_height="wrap_content" style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox" android:hint="@string/login_password" - app:passwordToggleEnabled="true"> + app:endIconMode="password_toggle" + app:error="@{model.passwordError}"> @@ -110,25 +110,25 @@ android:layout_width="match_parent" android:layout_height="wrap_content" style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox" - android:hint="@string/login_base_url"> + android:hint="@string/login_base_url" + app:error="@{model.baseUrlError}"> + android:hint="@string/login_user_name" + app:error="@{model.usernameError}"> @@ -136,15 +136,15 @@ android:layout_width="match_parent" android:layout_height="wrap_content" style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox" - app:passwordToggleEnabled="true" - android:hint="@string/login_password"> + android:hint="@string/login_password" + app:endIconMode="password_toggle" + app:error="@{model.passwordError}"> @@ -170,12 +170,12 @@ android:layout_width="match_parent" android:layout_height="wrap_content" style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox" - android:hint="@string/login_base_url"> + android:hint="@string/login_base_url" + app:error="@{model.baseUrlError}"> -- GitLab From 4498f9bf035f82c8a833051d6752e6b6df996465 Mon Sep 17 00:00:00 2001 From: Ricki Hirner Date: Tue, 25 Feb 2020 20:09:47 +0100 Subject: [PATCH 014/783] Update okhttp to 3.12.9 --- build.gradle | 2 +- ical4android | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index dfba67994..4f0658006 100644 --- a/build.gradle +++ b/build.gradle @@ -10,7 +10,7 @@ buildscript { ext.versions = [ dokka: '0.10.0', kotlin: '1.3.61', - okhttp: '3.12.8' + okhttp: '3.12.9' ] repositories { diff --git a/ical4android b/ical4android index 640fc4111..2865a22aa 160000 --- a/ical4android +++ b/ical4android @@ -1 +1 @@ -Subproject commit 640fc4111999851ca282ccfa3aa02245014e7164 +Subproject commit 2865a22aae177ca860f6fbf1a9fc77392ae7a321 -- GitLab From 224c92cc87341c6b7a8f9c1b61968090f036d99a Mon Sep 17 00:00:00 2001 From: Ricki Hirner Date: Wed, 26 Feb 2020 16:51:56 +0100 Subject: [PATCH 015/783] Update gradle version and Android plugin; dependencies --- app/build.gradle | 2 +- app/src/main/res/xml/network_security_config.xml | 2 +- build.gradle | 2 +- cert4android | 2 +- gradle/wrapper/gradle-wrapper.properties | 4 ++-- ical4android | 2 +- vcard4android | 2 +- 7 files changed, 8 insertions(+), 8 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 1d95df1a8..4fdc203b8 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -119,7 +119,7 @@ dependencies { implementation 'androidx.preference:preference:1.1.0' implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0-alpha03' implementation 'com.google.android:flexbox:1.1.0' - implementation 'com.google.android.material:material:1.2.0-alpha04' + implementation 'com.google.android.material:material:1.2.0-alpha05' def room_version = '2.2.4' implementation "androidx.room:room-runtime:$room_version" diff --git a/app/src/main/res/xml/network_security_config.xml b/app/src/main/res/xml/network_security_config.xml index 35ca6a704..0e574f0e5 100644 --- a/app/src/main/res/xml/network_security_config.xml +++ b/app/src/main/res/xml/network_security_config.xml @@ -11,7 +11,7 @@ - + diff --git a/build.gradle b/build.gradle index 4f0658006..fd2a04d3b 100644 --- a/build.gradle +++ b/build.gradle @@ -18,7 +18,7 @@ buildscript { google() } dependencies { - classpath 'com.android.tools.build:gradle:3.5.3' + classpath 'com.android.tools.build:gradle:3.6.0' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${versions.kotlin}" classpath "org.jetbrains.dokka:dokka-gradle-plugin:${versions.dokka}" } diff --git a/cert4android b/cert4android index 7d0a6e9de..7e60c03f6 160000 --- a/cert4android +++ b/cert4android @@ -1 +1 @@ -Subproject commit 7d0a6e9de29441a8603af4f366e298afb495891c +Subproject commit 7e60c03f6740e2354e14dd2a958c981eee315756 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index dcc77281b..64dfdbc73 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Wed Apr 17 22:31:47 CEST 2019 +#Wed Feb 26 16:47:37 CET 2020 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-5.5.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-all.zip diff --git a/ical4android b/ical4android index 2865a22aa..d8772b42b 160000 --- a/ical4android +++ b/ical4android @@ -1 +1 @@ -Subproject commit 2865a22aae177ca860f6fbf1a9fc77392ae7a321 +Subproject commit d8772b42b36435b95545a8c4fa93dc73e1e02c35 diff --git a/vcard4android b/vcard4android index bf4ea05d7..5429867ce 160000 --- a/vcard4android +++ b/vcard4android @@ -1 +1 @@ -Subproject commit bf4ea05d74ca8ee12face85af857718d610590e6 +Subproject commit 5429867cec6e13f6b2076e3d68d639877a270a68 -- GitLab From e30c41828fa0842f93311152c7919bb3a064900a Mon Sep 17 00:00:00 2001 From: Ricki Hirner Date: Tue, 3 Mar 2020 14:25:53 +0100 Subject: [PATCH 016/783] Update gradle plugin, okhttp --- build.gradle | 4 ++-- cert4android | 2 +- ical4android | 2 +- vcard4android | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/build.gradle b/build.gradle index fd2a04d3b..8684b162c 100644 --- a/build.gradle +++ b/build.gradle @@ -10,7 +10,7 @@ buildscript { ext.versions = [ dokka: '0.10.0', kotlin: '1.3.61', - okhttp: '3.12.9' + okhttp: '3.12.10' ] repositories { @@ -18,7 +18,7 @@ buildscript { google() } dependencies { - classpath 'com.android.tools.build:gradle:3.6.0' + classpath 'com.android.tools.build:gradle:3.6.1' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${versions.kotlin}" classpath "org.jetbrains.dokka:dokka-gradle-plugin:${versions.dokka}" } diff --git a/cert4android b/cert4android index 7e60c03f6..aa9b9cfc3 160000 --- a/cert4android +++ b/cert4android @@ -1 +1 @@ -Subproject commit 7e60c03f6740e2354e14dd2a958c981eee315756 +Subproject commit aa9b9cfc32d9fdef8a3bf385f5cca8a9cd673fe1 diff --git a/ical4android b/ical4android index d8772b42b..9a7dabeb3 160000 --- a/ical4android +++ b/ical4android @@ -1 +1 @@ -Subproject commit d8772b42b36435b95545a8c4fa93dc73e1e02c35 +Subproject commit 9a7dabeb3898f2862c2d568416436a06cb3a855c diff --git a/vcard4android b/vcard4android index 5429867ce..4e258cd0f 160000 --- a/vcard4android +++ b/vcard4android @@ -1 +1 @@ -Subproject commit 5429867cec6e13f6b2076e3d68d639877a270a68 +Subproject commit 4e258cd0f4e3faf009ca5782303d547182c086ca -- GitLab From 89183820035e78f8550fe4ae181d2dad12aced3c Mon Sep 17 00:00:00 2001 From: Ricki Hirner Date: Tue, 3 Mar 2020 16:26:57 +0100 Subject: [PATCH 017/783] Use requireView() instead of view!! and requireActivity() instead of activity!! --- .../java/at/bitfire/davdroid/ui/AppSettingsActivity.kt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/at/bitfire/davdroid/ui/AppSettingsActivity.kt b/app/src/main/java/at/bitfire/davdroid/ui/AppSettingsActivity.kt index 0ca133449..130b86e18 100644 --- a/app/src/main/java/at/bitfire/davdroid/ui/AppSettingsActivity.kt +++ b/app/src/main/java/at/bitfire/davdroid/ui/AppSettingsActivity.kt @@ -107,7 +107,7 @@ class AppSettingsActivity: AppCompatActivity() { summary = host true } catch(e: URISyntaxException) { - Snackbar.make(view!!, e.localizedMessage, Snackbar.LENGTH_LONG).show() + Snackbar.make(requireView(), e.localizedMessage, Snackbar.LENGTH_LONG).show() false } } @@ -149,12 +149,12 @@ class AppSettingsActivity: AppCompatActivity() { settings.remove(StartupDialogFragment.HINT_AUTOSTART_PERMISSIONS) settings.remove(StartupDialogFragment.HINT_BATTERY_OPTIMIZATIONS) settings.remove(StartupDialogFragment.HINT_OPENTASKS_NOT_INSTALLED) - Snackbar.make(view!!, R.string.app_settings_reset_hints_success, Snackbar.LENGTH_LONG).show() + Snackbar.make(requireView(), R.string.app_settings_reset_hints_success, Snackbar.LENGTH_LONG).show() } private fun resetCertificates() { - if (CustomCertManager.resetCertificates(activity!!)) - Snackbar.make(view!!, getString(R.string.app_settings_reset_certificates_success), Snackbar.LENGTH_LONG).show() + if (CustomCertManager.resetCertificates(requireActivity())) + Snackbar.make(requireView(), getString(R.string.app_settings_reset_certificates_success), Snackbar.LENGTH_LONG).show() } } -- GitLab From 6c0b555ec9c564c9dfc6a1062bafaa64c9d1e384 Mon Sep 17 00:00:00 2001 From: Ricki Hirner Date: Tue, 3 Mar 2020 18:57:50 +0100 Subject: [PATCH 018/783] lint --- app/src/main/java/at/bitfire/davdroid/DavUtils.kt | 1 + .../main/java/at/bitfire/davdroid/HttpClient.kt | 1 - .../main/java/at/bitfire/davdroid/log/Logger.kt | 2 +- .../bitfire/davdroid/settings/AccountSettings.kt | 2 ++ .../java/at/bitfire/davdroid/settings/Settings.kt | 15 ++++++++++----- .../davdroid/syncadapter/SyncAdapterService.kt | 2 +- .../java/at/bitfire/davdroid/ui/AboutActivity.kt | 6 +++--- .../at/bitfire/davdroid/ui/AccountListFragment.kt | 8 ++++---- .../davdroid/ui/CreateAddressBookActivity.kt | 4 ++-- .../bitfire/davdroid/ui/CreateCalendarActivity.kt | 6 +++--- .../davdroid/ui/CreateCollectionFragment.kt | 4 ++-- .../at/bitfire/davdroid/ui/DebugInfoActivity.kt | 4 ++-- .../davdroid/ui/DeleteCollectionFragment.kt | 10 +++++----- .../bitfire/davdroid/ui/StartupDialogFragment.kt | 4 ++-- .../davdroid/ui/account/AccountActivity.kt | 2 +- .../davdroid/ui/account/CollectionInfoFragment.kt | 4 ++-- .../davdroid/ui/account/CollectionsFragment.kt | 4 ++-- .../davdroid/ui/account/RenameAccountFragment.kt | 6 +++--- .../davdroid/ui/account/SettingsActivity.kt | 4 ++-- .../bitfire/davdroid/ui/account/WebcalFragment.kt | 2 +- .../davdroid/ui/setup/AccountDetailsFragment.kt | 6 +++--- .../davdroid/ui/setup/DavResourceFinder.kt | 2 +- .../ui/setup/DetectConfigurationFragment.kt | 14 +++++++------- cert4android | 2 +- ical4android | 2 +- vcard4android | 2 +- 26 files changed, 63 insertions(+), 56 deletions(-) diff --git a/app/src/main/java/at/bitfire/davdroid/DavUtils.kt b/app/src/main/java/at/bitfire/davdroid/DavUtils.kt index 700778773..e516f7e8a 100644 --- a/app/src/main/java/at/bitfire/davdroid/DavUtils.kt +++ b/app/src/main/java/at/bitfire/davdroid/DavUtils.kt @@ -27,6 +27,7 @@ import java.util.* */ object DavUtils { + @Suppress("FunctionName") fun ARGBtoCalDAVColor(colorWithAlpha: Int): String { val alpha = (colorWithAlpha shr 24) and 0xFF val color = colorWithAlpha and 0xFFFFFF diff --git a/app/src/main/java/at/bitfire/davdroid/HttpClient.kt b/app/src/main/java/at/bitfire/davdroid/HttpClient.kt index 8c562d409..f57703043 100644 --- a/app/src/main/java/at/bitfire/davdroid/HttpClient.kt +++ b/app/src/main/java/at/bitfire/davdroid/HttpClient.kt @@ -13,7 +13,6 @@ import android.os.Build import android.security.KeyChain import at.bitfire.cert4android.CustomCertManager import at.bitfire.dav4jvm.BasicDigestAuthHandler -import at.bitfire.dav4jvm.Constants import at.bitfire.dav4jvm.UrlUtils import at.bitfire.davdroid.log.Logger import at.bitfire.davdroid.model.Credentials diff --git a/app/src/main/java/at/bitfire/davdroid/log/Logger.kt b/app/src/main/java/at/bitfire/davdroid/log/Logger.kt index 1e6ade546..837519084 100644 --- a/app/src/main/java/at/bitfire/davdroid/log/Logger.kt +++ b/app/src/main/java/at/bitfire/davdroid/log/Logger.kt @@ -32,7 +32,7 @@ object Logger : SharedPreferences.OnSharedPreferenceChangeListener { private const val LOG_TO_FILE = "log_to_file" - val log = java.util.logging.Logger.getLogger("davx5") + val log: java.util.logging.Logger = java.util.logging.Logger.getLogger("davx5") private lateinit var context: Context private lateinit var preferences: SharedPreferences diff --git a/app/src/main/java/at/bitfire/davdroid/settings/AccountSettings.kt b/app/src/main/java/at/bitfire/davdroid/settings/AccountSettings.kt index 737e333ae..453fb25d0 100644 --- a/app/src/main/java/at/bitfire/davdroid/settings/AccountSettings.kt +++ b/app/src/main/java/at/bitfire/davdroid/settings/AccountSettings.kt @@ -44,6 +44,7 @@ import java.util.logging.Level * * @throws InvalidAccountException on construction when the account doesn't exist (anymore) */ +@Suppress("FunctionName") class AccountSettings( val context: Context, val account: Account @@ -295,6 +296,7 @@ class AccountSettings( provider.client.update(tasksUri, emptyETag, "${TaskContract.Tasks._DIRTY}=0 AND ${TaskContract.Tasks._DELETED}=0", null) } + @SuppressLint("Recycle") if (ContextCompat.checkSelfPermission(context, android.Manifest.permission.WRITE_CALENDAR) == PackageManager.PERMISSION_GRANTED) context.contentResolver.acquireContentProviderClient(CalendarContract.AUTHORITY)?.let { provider -> provider.update(AndroidCalendar.syncAdapterURI(CalendarContract.Calendars.CONTENT_URI, account), diff --git a/app/src/main/java/at/bitfire/davdroid/settings/Settings.kt b/app/src/main/java/at/bitfire/davdroid/settings/Settings.kt index d075101d5..005d0a6d9 100644 --- a/app/src/main/java/at/bitfire/davdroid/settings/Settings.kt +++ b/app/src/main/java/at/bitfire/davdroid/settings/Settings.kt @@ -15,7 +15,6 @@ import java.lang.ref.WeakReference import java.util.* import java.util.logging.Level -@WorkerThread class Settings( appContext: Context ) { @@ -68,16 +67,22 @@ class Settings( /*** OBSERVERS ***/ fun addOnChangeListener(observer: OnChangeListener) { - observers += WeakReference(observer) + synchronized(this) { + observers += WeakReference(observer) + } } fun removeOnChangeListener(observer: OnChangeListener) { - observers.removeAll { it.get() == null || it.get() == observer } + synchronized(this) { + observers.removeAll { it.get() == null || it.get() == observer } + } } fun onSettingsChanged() { - observers.mapNotNull { it.get() }.forEach { - it.onSettingsChanged() + synchronized(this) { + observers.mapNotNull { it.get() }.forEach { + it.onSettingsChanged() + } } } diff --git a/app/src/main/java/at/bitfire/davdroid/syncadapter/SyncAdapterService.kt b/app/src/main/java/at/bitfire/davdroid/syncadapter/SyncAdapterService.kt index 2d426df32..5acc93e41 100644 --- a/app/src/main/java/at/bitfire/davdroid/syncadapter/SyncAdapterService.kt +++ b/app/src/main/java/at/bitfire/davdroid/syncadapter/SyncAdapterService.kt @@ -25,9 +25,9 @@ import androidx.core.content.ContextCompat import at.bitfire.davdroid.R import at.bitfire.davdroid.log.Logger import at.bitfire.davdroid.settings.AccountSettings -import at.bitfire.davdroid.ui.account.SettingsActivity import at.bitfire.davdroid.ui.NotificationUtils import at.bitfire.davdroid.ui.account.AccountActivity +import at.bitfire.davdroid.ui.account.SettingsActivity import java.lang.ref.WeakReference import java.util.* import java.util.logging.Level diff --git a/app/src/main/java/at/bitfire/davdroid/ui/AboutActivity.kt b/app/src/main/java/at/bitfire/davdroid/ui/AboutActivity.kt index bbd68eed1..4da58cf4f 100644 --- a/app/src/main/java/at/bitfire/davdroid/ui/AboutActivity.kt +++ b/app/src/main/java/at/bitfire/davdroid/ui/AboutActivity.kt @@ -22,7 +22,7 @@ import androidx.fragment.app.FragmentPagerAdapter import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.MutableLiveData import androidx.lifecycle.Observer -import androidx.lifecycle.ViewModelProviders +import androidx.lifecycle.ViewModelProvider import at.bitfire.davdroid.App import at.bitfire.davdroid.BuildConfig import at.bitfire.davdroid.R @@ -108,8 +108,8 @@ class AboutActivity: AppCompatActivity() { if (true /* open-source version */) { warranty.setText(R.string.about_license_info_no_warranty) - val model = ViewModelProviders.of(this).get(LicenseModel::class.java) - model.htmlText.observe(this, Observer { spanned -> + val model = ViewModelProvider(this).get(LicenseModel::class.java) + model.htmlText.observe(viewLifecycleOwner, Observer { spanned -> license_text.text = spanned }) } diff --git a/app/src/main/java/at/bitfire/davdroid/ui/AccountListFragment.kt b/app/src/main/java/at/bitfire/davdroid/ui/AccountListFragment.kt index 4c1e91447..96c251bae 100644 --- a/app/src/main/java/at/bitfire/davdroid/ui/AccountListFragment.kt +++ b/app/src/main/java/at/bitfire/davdroid/ui/AccountListFragment.kt @@ -32,7 +32,7 @@ import androidx.fragment.app.ListFragment import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.MutableLiveData import androidx.lifecycle.Observer -import androidx.lifecycle.ViewModelProviders +import androidx.lifecycle.ViewModelProvider import at.bitfire.davdroid.R import at.bitfire.davdroid.ui.account.AccountActivity import kotlinx.android.synthetic.main.account_list.* @@ -43,14 +43,14 @@ class AccountListFragment: ListFragment() { override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { listAdapter = AccountListAdapter(requireActivity()) - val model = ViewModelProviders.of(this).get(Model::class.java) - model.accounts.observe(this, Observer { accounts -> + val model = ViewModelProvider(this).get(Model::class.java) + model.accounts.observe(viewLifecycleOwner, Observer { accounts -> val adapter = listAdapter as AccountListAdapter adapter.clear() adapter.addAll(*accounts) }) - model.networkAvailable.observe(this, Observer { networkAvailable -> + model.networkAvailable.observe(viewLifecycleOwner, Observer { networkAvailable -> no_network_info.visibility = if (networkAvailable) View.GONE else View.VISIBLE }) diff --git a/app/src/main/java/at/bitfire/davdroid/ui/CreateAddressBookActivity.kt b/app/src/main/java/at/bitfire/davdroid/ui/CreateAddressBookActivity.kt index b77ec7c51..f109d0977 100644 --- a/app/src/main/java/at/bitfire/davdroid/ui/CreateAddressBookActivity.kt +++ b/app/src/main/java/at/bitfire/davdroid/ui/CreateAddressBookActivity.kt @@ -20,7 +20,7 @@ import androidx.core.app.NavUtils import androidx.databinding.DataBindingUtil import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.MutableLiveData -import androidx.lifecycle.ViewModelProviders +import androidx.lifecycle.ViewModelProvider import at.bitfire.davdroid.R import at.bitfire.davdroid.databinding.ActivityCreateAddressBookBinding import at.bitfire.davdroid.model.AppDatabase @@ -43,7 +43,7 @@ class CreateAddressBookActivity: AppCompatActivity() { super.onCreate(savedInstanceState) supportActionBar?.setDisplayHomeAsUpEnabled(true) - model = ViewModelProviders.of(this).get(Model::class.java) + model = ViewModelProvider(this).get(Model::class.java) (intent?.getParcelableExtra(EXTRA_ACCOUNT) as? Account)?.let { model.initialize(it) } diff --git a/app/src/main/java/at/bitfire/davdroid/ui/CreateCalendarActivity.kt b/app/src/main/java/at/bitfire/davdroid/ui/CreateCalendarActivity.kt index ae151048c..66f905144 100644 --- a/app/src/main/java/at/bitfire/davdroid/ui/CreateCalendarActivity.kt +++ b/app/src/main/java/at/bitfire/davdroid/ui/CreateCalendarActivity.kt @@ -24,7 +24,7 @@ import androidx.core.app.NavUtils import androidx.databinding.DataBindingUtil import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.MutableLiveData -import androidx.lifecycle.ViewModelProviders +import androidx.lifecycle.ViewModelProvider import at.bitfire.davdroid.Constants import at.bitfire.davdroid.R import at.bitfire.davdroid.databinding.ActivityCreateCalendarBinding @@ -53,7 +53,7 @@ class CreateCalendarActivity: AppCompatActivity(), ColorPickerDialogListener { super.onCreate(savedInstanceState) supportActionBar?.setDisplayHomeAsUpEnabled(true) - model = ViewModelProviders.of(this).get(Model::class.java) + model = ViewModelProvider(this).get(Model::class.java) (intent?.getParcelableExtra(EXTRA_ACCOUNT) as? Account)?.let { model.initialize(it) } @@ -163,7 +163,7 @@ class CreateCalendarActivity: AppCompatActivity(), ColorPickerDialogListener { context: Context ): ArrayAdapter(context, android.R.layout.simple_list_item_1, android.R.id.text1) { - val tz = TimeZone.getAvailableIDs()!! + val tz: Array = TimeZone.getAvailableIDs() override fun getFilter(): Filter { return object: Filter() { diff --git a/app/src/main/java/at/bitfire/davdroid/ui/CreateCollectionFragment.kt b/app/src/main/java/at/bitfire/davdroid/ui/CreateCollectionFragment.kt index a59a4f53d..f53796fed 100644 --- a/app/src/main/java/at/bitfire/davdroid/ui/CreateCollectionFragment.kt +++ b/app/src/main/java/at/bitfire/davdroid/ui/CreateCollectionFragment.kt @@ -56,7 +56,7 @@ class CreateCollectionFragment: DialogFragment() { super.onCreate(savedInstanceState) val args = arguments ?: throw IllegalArgumentException() - model = ViewModelProviders.of(this).get(Model::class.java) + model = ViewModelProvider(this).get(Model::class.java) model.account = args.getParcelable(ARG_ACCOUNT) ?: throw IllegalArgumentException("ARG_ACCOUNT required") model.serviceType = args.getString(ARG_SERVICE_TYPE) ?: throw java.lang.IllegalArgumentException("ARG_SERVICE_TYPE required") @@ -80,7 +80,7 @@ class CreateCollectionFragment: DialogFragment() { requireActivity().finish() else { dismiss() - requireFragmentManager().beginTransaction() + parentFragmentManager.beginTransaction() .add(ExceptionInfoFragment.newInstance(exception, model.account), null) .commit() } diff --git a/app/src/main/java/at/bitfire/davdroid/ui/DebugInfoActivity.kt b/app/src/main/java/at/bitfire/davdroid/ui/DebugInfoActivity.kt index 8479aac66..3650f23d4 100644 --- a/app/src/main/java/at/bitfire/davdroid/ui/DebugInfoActivity.kt +++ b/app/src/main/java/at/bitfire/davdroid/ui/DebugInfoActivity.kt @@ -36,7 +36,7 @@ import androidx.core.content.pm.PackageInfoCompat import androidx.databinding.DataBindingUtil import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.MutableLiveData -import androidx.lifecycle.ViewModelProviders +import androidx.lifecycle.ViewModelProvider import at.bitfire.dav4jvm.exception.HttpException import at.bitfire.davdroid.BuildConfig import at.bitfire.davdroid.InvalidAccountException @@ -71,7 +71,7 @@ class DebugInfoActivity: AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - model = ViewModelProviders.of(this).get(ReportModel::class.java) + model = ViewModelProvider(this).get(ReportModel::class.java) model.initialize(intent.extras) val binding = DataBindingUtil.setContentView(this, R.layout.activity_debug_info) diff --git a/app/src/main/java/at/bitfire/davdroid/ui/DeleteCollectionFragment.kt b/app/src/main/java/at/bitfire/davdroid/ui/DeleteCollectionFragment.kt index 0dd939986..f80857782 100644 --- a/app/src/main/java/at/bitfire/davdroid/ui/DeleteCollectionFragment.kt +++ b/app/src/main/java/at/bitfire/davdroid/ui/DeleteCollectionFragment.kt @@ -45,10 +45,10 @@ class DeleteCollectionFragment: DialogFragment() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - model = ViewModelProviders.of(this).get(DeleteCollectionModel::class.java) + model = ViewModelProvider(this).get(DeleteCollectionModel::class.java) model.initialize( - arguments!!.getParcelable(ARG_ACCOUNT)!!, - arguments!!.getLong(ARG_COLLECTION_ID) + requireArguments().getParcelable(ARG_ACCOUNT)!!, + requireArguments().getLong(ARG_COLLECTION_ID) ) } @@ -62,9 +62,9 @@ class DeleteCollectionFragment: DialogFragment() { binding.progress.visibility = View.VISIBLE binding.controls.visibility = View.GONE - model.deleteCollection().observe(this, Observer { exception -> + model.deleteCollection().observe(viewLifecycleOwner, Observer { exception -> if (exception != null) - requireFragmentManager().beginTransaction() + parentFragmentManager.beginTransaction() .add(ExceptionInfoFragment.newInstance(exception, model.account), null) .commit() dismiss() diff --git a/app/src/main/java/at/bitfire/davdroid/ui/StartupDialogFragment.kt b/app/src/main/java/at/bitfire/davdroid/ui/StartupDialogFragment.kt index 3a989577e..485f916c9 100644 --- a/app/src/main/java/at/bitfire/davdroid/ui/StartupDialogFragment.kt +++ b/app/src/main/java/at/bitfire/davdroid/ui/StartupDialogFragment.kt @@ -64,7 +64,7 @@ class StartupDialogFragment: DialogFragment() { } // vendor-specific auto-start information - if (autostartManufacturers.contains(Build.MANUFACTURER.toLowerCase()) && settings.getBoolean(HINT_AUTOSTART_PERMISSIONS) != false) + if (autostartManufacturers.contains(Build.MANUFACTURER.toLowerCase(Locale.ROOT)) && settings.getBoolean(HINT_AUTOSTART_PERMISSIONS) != false) dialogs.add(instantiate(Mode.AUTOSTART_PERMISSIONS)) // OpenTasks information @@ -92,7 +92,7 @@ class StartupDialogFragment: DialogFragment() { val settings = Settings.getInstance(requireActivity()) val activity = requireActivity() - return when (Mode.valueOf(arguments!!.getString(ARGS_MODE)!!)) { + return when (Mode.valueOf(requireArguments().getString(ARGS_MODE)!!)) { Mode.AUTOSTART_PERMISSIONS -> MaterialAlertDialogBuilder(activity) .setIcon(R.drawable.ic_error_dark) diff --git a/app/src/main/java/at/bitfire/davdroid/ui/account/AccountActivity.kt b/app/src/main/java/at/bitfire/davdroid/ui/account/AccountActivity.kt index 51534d1ef..535d76a02 100644 --- a/app/src/main/java/at/bitfire/davdroid/ui/account/AccountActivity.kt +++ b/app/src/main/java/at/bitfire/davdroid/ui/account/AccountActivity.kt @@ -47,7 +47,7 @@ class AccountActivity: AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - model = ViewModelProviders.of(this).get(Model::class.java) + model = ViewModelProvider(this).get(Model::class.java) (intent.getParcelableExtra(EXTRA_ACCOUNT) as? Account)?.let { account -> model.initialize(account) } diff --git a/app/src/main/java/at/bitfire/davdroid/ui/account/CollectionInfoFragment.kt b/app/src/main/java/at/bitfire/davdroid/ui/account/CollectionInfoFragment.kt index 10c464c4b..cb7a5e7bd 100644 --- a/app/src/main/java/at/bitfire/davdroid/ui/account/CollectionInfoFragment.kt +++ b/app/src/main/java/at/bitfire/davdroid/ui/account/CollectionInfoFragment.kt @@ -17,7 +17,7 @@ import androidx.annotation.UiThread import androidx.fragment.app.DialogFragment import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.MutableLiveData -import androidx.lifecycle.ViewModelProviders +import androidx.lifecycle.ViewModelProvider import at.bitfire.davdroid.databinding.CollectionPropertiesBinding import at.bitfire.davdroid.model.AppDatabase import at.bitfire.davdroid.model.Collection @@ -40,7 +40,7 @@ class CollectionInfoFragment: DialogFragment() { } override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { - val model = ViewModelProviders.of(this).get(Model::class.java) + val model = ViewModelProvider(this).get(Model::class.java) arguments?.getLong(ARGS_COLLECTION_ID)?.let { id -> model.initialize(id) } diff --git a/app/src/main/java/at/bitfire/davdroid/ui/account/CollectionsFragment.kt b/app/src/main/java/at/bitfire/davdroid/ui/account/CollectionsFragment.kt index 7dab33491..48e30a4f0 100644 --- a/app/src/main/java/at/bitfire/davdroid/ui/account/CollectionsFragment.kt +++ b/app/src/main/java/at/bitfire/davdroid/ui/account/CollectionsFragment.kt @@ -46,8 +46,8 @@ abstract class CollectionsFragment: Fragment(), SwipeRefreshLayout.OnRefreshList override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - accountModel = ViewModelProviders.of(requireActivity()).get(AccountActivity.Model::class.java) - model = ViewModelProviders.of(this).get(Model::class.java) + accountModel = ViewModelProvider(requireActivity()).get(AccountActivity.Model::class.java) + model = ViewModelProvider(this).get(Model::class.java) model.initialize( accountModel, arguments?.getLong(EXTRA_SERVICE_ID) ?: throw IllegalArgumentException("EXTRA_SERVICE_ID required"), diff --git a/app/src/main/java/at/bitfire/davdroid/ui/account/RenameAccountFragment.kt b/app/src/main/java/at/bitfire/davdroid/ui/account/RenameAccountFragment.kt index 1b4138be2..b274dba82 100644 --- a/app/src/main/java/at/bitfire/davdroid/ui/account/RenameAccountFragment.kt +++ b/app/src/main/java/at/bitfire/davdroid/ui/account/RenameAccountFragment.kt @@ -22,7 +22,7 @@ import androidx.fragment.app.DialogFragment import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.MutableLiveData import androidx.lifecycle.Observer -import androidx.lifecycle.ViewModelProviders +import androidx.lifecycle.ViewModelProvider import at.bitfire.davdroid.DavUtils import at.bitfire.davdroid.R import at.bitfire.davdroid.closeCompat @@ -55,7 +55,7 @@ class RenameAccountFragment: DialogFragment() { @SuppressLint("Recycle") override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { - val oldAccount: Account = arguments!!.getParcelable(ARG_ACCOUNT)!! + val oldAccount: Account = requireArguments().getParcelable(ARG_ACCOUNT)!! val editText = EditText(requireActivity()).apply { setText(oldAccount.name) @@ -66,7 +66,7 @@ class RenameAccountFragment: DialogFragment() { layout.setPadding(8*density, 8*density, 8*density, 8*density) layout.addView(editText) - val model = ViewModelProviders.of(this).get(Model::class.java) + val model = ViewModelProvider(this).get(Model::class.java) model.finished.observe(this, Observer { this@RenameAccountFragment.requireActivity().finish() }) diff --git a/app/src/main/java/at/bitfire/davdroid/ui/account/SettingsActivity.kt b/app/src/main/java/at/bitfire/davdroid/ui/account/SettingsActivity.kt index 57360b6f7..5fa469b16 100644 --- a/app/src/main/java/at/bitfire/davdroid/ui/account/SettingsActivity.kt +++ b/app/src/main/java/at/bitfire/davdroid/ui/account/SettingsActivity.kt @@ -79,9 +79,9 @@ class SettingsActivity: AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) settings = Settings.getInstance(requireActivity()) - account = arguments!!.getParcelable(EXTRA_ACCOUNT)!! + account = requireArguments().getParcelable(EXTRA_ACCOUNT)!! - model = ViewModelProviders.of(this).get(Model::class.java) + model = ViewModelProvider(this).get(Model::class.java) model.initialize(account) initSettings() diff --git a/app/src/main/java/at/bitfire/davdroid/ui/account/WebcalFragment.kt b/app/src/main/java/at/bitfire/davdroid/ui/account/WebcalFragment.kt index d2fccd01f..a138029a3 100644 --- a/app/src/main/java/at/bitfire/davdroid/ui/account/WebcalFragment.kt +++ b/app/src/main/java/at/bitfire/davdroid/ui/account/WebcalFragment.kt @@ -43,7 +43,7 @@ class WebcalFragment: CollectionsFragment() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - webcalModel = ViewModelProviders.of(this).get(WebcalModel::class.java) + webcalModel = ViewModelProvider(this).get(WebcalModel::class.java) webcalModel.calendarPermission.observe(this, Observer { granted -> if (!granted) requestPermissions(arrayOf(Manifest.permission.READ_CALENDAR), 0) diff --git a/app/src/main/java/at/bitfire/davdroid/ui/setup/AccountDetailsFragment.kt b/app/src/main/java/at/bitfire/davdroid/ui/setup/AccountDetailsFragment.kt index 1abc29a37..cfde653c5 100644 --- a/app/src/main/java/at/bitfire/davdroid/ui/setup/AccountDetailsFragment.kt +++ b/app/src/main/java/at/bitfire/davdroid/ui/setup/AccountDetailsFragment.kt @@ -46,8 +46,8 @@ class AccountDetailsFragment: Fragment() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - loginModel = ViewModelProviders.of(requireActivity()).get(LoginModel::class.java) - model = ViewModelProviders.of(this).get(AccountDetailsModel::class.java) + loginModel = ViewModelProvider(requireActivity()).get(LoginModel::class.java) + model = ViewModelProvider(this).get(AccountDetailsModel::class.java) } override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { @@ -90,7 +90,7 @@ class AccountDetailsFragment: Fragment() { loginModel.credentials!!, config, GroupMethod.valueOf(groupMethodName) - ).observe(this, Observer { success -> + ).observe(viewLifecycleOwner, Observer { success -> if (success) requireActivity().finish() else { diff --git a/app/src/main/java/at/bitfire/davdroid/ui/setup/DavResourceFinder.kt b/app/src/main/java/at/bitfire/davdroid/ui/setup/DavResourceFinder.kt index 280506888..778bd8971 100644 --- a/app/src/main/java/at/bitfire/davdroid/ui/setup/DavResourceFinder.kt +++ b/app/src/main/java/at/bitfire/davdroid/ui/setup/DavResourceFinder.kt @@ -43,7 +43,7 @@ class DavResourceFinder( override fun toString() = wellKnownName } - val log = Logger.getLogger("davdroid.DavResourceFinder") + val log: Logger = Logger.getLogger("davdroid.DavResourceFinder") private val logBuffer = StringHandler() init { log.level = Level.FINEST diff --git a/app/src/main/java/at/bitfire/davdroid/ui/setup/DetectConfigurationFragment.kt b/app/src/main/java/at/bitfire/davdroid/ui/setup/DetectConfigurationFragment.kt index 04793df9d..91ed49eaa 100644 --- a/app/src/main/java/at/bitfire/davdroid/ui/setup/DetectConfigurationFragment.kt +++ b/app/src/main/java/at/bitfire/davdroid/ui/setup/DetectConfigurationFragment.kt @@ -33,23 +33,23 @@ class DetectConfigurationFragment: Fragment() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - loginModel = ViewModelProviders.of(requireActivity()).get(LoginModel::class.java) - model = ViewModelProviders.of(this).get(DetectConfigurationModel::class.java) + loginModel = ViewModelProvider(requireActivity()).get(LoginModel::class.java) + model = ViewModelProvider(this).get(DetectConfigurationModel::class.java) model.detectConfiguration(loginModel).observe(this, Observer { result -> // save result for next step loginModel.configuration = result // remove "Detecting configuration" fragment, it shouldn't come back - requireFragmentManager().popBackStack() + parentFragmentManager.popBackStack() if (result.calDAV != null || result.cardDAV != null) - requireFragmentManager().beginTransaction() + parentFragmentManager.beginTransaction() .replace(android.R.id.content, AccountDetailsFragment()) .addToBackStack(null) .commit() else - requireFragmentManager().beginTransaction() + parentFragmentManager.beginTransaction() .add(NothingDetectedFragment(), null) .commit() }) @@ -105,7 +105,7 @@ class DetectConfigurationFragment: Fragment() { class NothingDetectedFragment: DialogFragment() { override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { - val model = ViewModelProviders.of(requireActivity()).get(LoginModel::class.java) + val model = ViewModelProvider(requireActivity()).get(LoginModel::class.java) return MaterialAlertDialogBuilder(requireActivity()) .setTitle(R.string.login_configuration_detection) .setIcon(R.drawable.ic_error_dark) @@ -118,7 +118,7 @@ class DetectConfigurationFragment: Fragment() { .setPositiveButton(android.R.string.ok) { _, _ -> // just dismiss } - .create()!! + .create() } } diff --git a/cert4android b/cert4android index aa9b9cfc3..e4a7866ef 160000 --- a/cert4android +++ b/cert4android @@ -1 +1 @@ -Subproject commit aa9b9cfc32d9fdef8a3bf385f5cca8a9cd673fe1 +Subproject commit e4a7866ef87f5d427722af97007ce8ae52b56b18 diff --git a/ical4android b/ical4android index 9a7dabeb3..7669dba7b 160000 --- a/ical4android +++ b/ical4android @@ -1 +1 @@ -Subproject commit 9a7dabeb3898f2862c2d568416436a06cb3a855c +Subproject commit 7669dba7b87bc6643f09adb74a4f0cae4968eea1 diff --git a/vcard4android b/vcard4android index 4e258cd0f..2290ac6d0 160000 --- a/vcard4android +++ b/vcard4android @@ -1 +1 @@ -Subproject commit 4e258cd0f4e3faf009ca5782303d547182c086ca +Subproject commit 2290ac6d002d05477b9419d71fd0d592fa756c7b -- GitLab From 3dc2aa65dfdcbf9cb9fb8ce87c98bbb19994c98d Mon Sep 17 00:00:00 2001 From: Ricki Hirner Date: Thu, 5 Mar 2020 23:45:27 +0100 Subject: [PATCH 019/783] Sync algorithm bug fixes/improvements - Collection sync: don't save new sync state before downloading is finished - throw exception when waiting for completion times out - always use multi-get, even for single vCards/iCalendars --- .../syncadapter/CalendarSyncManager.kt | 50 +++++++----------- .../syncadapter/ContactsSyncManager.kt | 52 ++++++++----------- .../davdroid/syncadapter/SyncManager.kt | 18 +++++-- build.gradle | 2 +- 4 files changed, 55 insertions(+), 67 deletions(-) diff --git a/app/src/main/java/at/bitfire/davdroid/syncadapter/CalendarSyncManager.kt b/app/src/main/java/at/bitfire/davdroid/syncadapter/CalendarSyncManager.kt index d0ed2cf07..753c972b2 100644 --- a/app/src/main/java/at/bitfire/davdroid/syncadapter/CalendarSyncManager.kt +++ b/app/src/main/java/at/bitfire/davdroid/syncadapter/CalendarSyncManager.kt @@ -13,7 +13,6 @@ import android.content.Context import android.content.SyncResult import android.os.Bundle import at.bitfire.dav4jvm.DavCalendar -import at.bitfire.dav4jvm.DavResource import at.bitfire.dav4jvm.DavResponseCallback import at.bitfire.dav4jvm.Response import at.bitfire.dav4jvm.exception.DavException @@ -113,41 +112,32 @@ class CalendarSyncManager( override fun downloadRemote(bunch: List) { Logger.log.info("Downloading ${bunch.size} iCalendars: $bunch") - if (bunch.size == 1) { - val remote = bunch.first() - // only one contact, use GET - useRemote(DavResource(httpClient.okHttpClient, remote)) { resource -> - resource.get(DavCalendar.MIME_ICALENDAR.toString()) { response -> - // CalDAV servers MUST return ETag on GET [https://tools.ietf.org/html/rfc4791#section-5.3.4] - val eTag = response.header("ETag")?.let { GetETag(it).eTag } - ?: throw DavException("Received CalDAV GET response without ETag") - - response.body()!!.use { - processVEvent(resource.fileName(), eTag, it.charStream()) + /* Always use calendar-multi-get to get the iCalendars. + We don't use single GETs anymore, because then there are two methods, and in case of a bad + server (like iCloud) syncing could fail when there's only one changed resource + and succeed when there are multiple changed resources (or vice versa). This is intransparent + to the user. Also, when there's only one method, there's less code to maintain and there + are less possibilities for errors. + */ + useRemoteCollection { + it.multiget(bunch) { response, _ -> + useRemote(response) { + if (!response.isSuccess()) { + Logger.log.warning("Received non-successful multiget response for ${response.href}") + return@useRemote } - } - } - } else - // multiple iCalendars, use calendar-multi-get - useRemoteCollection { - it.multiget(bunch) { response, _ -> - useRemote(response) { - if (!response.isSuccess()) { - Logger.log.warning("Received non-successful multiget response for ${response.href}") - return@useRemote - } - val eTag = response[GetETag::class.java]?.eTag - ?: throw DavException("Received multi-get response without ETag") + val eTag = response[GetETag::class.java]?.eTag + ?: throw DavException("Received multi-get response without ETag") - val calendarData = response[CalendarData::class.java] - val iCal = calendarData?.iCalendar - ?: throw DavException("Received multi-get response without address data") + val calendarData = response[CalendarData::class.java] + val iCal = calendarData?.iCalendar + ?: throw DavException("Received multi-get response without address data") - processVEvent(DavUtils.lastSegmentOfUrl(response.href), eTag, StringReader(iCal)) - } + processVEvent(DavUtils.lastSegmentOfUrl(response.href), eTag, StringReader(iCal)) } } + } } override fun postProcess() { diff --git a/app/src/main/java/at/bitfire/davdroid/syncadapter/ContactsSyncManager.kt b/app/src/main/java/at/bitfire/davdroid/syncadapter/ContactsSyncManager.kt index 372835996..d9e41c2e7 100644 --- a/app/src/main/java/at/bitfire/davdroid/syncadapter/ContactsSyncManager.kt +++ b/app/src/main/java/at/bitfire/davdroid/syncadapter/ContactsSyncManager.kt @@ -14,7 +14,6 @@ import android.os.Build import android.os.Bundle import android.provider.ContactsContract.Groups import at.bitfire.dav4jvm.DavAddressBook -import at.bitfire.dav4jvm.DavResource import at.bitfire.dav4jvm.DavResponseCallback import at.bitfire.dav4jvm.Response import at.bitfire.dav4jvm.exception.DavException @@ -269,42 +268,33 @@ class ContactsSyncManager( } override fun downloadRemote(bunch: List) { - Logger.log.info("Downloading ${bunch.size} vCards: $bunch") - if (bunch.size == 1) { - val remote = bunch.first() - // only one contact, use GET - useRemote(DavResource(httpClient.okHttpClient, remote)) { resource -> - resource.get("text/vcard;version=4.0, text/vcard;charset=utf-8;q=0.8, text/vcard;q=0.5") { response -> - // CardDAV servers MUST return ETag on GET [https://tools.ietf.org/html/rfc6352#section-6.3.2.3] - val eTag = response.header("ETag")?.let { GetETag(it).eTag } - ?: throw DavException("Received CardDAV GET response without ETag") - - response.body()!!.use { - processVCard(resource.fileName(), eTag, it.charStream(), resourceDownloader) + Logger.log.info("Downloading ${bunch.size} vCard(s): $bunch") + /* Always use addressbook-multi-get to get the vCards. + We don't use single GETs anymore, because then there are two methods, and in case of a bad + server (like iCloud) syncing could fail when there's only one changed resource + and succeed when there are multiple changed resources (or vice versa). This is intransparent + to the user. Also, when there's only one method, there's less code to maintain and there + are less possibilities for errors. + */ + useRemoteCollection { + it.multiget(bunch, hasVCard4) { response, _ -> + useRemote(response) { + if (!response.isSuccess()) { + Logger.log.warning("Received non-successful multiget response for ${response.href}") + return@useRemote } - } - } - } else - // multiple vCards, use addressbook-multi-get - useRemoteCollection { - it.multiget(bunch, hasVCard4) { response, _ -> - useRemote(response) { - if (!response.isSuccess()) { - Logger.log.warning("Received non-successful multiget response for ${response.href}") - return@useRemote - } - val eTag = response[GetETag::class.java]?.eTag - ?: throw DavException("Received multi-get response without ETag") + val eTag = response[GetETag::class.java]?.eTag + ?: throw DavException("Received multi-get response without ETag") - val addressData = response[AddressData::class.java] - val vCard = addressData?.vCard - ?: throw DavException("Received multi-get response without address data") + val addressData = response[AddressData::class.java] + val vCard = addressData?.vCard + ?: throw DavException("Received multi-get response without address data") - processVCard(DavUtils.lastSegmentOfUrl(response.href), eTag, StringReader(vCard), resourceDownloader) - } + processVCard(DavUtils.lastSegmentOfUrl(response.href), eTag, StringReader(vCard), resourceDownloader) } } + } } override fun postProcess() { diff --git a/app/src/main/java/at/bitfire/davdroid/syncadapter/SyncManager.kt b/app/src/main/java/at/bitfire/davdroid/syncadapter/SyncManager.kt index f31a2b244..b263e3253 100644 --- a/app/src/main/java/at/bitfire/davdroid/syncadapter/SyncManager.kt +++ b/app/src/main/java/at/bitfire/davdroid/syncadapter/SyncManager.kt @@ -199,11 +199,11 @@ abstract class SyncManager, out CollectionType: L } else throw e } - - Logger.log.log(Level.INFO, "Saving sync state", syncState) - localCollection.lastSyncState = syncState } + Logger.log.log(Level.INFO, "Saving sync state", syncState) + localCollection.lastSyncState = syncState + Logger.log.info("Server has further changes: $furtherChanges") } while(furtherChanges) @@ -436,6 +436,12 @@ abstract class SyncManager, out CollectionType: L Logger.log.info("Number of local non-dirty entries: $number") } + /** + * Calls a callback to list remote resources. All resources from the returned + * list are downloaded and processed. + * + * @param listRemote function to list remote resources (for instance, all since a certain sync-token) + */ protected open fun syncRemote(listRemote: (DavResponseCallback) -> Unit) { // results must be processed in main thread because exceptions must be thrown in main // thread, so that they can be catched by SyncManager @@ -532,7 +538,8 @@ abstract class SyncManager, out CollectionType: L // process remaining responses processor.shutdown() - processor.awaitTermination(5, TimeUnit.MINUTES) + if (!processor.awaitTermination(5, TimeUnit.MINUTES)) + throw TimeoutException("Processing the remote resource list took too long") // download remaining resources if (toDownload.isNotEmpty()) @@ -540,7 +547,8 @@ abstract class SyncManager, out CollectionType: L // signal end of queue and wait for download thread downloader.shutdown() - downloader.awaitTermination(5, TimeUnit.MINUTES) + if (!downloader.awaitTermination(5, TimeUnit.MINUTES)) + throw TimeoutException("Downloading and processing the remote resources took too long") // check remaining results for exceptions checkResults(results) diff --git a/build.gradle b/build.gradle index 8684b162c..72123670c 100644 --- a/build.gradle +++ b/build.gradle @@ -9,7 +9,7 @@ buildscript { ext.versions = [ dokka: '0.10.0', - kotlin: '1.3.61', + kotlin: '1.3.70', okhttp: '3.12.10' ] -- GitLab From 41d33fac448e8b01afb0a6dacdd04ddf08263e5a Mon Sep 17 00:00:00 2001 From: Ricki Hirner Date: Fri, 6 Mar 2020 00:20:47 +0100 Subject: [PATCH 020/783] Version bump to 2.6.5 --- app/build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 4fdc203b8..71f049b94 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -19,7 +19,7 @@ android { defaultConfig { applicationId "at.bitfire.davdroid" - versionCode 331 + versionCode 332 buildConfigField "long", "buildTime", System.currentTimeMillis() + "L" buildConfigField "boolean", "customCerts", "true" @@ -44,7 +44,7 @@ android { flavorDimensions "distribution" productFlavors { standard { - versionName "2.6.4-ose" + versionName "2.6.5-ose" } } -- GitLab From 37a299d0f73b79d11417e84b5dfaaffb5bdbc60a Mon Sep 17 00:00:00 2001 From: Ricki Hirner Date: Fri, 6 Mar 2020 00:23:42 +0100 Subject: [PATCH 021/783] Fetch translations from Transifex --- app/src/main/res/values-el/strings.xml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/app/src/main/res/values-el/strings.xml b/app/src/main/res/values-el/strings.xml index 254f6ff0f..72f633490 100644 --- a/app/src/main/res/values-el/strings.xml +++ b/app/src/main/res/values-el/strings.xml @@ -194,7 +194,12 @@ Τα συμβάντα που υπερβαίνουν αυτόν τον αριθμό ημερών στο παρελθόν θα αγνοηθούν (μπορεί να είναι 0). Αφήστε κενό για συγχρονισμό όλων των συμβάντων. Προεπιλεγμένη υπενθύμιση + + Προεπιλεγμένη υπενθύμιση ένα λεπτό πριν το συμβάν + Προεπιλεγμένη υπενθύμιση %dλεπτά πριν το συμβάν. + Δεν δημιουργούνται προεπιλεγμένες υπενθυμίσεις + Εάν δημιουργούνται προεπιλεγμένες υπενθυμίσεις για συμβάντα χωρίς υπενθύμιση: επιθυμητός αριθμός λεπτών πριν από το συμβάν. Αφήστε κενό για να απενεργοποιήσετε τις προεπιλεγμένες υπενθυμίσεις. Διαχείριση χρωμάτων ημερολογίου Τα χρώματα του ημερολογίου διαχειρίζονται από το DAVx⁵ Τα χρώματα του ημερολογίου δεν ορίστηκαν από το DAVx⁵ @@ -203,6 +208,10 @@ Να μην γίνει συγχρονισμός χρωμάτων συμβάντων CardDAV Αλλαγή μεθόδου ομάδας + + Οι ομάδες είναι ξεχωριστές vCards + Οι ομάδες είναι κατηγορίες ανά επαφή + Αλλαγή μεθόδου ομάδας Δημιουργία βιβλίου διευθύνσεων -- GitLab From b7e60cd14395f83664798d016fad613540db2905 Mon Sep 17 00:00:00 2001 From: Ricki Hirner Date: Fri, 6 Mar 2020 11:56:48 +0100 Subject: [PATCH 022/783] Only use multi-get for tasks sync, too --- .../syncadapter/CalendarSyncManager.kt | 7 --- .../syncadapter/ContactsSyncManager.kt | 7 --- .../davdroid/syncadapter/SyncManager.kt | 18 ++++++++ .../davdroid/syncadapter/TasksSyncManager.kt | 44 ++++++------------- 4 files changed, 32 insertions(+), 44 deletions(-) diff --git a/app/src/main/java/at/bitfire/davdroid/syncadapter/CalendarSyncManager.kt b/app/src/main/java/at/bitfire/davdroid/syncadapter/CalendarSyncManager.kt index 753c972b2..93909cd11 100644 --- a/app/src/main/java/at/bitfire/davdroid/syncadapter/CalendarSyncManager.kt +++ b/app/src/main/java/at/bitfire/davdroid/syncadapter/CalendarSyncManager.kt @@ -112,13 +112,6 @@ class CalendarSyncManager( override fun downloadRemote(bunch: List) { Logger.log.info("Downloading ${bunch.size} iCalendars: $bunch") - /* Always use calendar-multi-get to get the iCalendars. - We don't use single GETs anymore, because then there are two methods, and in case of a bad - server (like iCloud) syncing could fail when there's only one changed resource - and succeed when there are multiple changed resources (or vice versa). This is intransparent - to the user. Also, when there's only one method, there's less code to maintain and there - are less possibilities for errors. - */ useRemoteCollection { it.multiget(bunch) { response, _ -> useRemote(response) { diff --git a/app/src/main/java/at/bitfire/davdroid/syncadapter/ContactsSyncManager.kt b/app/src/main/java/at/bitfire/davdroid/syncadapter/ContactsSyncManager.kt index d9e41c2e7..c5a8a5f80 100644 --- a/app/src/main/java/at/bitfire/davdroid/syncadapter/ContactsSyncManager.kt +++ b/app/src/main/java/at/bitfire/davdroid/syncadapter/ContactsSyncManager.kt @@ -269,13 +269,6 @@ class ContactsSyncManager( override fun downloadRemote(bunch: List) { Logger.log.info("Downloading ${bunch.size} vCard(s): $bunch") - /* Always use addressbook-multi-get to get the vCards. - We don't use single GETs anymore, because then there are two methods, and in case of a bad - server (like iCloud) syncing could fail when there's only one changed resource - and succeed when there are multiple changed resources (or vice versa). This is intransparent - to the user. Also, when there's only one method, there's less code to maintain and there - are less possibilities for errors. - */ useRemoteCollection { it.multiget(bunch, hasVCard4) { response, _ -> useRemote(response) { diff --git a/app/src/main/java/at/bitfire/davdroid/syncadapter/SyncManager.kt b/app/src/main/java/at/bitfire/davdroid/syncadapter/SyncManager.kt index b263e3253..b1b5c0e8c 100644 --- a/app/src/main/java/at/bitfire/davdroid/syncadapter/SyncManager.kt +++ b/app/src/main/java/at/bitfire/davdroid/syncadapter/SyncManager.kt @@ -593,6 +593,24 @@ abstract class SyncManager, out CollectionType: L return Pair(syncToken!!, furtherResults) } + /** + * Downloads and processes resources, given as a list of URLs. Will be called with a list + * of changed/new remote resources. + * + * Implementations should not use GET to fetch single resources, but always multi-get, even + * for single resources for these reasons: + * + * 1. GET can only be used without HTTP compression, because it may change the ETag. + * multi-get sends the ETag in the XML body, so there's no problem with compression. + * 2. Some servers are wrongly configured to suppress the ETag header in the response. + * With multi-get, the ETag is in the XML body, so it won't be affected by that. + * 3. If there are two methods to download resources (GET and multi-get), both methods + * have to be implemented, tested and maintained. Given that multi-get is required + * in any case, it's better to have only one method. + * 4. For users, it's strange behavior when DAVx5 can download multiple remote changes, + * but not a single one (or vice versa). So only one method is more user-friendly. + * 5. March 2020: iCloud now crashes with HTTP 500 upon CardDAV GET requests. + */ protected abstract fun downloadRemote(bunch: List) /** diff --git a/app/src/main/java/at/bitfire/davdroid/syncadapter/TasksSyncManager.kt b/app/src/main/java/at/bitfire/davdroid/syncadapter/TasksSyncManager.kt index a41b52916..f79910c7f 100644 --- a/app/src/main/java/at/bitfire/davdroid/syncadapter/TasksSyncManager.kt +++ b/app/src/main/java/at/bitfire/davdroid/syncadapter/TasksSyncManager.kt @@ -13,7 +13,6 @@ import android.content.Context import android.content.SyncResult import android.os.Bundle import at.bitfire.dav4jvm.DavCalendar -import at.bitfire.dav4jvm.DavResource import at.bitfire.dav4jvm.DavResponseCallback import at.bitfire.dav4jvm.Response import at.bitfire.dav4jvm.exception.DavException @@ -93,41 +92,26 @@ class TasksSyncManager( override fun downloadRemote(bunch: List) { Logger.log.info("Downloading ${bunch.size} iCalendars: $bunch") - if (bunch.size == 1) { - val remote = bunch.first() - // only one contact, use GET - useRemote(DavResource(httpClient.okHttpClient, remote)) { resource -> - resource.get(DavCalendar.MIME_ICALENDAR.toString()) { response -> - // CalDAV servers MUST return ETag on GET [https://tools.ietf.org/html/rfc4791#section-5.3.4] - val eTag = response.header("ETag")?.let { GetETag(it).eTag } - ?: throw DavException("Received CalDAV GET response without ETag") - - response.body()!!.use { - processVTodo(resource.fileName(), eTag, it.charStream()) + // multiple iCalendars, use calendar-multi-get + useRemoteCollection { + it.multiget(bunch) { response, _ -> + useRemote(response) { + if (!response.isSuccess()) { + Logger.log.warning("Received non-successful multiget response for ${response.href}") + return@useRemote } - } - } - } else - // multiple iCalendars, use calendar-multi-get - useRemoteCollection { - it.multiget(bunch) { response, _ -> - useRemote(response) { - if (!response.isSuccess()) { - Logger.log.warning("Received non-successful multiget response for ${response.href}") - return@useRemote - } - val eTag = response[GetETag::class.java]?.eTag - ?: throw DavException("Received multi-get response without ETag") + val eTag = response[GetETag::class.java]?.eTag + ?: throw DavException("Received multi-get response without ETag") - val calendarData = response[CalendarData::class.java] - val iCal = calendarData?.iCalendar - ?: throw DavException("Received multi-get response without address data") + val calendarData = response[CalendarData::class.java] + val iCal = calendarData?.iCalendar + ?: throw DavException("Received multi-get response without address data") - processVTodo(DavUtils.lastSegmentOfUrl(response.href), eTag, StringReader(iCal)) - } + processVTodo(DavUtils.lastSegmentOfUrl(response.href), eTag, StringReader(iCal)) } } + } } override fun postProcess() { -- GitLab From 213851856e23a4805de1da4cda931f658eb7a07d Mon Sep 17 00:00:00 2001 From: Alex Baker Date: Thu, 5 Mar 2020 17:52:41 -0600 Subject: [PATCH 023/783] Clear password error on text changed --- .../bitfire/davdroid/ui/setup/DefaultLoginCredentialsModel.kt | 4 ++++ app/src/main/res/layout/login_credentials_fragment.xml | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/app/src/main/java/at/bitfire/davdroid/ui/setup/DefaultLoginCredentialsModel.kt b/app/src/main/java/at/bitfire/davdroid/ui/setup/DefaultLoginCredentialsModel.kt index ba32312d8..3dada909b 100644 --- a/app/src/main/java/at/bitfire/davdroid/ui/setup/DefaultLoginCredentialsModel.kt +++ b/app/src/main/java/at/bitfire/davdroid/ui/setup/DefaultLoginCredentialsModel.kt @@ -30,6 +30,10 @@ class DefaultLoginCredentialsModel: ViewModel() { loginWithEmailAddress.value = true } + fun clearPasswordError(s: CharSequence, start: Int, before: Int, count: Int) { + passwordError.value = null + } + @MainThread fun initialize(intent: Intent) { if (initialized) diff --git a/app/src/main/res/layout/login_credentials_fragment.xml b/app/src/main/res/layout/login_credentials_fragment.xml index a29656915..07001527f 100644 --- a/app/src/main/res/layout/login_credentials_fragment.xml +++ b/app/src/main/res/layout/login_credentials_fragment.xml @@ -79,6 +79,7 @@ android:hint="@string/login_password" app:endIconMode="password_toggle" app:error="@{model.passwordError}"> + @@ -139,6 +141,7 @@ android:hint="@string/login_password" app:endIconMode="password_toggle" app:error="@{model.passwordError}"> + -- GitLab From cfc0130bec6200bcfea755659f322c9f7f714181 Mon Sep 17 00:00:00 2001 From: Ricki Hirner Date: Fri, 6 Mar 2020 12:07:50 +0100 Subject: [PATCH 024/783] Version bump to 2.6.5-beta1; update dependencies --- app/build.gradle | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 71f049b94..b6b203ee4 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -19,7 +19,7 @@ android { defaultConfig { applicationId "at.bitfire.davdroid" - versionCode 332 + versionCode 333 buildConfigField "long", "buildTime", System.currentTimeMillis() + "L" buildConfigField "boolean", "customCerts", "true" @@ -44,7 +44,7 @@ android { flavorDimensions "distribution" productFlavors { standard { - versionName "2.6.5-ose" + versionName "2.6.5-beta1-ose" } } @@ -117,7 +117,7 @@ dependencies { implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0' implementation 'androidx.paging:paging-runtime-ktx:2.1.1' implementation 'androidx.preference:preference:1.1.0' - implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0-alpha03' + implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0-beta01' implementation 'com.google.android:flexbox:1.1.0' implementation 'com.google.android.material:material:1.2.0-alpha05' -- GitLab From c06950751b5861d2c5df62eddff948a136748fd0 Mon Sep 17 00:00:00 2001 From: Ricki Hirner Date: Sat, 7 Mar 2020 12:28:08 +0100 Subject: [PATCH 025/783] Version bump to 2.6.5 --- app/build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index b6b203ee4..304b87ee0 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -19,7 +19,7 @@ android { defaultConfig { applicationId "at.bitfire.davdroid" - versionCode 333 + versionCode 334 buildConfigField "long", "buildTime", System.currentTimeMillis() + "L" buildConfigField "boolean", "customCerts", "true" @@ -44,7 +44,7 @@ android { flavorDimensions "distribution" productFlavors { standard { - versionName "2.6.5-beta1-ose" + versionName "2.6.5-ose" } } -- GitLab From a0816c11d2664316372ff0340fce52ab6fda4d32 Mon Sep 17 00:00:00 2001 From: Ricki Hirner Date: Wed, 8 Jan 2020 07:55:23 +0100 Subject: [PATCH 026/783] Show languages in About --- app/src/main/assets/translators.json | 1 + .../at/bitfire/davdroid/ui/AboutActivity.kt | 121 +++++++++++++++++- app/src/main/res/layout/about_languages.xml | 7 + app/src/main/res/layout/about_translation.xml | 43 +++++++ app/src/main/res/values/strings.xml | 1 + scripts/fetch-translations.sh | 2 + scripts/rewrite-translators.rb | 14 ++ 7 files changed, 182 insertions(+), 7 deletions(-) create mode 100644 app/src/main/assets/translators.json create mode 100644 app/src/main/res/layout/about_languages.xml create mode 100644 app/src/main/res/layout/about_translation.xml create mode 100755 scripts/rewrite-translators.rb diff --git a/app/src/main/assets/translators.json b/app/src/main/assets/translators.json new file mode 100644 index 000000000..b8a0fcac8 --- /dev/null +++ b/app/src/main/assets/translators.json @@ -0,0 +1 @@ +{"ar_SA":["abdunnasir"],"bg":["dpa_transifex"],"ca":["zagur"],"cs":["pavelb","tomas.odehnal"],"da":["knutztar","mjjzf","Tntdruid_","twikedk"],"de":["anestiskaci","Atalanttore","corppneq","maxkl","nicolas_git","owncube","TheName","Wyrrrd","YvanM"],"el":["anestiskaci","diamond_gr"],"es":["Ark74","Elhea","GranPC","jcvielma","plaguna","polkhas","xphnx"],"eu":["cockeredradiation","Osoitz"],"fa":["ahangarha","amiraliakbari","maryambehzi","mtashackori","Numb","taranehsaei"],"fi_FI":["raketti","tseipii"],"fr":["AlainR","alkino2","Amadeen","boutil","callmemagnus","chfo","chrcha","Floflr","grenatrad","Jorg722","Llorc","Novick","Poussinou","Thecross","YvanM","ÉricB."],"fr_FR":["chrcha","Llorc","Poussinou"],"gl":["pikamoku"],"hu":["jtg"],"it":["Damtux","ed0","FranzMari","noccio","nwandy","rickyroo","technezio"],"ja":["Naofumi"],"nb_NO":["elonus"],"nl":["davtemp","dehart","toonvangerwen","XtremeNova"],"pl":["gsz","mg6","oskarjakiela","TheName","TORminator"],"pt_BR":["amalvarenga","wanderlei.huttel"],"ru":["aigoshin","anm","astalavister","nick.savin","vaddd"],"sk_SK":["brango67","tiborepcek"],"sl_SI":["MrLaaky","uroszor"],"sr":["daimonion"],"szl":["chlodny"],"tr_TR":["ooguz","pultars"],"uk":["androsua","olexn","twixi007"],"uk_UA":["astalavister"],"zh_CN":["anolir","mofitt2016","oksjd","phy"],"zh_TW":["mofitt2016","phy"]} diff --git a/app/src/main/java/at/bitfire/davdroid/ui/AboutActivity.kt b/app/src/main/java/at/bitfire/davdroid/ui/AboutActivity.kt index 4da58cf4f..dc47d61f0 100644 --- a/app/src/main/java/at/bitfire/davdroid/ui/AboutActivity.kt +++ b/app/src/main/java/at/bitfire/davdroid/ui/AboutActivity.kt @@ -14,7 +14,9 @@ import android.os.Bundle import android.text.Spanned import android.util.DisplayMetrics import android.view.* +import androidx.annotation.UiThread import androidx.appcompat.app.AppCompatActivity +import androidx.cardview.widget.CardView import androidx.core.text.HtmlCompat import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentManager @@ -23,13 +25,18 @@ import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.MutableLiveData import androidx.lifecycle.Observer import androidx.lifecycle.ViewModelProvider +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView import at.bitfire.davdroid.App import at.bitfire.davdroid.BuildConfig import at.bitfire.davdroid.R import com.mikepenz.aboutlibraries.LibsBuilder import kotlinx.android.synthetic.main.about.* +import kotlinx.android.synthetic.main.about_languages.* +import kotlinx.android.synthetic.main.about_translation.view.* import kotlinx.android.synthetic.main.activity_about.* import org.apache.commons.io.IOUtils +import org.json.JSONObject import java.text.SimpleDateFormat import java.util.* import kotlin.concurrent.thread @@ -70,17 +77,19 @@ class AboutActivity: AppCompatActivity() { fm: FragmentManager ): FragmentPagerAdapter(fm, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) { - override fun getCount() = 2 + override fun getCount() = 3 override fun getPageTitle(position: Int): String = when (position) { 0 -> getString(R.string.app_name) + 1 -> getString(R.string.about_languages) else -> getString(R.string.about_libraries) } override fun getItem(position: Int) = when (position) { 0 -> AppFragment() + 1 -> LanguagesFragment() else -> LibsBuilder() .withAutoDetect(false) .withFields(R.string::class.java.fields) @@ -108,7 +117,8 @@ class AboutActivity: AppCompatActivity() { if (true /* open-source version */) { warranty.setText(R.string.about_license_info_no_warranty) - val model = ViewModelProvider(this).get(LicenseModel::class.java) + val model = ViewModelProvider(this).get(TextFileModel::class.java) + model.initialize("gplv3.html", true) model.htmlText.observe(viewLifecycleOwner, Observer { spanned -> license_text.text = spanned }) @@ -117,19 +127,116 @@ class AboutActivity: AppCompatActivity() { } - class LicenseModel( + class LanguagesFragment: Fragment() { + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?) = + inflater.inflate(R.layout.about_languages, container, false)!! + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + val model = ViewModelProvider(this).get(TextFileModel::class.java) + model.initialize("translators.json", false) + model.plainText.observe(viewLifecycleOwner, Observer { json -> + val jsonTranslations = JSONObject(json) + translators.adapter = TranslationsAdapter(jsonTranslations) + + /*for (locale in Locale.getAvailableLocales()) { + text.append(locale.toLanguageTag()).append("
") + } + for (langCode in languages.keys()) { + text.append(langCode).append("
") + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + text.append(langCode).append("
") + val langTag = langCode.replace('_', '-') + + text.append(Locale.forLanguageTag(langTag).displayName).append("
") + } + + val translators = langCode.getJSONArray("translators") + for (j in 0 until translators.length()) { + val translator = translators.getString(j) + text.append("@").append(translator).append(" (Transifex)
") + } + text.append("
") + } + translators.setText(HtmlCompat.fromHtml(text.toString(), HtmlCompat.FROM_HTML_MODE_COMPACT))*/ + }) + + translators.layoutManager = LinearLayoutManager(requireActivity()) + } + + class Translation( + val langCode: String, + val translators: Array + ) + + class TranslationsAdapter( + jsonTranslations: JSONObject + ): RecyclerView.Adapter() { + class ViewHolder(val cardView: CardView): RecyclerView.ViewHolder(cardView) + + private val translations = LinkedList() + + init { + for (langCode in jsonTranslations.keys()) { + val jsonTranslators = jsonTranslations.getJSONArray(langCode) + val translators = Array(jsonTranslators.length()) { + idx -> jsonTranslators.getString(idx) + } + translations += Translation(langCode, translators) + } + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { + val tv = LayoutInflater.from(parent.context).inflate(R.layout.about_translation, parent, false) as CardView + return ViewHolder(tv) + } + + override fun onBindViewHolder(holder: ViewHolder, position: Int) { + val translation = translations[position] + holder.cardView.apply { + languageCode.text = translation.langCode + + val langTag = translation.langCode.replace('_', '-') + language.text = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) + Locale.forLanguageTag(langTag).displayName + else + langTag + + translators.text = translation.translators.joinToString(" · ") + } + } + + override fun getItemCount() = translations.size + } + + } + + + class TextFileModel( application: Application ): AndroidViewModel(application) { + var initialized = false val htmlText = MutableLiveData() + val plainText = MutableLiveData() + + @UiThread + fun initialize(assetName: String, html: Boolean) { + if (initialized) return - init { thread { - getApplication().resources.assets.open("gplv3.html").use { - val spanned = HtmlCompat.fromHtml(IOUtils.toString(it, Charsets.UTF_8), HtmlCompat.FROM_HTML_MODE_LEGACY) - htmlText.postValue(spanned) + getApplication().resources.assets.open(assetName).use { + val raw = IOUtils.toString(it, Charsets.UTF_8) + if (html) { + val spanned = HtmlCompat.fromHtml(raw, HtmlCompat.FROM_HTML_MODE_LEGACY) + htmlText.postValue(spanned) + } else + plainText.postValue(raw) } } + + initialized = true } } diff --git a/app/src/main/res/layout/about_languages.xml b/app/src/main/res/layout/about_languages.xml new file mode 100644 index 000000000..644974ba7 --- /dev/null +++ b/app/src/main/res/layout/about_languages.xml @@ -0,0 +1,7 @@ + + \ No newline at end of file diff --git a/app/src/main/res/layout/about_translation.xml b/app/src/main/res/layout/about_translation.xml new file mode 100644 index 000000000..60e9171ea --- /dev/null +++ b/app/src/main/res/layout/about_translation.xml @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index ce4bc163f..565621717 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -50,6 +50,7 @@ Install OpenTasks + Languages Libraries Version %1$s (%2$d) Compiled on %s diff --git a/scripts/fetch-translations.sh b/scripts/fetch-translations.sh index 4b3c8f38e..f14f1ff29 100755 --- a/scripts/fetch-translations.sh +++ b/scripts/fetch-translations.sh @@ -42,3 +42,5 @@ do fetch_txt "https://www.transifex.com/api/2/project/davx5/resource/metadata-full-description/translation/$lang?file" ${android[$lang]} full_description.txt fetch_txt "https://www.transifex.com/api/2/project/davx5/resource/metadata-short-description/translation/$lang?file" ${android[$lang]} short_description.txt done +curl -n https://www.transifex.com/api/2/project/davx5/languages/ | ./rewrite-translators.rb >../app/src/main/assets/translators.json + diff --git a/scripts/rewrite-translators.rb b/scripts/rewrite-translators.rb new file mode 100755 index 000000000..457adb949 --- /dev/null +++ b/scripts/rewrite-translators.rb @@ -0,0 +1,14 @@ +#!/usr/bin/ruby + +require 'json' + +contributors = {} + +transifex = JSON.parse(STDIN.read, :symbolize_names => true) +for t in transifex + lang = t[:language_code] + people = t[:translators] + contributors[lang] = people.sort_by { |nick| nick.downcase } +end + +puts contributors.sort.to_h.to_json -- GitLab From 8a46fcedbad35fcb00a89371b619026d26b51c25 Mon Sep 17 00:00:00 2001 From: Ricki Hirner Date: Sun, 29 Mar 2020 17:56:55 +0200 Subject: [PATCH 027/783] Do full resync if "past event time limit" is changed from number to null --- .../at/bitfire/davdroid/ui/account/SettingsActivity.kt | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/at/bitfire/davdroid/ui/account/SettingsActivity.kt b/app/src/main/java/at/bitfire/davdroid/ui/account/SettingsActivity.kt index 5fa469b16..e4c3c167c 100644 --- a/app/src/main/java/at/bitfire/davdroid/ui/account/SettingsActivity.kt +++ b/app/src/main/java/at/bitfire/davdroid/ui/account/SettingsActivity.kt @@ -503,7 +503,12 @@ class SettingsActivity: AppCompatActivity() { accountSettings?.setTimeRangePastDays(days) reload() - resyncCalendars(fullResync = false, tasks = false) + /* If the new setting is a certain number of days, no full resync is required, + because every sync will cause a REPORT calendar-query with the given number of days. + However, if the new setting is "all events", collection sync may/should be used, so + the last sync-token has to be reset, which is done by setting fullResync=true. + */ + resyncCalendars(fullResync = days == null, tasks = false) } fun updateDefaultAlarm(minBefore: Int?) { -- GitLab From 58ca99198f08406c6813ec1e31702953b32199e3 Mon Sep 17 00:00:00 2001 From: Ricki Hirner Date: Sat, 21 Mar 2020 12:58:30 +0100 Subject: [PATCH 028/783] Introduce IntroActivity instead of startup fragments --- app/build.gradle | 13 +- app/src/main/AndroidManifest.xml | 1 + app/src/main/java/at/bitfire/davdroid/App.kt | 9 +- .../bitfire/davdroid/ui/AccountsActivity.kt | 29 ++- .../davdroid/ui/AppSettingsActivity.kt | 11 +- .../davdroid/ui/StartupDialogFragment.kt | 164 ---------------- .../ui/intro/BatteryOptimizationsFragment.kt | 180 ++++++++++++++++++ .../ui/intro/IIntroFragmentFactory.kt | 37 ++++ .../davdroid/ui/intro/IntroActivity.kt | 55 ++++++ .../davdroid/ui/intro/OpenSourceFragment.kt | 76 ++++++++ .../davdroid/ui/intro/OpenTasksFragment.kt | 143 ++++++++++++++ .../davdroid/ui/intro/WelcomeFragment.kt | 26 +++ .../davdroid/ui/widget/CropImageView.kt | 99 ++++++++++ .../main/res/drawable/accounts_background.xml | 6 + .../res/drawable/intro_logo_background.xml | 3 + .../main/res/drawable/intro_open_source.xml | 6 + app/src/main/res/drawable/intro_tasks.xml | 6 + .../main/res/layout-land/intro_welcome.xml | 71 +++++++ app/src/main/res/layout/about.xml | 3 +- app/src/main/res/layout/account_list.xml | 36 +++- .../layout/intro_battery_optimizations.xml | 158 +++++++++++++++ app/src/main/res/layout/intro_open_source.xml | 111 +++++++++++ app/src/main/res/layout/intro_opentasks.xml | 159 ++++++++++++++++ app/src/main/res/layout/intro_welcome.xml | 76 ++++++++ app/src/main/res/values-ar/strings.xml | 2 +- app/src/main/res/values-bg/strings.xml | 2 +- app/src/main/res/values-ca/strings.xml | 2 +- app/src/main/res/values-cs/strings.xml | 2 +- app/src/main/res/values-da/strings.xml | 2 +- app/src/main/res/values-de/strings.xml | 5 +- app/src/main/res/values-el/strings.xml | 2 +- app/src/main/res/values-es/strings.xml | 2 +- app/src/main/res/values-eu/strings.xml | 2 +- app/src/main/res/values-fa/strings.xml | 2 +- app/src/main/res/values-fi/strings.xml | 2 +- app/src/main/res/values-fr/strings.xml | 2 +- app/src/main/res/values-gl/strings.xml | 2 +- app/src/main/res/values-h480dp/dimen.xml | 4 + app/src/main/res/values-hu/strings.xml | 2 +- app/src/main/res/values-it/strings.xml | 2 +- app/src/main/res/values-ja/strings.xml | 2 +- app/src/main/res/values-nb-rNO/strings.xml | 2 +- app/src/main/res/values-nl/strings.xml | 2 +- app/src/main/res/values-pl/strings.xml | 2 +- app/src/main/res/values-pt-rBR/strings.xml | 2 +- app/src/main/res/values-ru/strings.xml | 2 +- app/src/main/res/values-sk/strings.xml | 2 +- app/src/main/res/values-sl-rSI/strings.xml | 2 +- app/src/main/res/values-sr/strings.xml | 2 +- app/src/main/res/values-szl/strings.xml | 2 +- app/src/main/res/values-uk/strings.xml | 2 +- app/src/main/res/values-v21/styles.xml | 17 -- app/src/main/res/values-zh-rCN/strings.xml | 2 +- app/src/main/res/values/attrs.xml | 5 +- app/src/main/res/values/dimen.xml | 4 + app/src/main/res/values/strings.xml | 41 ++-- app/src/main/res/values/styles.xml | 16 +- ...re.davdroid.ui.intro.IIntroFragmentFactory | 4 + build.gradle | 2 +- 59 files changed, 1371 insertions(+), 257 deletions(-) delete mode 100644 app/src/main/java/at/bitfire/davdroid/ui/StartupDialogFragment.kt create mode 100644 app/src/main/java/at/bitfire/davdroid/ui/intro/BatteryOptimizationsFragment.kt create mode 100644 app/src/main/java/at/bitfire/davdroid/ui/intro/IIntroFragmentFactory.kt create mode 100644 app/src/main/java/at/bitfire/davdroid/ui/intro/IntroActivity.kt create mode 100644 app/src/main/java/at/bitfire/davdroid/ui/intro/OpenSourceFragment.kt create mode 100644 app/src/main/java/at/bitfire/davdroid/ui/intro/OpenTasksFragment.kt create mode 100644 app/src/main/java/at/bitfire/davdroid/ui/intro/WelcomeFragment.kt create mode 100644 app/src/main/java/at/bitfire/davdroid/ui/widget/CropImageView.kt create mode 100644 app/src/main/res/drawable/accounts_background.xml create mode 100644 app/src/main/res/drawable/intro_logo_background.xml create mode 100644 app/src/main/res/drawable/intro_open_source.xml create mode 100644 app/src/main/res/drawable/intro_tasks.xml create mode 100644 app/src/main/res/layout-land/intro_welcome.xml create mode 100644 app/src/main/res/layout/intro_battery_optimizations.xml create mode 100644 app/src/main/res/layout/intro_open_source.xml create mode 100644 app/src/main/res/layout/intro_opentasks.xml create mode 100644 app/src/main/res/layout/intro_welcome.xml create mode 100644 app/src/main/res/values-h480dp/dimen.xml delete mode 100644 app/src/main/res/values-v21/styles.xml create mode 100644 app/src/main/resources/META-INF/services/at.bitfire.davdroid.ui.intro.IIntroFragmentFactory diff --git a/app/build.gradle b/app/build.gradle index 304b87ee0..6cc8c0620 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -111,24 +111,25 @@ dependencies { implementation 'androidx.appcompat:appcompat:1.1.0' implementation 'androidx.cardview:cardview:1.0.0' - implementation 'androidx.constraintlayout:constraintlayout:1.1.3' - implementation 'androidx.fragment:fragment-ktx:1.2.2' + implementation 'androidx.constraintlayout:constraintlayout:2.0.0-beta4' + implementation 'androidx.fragment:fragment-ktx:1.2.3' implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0' implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0' - implementation 'androidx.paging:paging-runtime-ktx:2.1.1' + implementation 'androidx.paging:paging-runtime-ktx:2.1.2' implementation 'androidx.preference:preference:1.1.0' implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0-beta01' implementation 'com.google.android:flexbox:1.1.0' implementation 'com.google.android.material:material:1.2.0-alpha05' - def room_version = '2.2.4' + def room_version = '2.2.5' implementation "androidx.room:room-runtime:$room_version" implementation "androidx.room:room-ktx:$room_version" kapt "androidx.room:room-compiler:$room_version" - implementation 'com.gitlab.bitfireAT:dav4jvm:1.0.1' implementation 'com.jaredrummler:colorpicker:1.1.0' - implementation('com.mikepenz:aboutlibraries:7.1.0') + implementation 'com.github.AppIntro:AppIntro:5.1.0' + implementation 'com.gitlab.bitfireAT:dav4jvm:1.0.1' + implementation 'com.mikepenz:aboutlibraries:7.1.0' implementation "com.squareup.okhttp3:okhttp:${versions.okhttp}" implementation "com.squareup.okhttp3:logging-interceptor:${versions.okhttp}" implementation 'commons-io:commons-io:2.6' diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index bcb39eed0..d2d9ccb62 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -47,6 +47,7 @@ + { - val dialogs = LinkedList() - val settings = Settings.getInstance(context) - - if (System.currentTimeMillis() > settings.getLong(SETTING_NEXT_DONATION_POPUP) ?: 0) - dialogs += StartupDialogFragment.instantiate(Mode.OSE_DONATE) - - // battery optimization white-listing - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && settings.getBoolean(HINT_BATTERY_OPTIMIZATIONS) != false) { - val powerManager = context.getSystemService(Context.POWER_SERVICE) as PowerManager - if (!powerManager.isIgnoringBatteryOptimizations(BuildConfig.APPLICATION_ID)) - dialogs.add(instantiate(Mode.BATTERY_OPTIMIZATIONS)) - } - - // vendor-specific auto-start information - if (autostartManufacturers.contains(Build.MANUFACTURER.toLowerCase(Locale.ROOT)) && settings.getBoolean(HINT_AUTOSTART_PERMISSIONS) != false) - dialogs.add(instantiate(Mode.AUTOSTART_PERMISSIONS)) - - // OpenTasks information - if (true /* don't show in other flavors */) - if (!LocalTaskList.tasksProviderAvailable(context) && settings.getBoolean(HINT_OPENTASKS_NOT_INSTALLED) != false) - dialogs.add(instantiate(Mode.OPENTASKS_NOT_INSTALLED)) - - return dialogs.reversed() - } - - fun instantiate(mode: Mode): StartupDialogFragment { - val frag = StartupDialogFragment() - val args = Bundle(1) - args.putString(ARGS_MODE, mode.name) - frag.arguments = args - return frag - } - - } - - - @SuppressLint("BatteryLife") - override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { - isCancelable = false - - val settings = Settings.getInstance(requireActivity()) - val activity = requireActivity() - return when (Mode.valueOf(requireArguments().getString(ARGS_MODE)!!)) { - Mode.AUTOSTART_PERMISSIONS -> - MaterialAlertDialogBuilder(activity) - .setIcon(R.drawable.ic_error_dark) - .setTitle(R.string.startup_autostart_permission) - .setMessage(getString(R.string.startup_autostart_permission_message, Build.MANUFACTURER)) - .setPositiveButton(R.string.startup_more_info) { _, _ -> - UiUtils.launchUri(requireActivity(), App.homepageUrl(requireActivity()).buildUpon() - .appendPath("faq").appendPath("synchronization-is-not-run-as-expected").build()) - } - .setNeutralButton(R.string.startup_not_now) { _, _ -> } - .setNegativeButton(R.string.startup_dont_show_again) { _, _ -> - settings.putBoolean(HINT_AUTOSTART_PERMISSIONS, false) - } - .create() - - Mode.BATTERY_OPTIMIZATIONS -> - MaterialAlertDialogBuilder(activity) - .setIcon(R.drawable.ic_info_dark) - .setTitle(R.string.startup_battery_optimization) - .setMessage(R.string.startup_battery_optimization_message) - .setPositiveButton(R.string.startup_battery_optimization_disable) @TargetApi(Build.VERSION_CODES.M) { _, _ -> - UiUtils.launchUri(requireActivity(), Uri.parse("package:" + BuildConfig.APPLICATION_ID), - android.provider.Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS) - } - .setNeutralButton(R.string.startup_not_now) { _, _ -> } - .setNegativeButton(R.string.startup_dont_show_again) { _: DialogInterface, _: Int -> - settings.putBoolean(HINT_BATTERY_OPTIMIZATIONS, false) - } - .create() - - Mode.OPENTASKS_NOT_INSTALLED -> { - val builder = StringBuilder(getString(R.string.startup_opentasks_not_installed_message)) - if (Build.VERSION.SDK_INT < 23) - builder.append("\n\n").append(getString(R.string.startup_opentasks_reinstall_davx5)) - return MaterialAlertDialogBuilder(activity) - .setIcon(R.drawable.ic_playlist_add_check_dark) - .setTitle(R.string.startup_opentasks_not_installed) - .setMessage(builder.toString()) - .setPositiveButton(R.string.startup_opentasks_not_installed_install) { _, _ -> - if (!UiUtils.launchUri(requireActivity(), Uri.parse("market://details?id=org.dmfs.tasks"), toastInstallBrowser = false)) - Logger.log.warning("No market app available, can't install OpenTasks") - } - .setNeutralButton(R.string.startup_not_now) { _, _ -> } - .setNegativeButton(R.string.startup_dont_show_again) { _: DialogInterface, _: Int -> - settings.putBoolean(HINT_OPENTASKS_NOT_INSTALLED, false) - } - .create() - } - - Mode.OSE_DONATE -> - return MaterialAlertDialogBuilder(activity) - .setIcon(R.mipmap.ic_launcher) - .setTitle(R.string.startup_donate) - .setMessage(R.string.startup_donate_message) - .setPositiveButton(R.string.startup_donate_now) { _, _ -> - UiUtils.launchUri(requireActivity(), App.homepageUrl(requireActivity()).buildUpon() - .appendPath("donate") - .build()) - settings.putLong(SETTING_NEXT_DONATION_POPUP, System.currentTimeMillis() + 30 * 86400000L) // 30 days - } - .setNegativeButton(R.string.startup_donate_later) { _, _ -> - settings.putLong(SETTING_NEXT_DONATION_POPUP, System.currentTimeMillis() + 14 * 86400000L) // 14 days - } - .create() - - } - } - -} diff --git a/app/src/main/java/at/bitfire/davdroid/ui/intro/BatteryOptimizationsFragment.kt b/app/src/main/java/at/bitfire/davdroid/ui/intro/BatteryOptimizationsFragment.kt new file mode 100644 index 000000000..00eebd0cd --- /dev/null +++ b/app/src/main/java/at/bitfire/davdroid/ui/intro/BatteryOptimizationsFragment.kt @@ -0,0 +1,180 @@ +package at.bitfire.davdroid.ui.intro + +import android.annotation.SuppressLint +import android.app.Application +import android.content.Context +import android.content.Intent +import android.net.Uri +import android.os.Build +import android.os.Bundle +import android.os.PowerManager +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.core.content.ContextCompat +import androidx.databinding.ObservableBoolean +import androidx.fragment.app.Fragment +import androidx.lifecycle.AndroidViewModel +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.Observer +import androidx.lifecycle.ViewModelProvider +import at.bitfire.davdroid.App +import at.bitfire.davdroid.BuildConfig +import at.bitfire.davdroid.R +import at.bitfire.davdroid.databinding.IntroBatteryOptimizationsBinding +import at.bitfire.davdroid.settings.Settings +import at.bitfire.davdroid.ui.UiUtils +import at.bitfire.davdroid.ui.intro.BatteryOptimizationsFragment.Model.Companion.HINT_AUTOSTART_PERMISSION +import at.bitfire.davdroid.ui.intro.BatteryOptimizationsFragment.Model.Companion.HINT_BATTERY_OPTIMIZATIONS + +class BatteryOptimizationsFragment: Fragment() { + + companion object { + const val REQUEST_IGNORE_BATTERY_OPTIMIZATIONS = 0 + } + + lateinit var model: Model + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + model = ViewModelProvider(this).get(Model::class.java) + } + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { + val binding = IntroBatteryOptimizationsBinding.inflate(inflater, container, false) + binding.lifecycleOwner = viewLifecycleOwner + binding.model = model + + model.shouldBeWhitelisted.observe(viewLifecycleOwner, Observer { shouldBeWhitelisted -> + @SuppressLint("BatteryLife") + if (shouldBeWhitelisted && !model.isWhitelisted.value!! && Build.VERSION.SDK_INT >= 23) + startActivityForResult(Intent( + android.provider.Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS, + Uri.parse("package:" + BuildConfig.APPLICATION_ID) + ), REQUEST_IGNORE_BATTERY_OPTIMIZATIONS) + }) + + binding.autostartHeading.text = getString(R.string.intro_autostart_title, Build.MANUFACTURER) + binding.autostartText.text = getString(R.string.intro_autostart_text, Build.MANUFACTURER) + binding.autostartMoreInfo.setOnClickListener { + UiUtils.launchUri(requireActivity(), App.homepageUrl(requireActivity()).buildUpon() + .appendPath("faq").appendPath("synchronization-is-not-run-as-expected") + .appendQueryParameter("manufacturer", Build.MANUFACTURER).build()) + } + + binding.infoLeaveUnchecked.text = getString(R.string.intro_leave_unchecked, getString(R.string.app_settings_reset_hints)) + + return binding.root + } + + override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { + super.onActivityResult(requestCode, resultCode, data) + if (requestCode == REQUEST_IGNORE_BATTERY_OPTIMIZATIONS) + model.checkWhitelisted() + } + + + class Model(app: Application): AndroidViewModel(app) { + + companion object { + + /** + * Whether the request for whitelisting from battery optimizations shall be shown. + * If this setting is true or null/not set, the notice shall be shown. Only if this + * setting is false, the notice shall not be shown. + */ + const val HINT_BATTERY_OPTIMIZATIONS = "hint_BatteryOptimizations" + + /** + * Whether the autostart permission notice shall be shown. If this setting is true + * or null/not set, the notice shall be shown. Only if this setting is false, the notice + * shall not be shown. + * + * Type: Boolean + */ + const val HINT_AUTOSTART_PERMISSION = "hint_AutostartPermissions" + + /** + * List of manufacturers which are known to restrict background processes or otherwise + * block synchronization. + * + * See https://www.davx5.com/faq/synchronization-is-not-run-as-expected for why this is evil. + * See https://github.com/jaredrummler/AndroidDeviceNames/blob/master/json/ for manufacturer values. + */ + private val evilManufacturers = arrayOf("asus", "huawei", "lenovo", "letv", "meizu", "nokia", + "oneplus", "oppo", "samsung", "sony", "vivo", "wiko", "xiaomi", "zte") + + /** + * Whether the device has been produced by an evil manufacturer. + * + * Always true for debug builds (to test the UI). + * + * @see evilManufacturers + */ + val evilManufacturer = evilManufacturers.contains(Build.MANUFACTURER.toLowerCase()) || BuildConfig.DEBUG + + fun isWhitelisted(context: Context) = + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + val powerManager = ContextCompat.getSystemService(context, PowerManager::class.java)!! + powerManager.isIgnoringBatteryOptimizations(BuildConfig.APPLICATION_ID) + } else + true + } + + val settings = Settings(app) + + val shouldBeWhitelisted = MutableLiveData() + val isWhitelisted = MutableLiveData() + val dontShowBattery = object: ObservableBoolean() { + override fun get() = settings.getBoolean(HINT_BATTERY_OPTIMIZATIONS) == false + override fun set(dontShowAgain: Boolean) { + if (dontShowAgain) + settings.putBoolean(HINT_BATTERY_OPTIMIZATIONS, false) + else + settings.remove(HINT_BATTERY_OPTIMIZATIONS) + } + } + + val dontShowAutostart = object: ObservableBoolean() { + override fun get() = settings.getBoolean(HINT_AUTOSTART_PERMISSION) == false + override fun set(dontShowAgain: Boolean) { + if (dontShowAgain) + settings.putBoolean(HINT_AUTOSTART_PERMISSION, false) + else + settings.remove(HINT_AUTOSTART_PERMISSION) + } + } + + init { + checkWhitelisted() + } + + fun checkWhitelisted() { + val whitelisted = isWhitelisted(getApplication()) + isWhitelisted.value = whitelisted + shouldBeWhitelisted.value = whitelisted + + // if DAVx5 is whitelisted, always show a reminder as soon as it's not whitelisted anymore + if (whitelisted) + settings.remove(HINT_BATTERY_OPTIMIZATIONS) + } + + } + + + class Factory: IIntroFragmentFactory { + + override fun shouldBeShown(context: Context, settings: Settings) = + // show fragment when: + // 1. DAVx5 is not whitelisted yet and "don't show anymore" has not been clicked, and/or + // 2. evil manufacturer and "don't show anymore" has not been clicked + if ((!Model.isWhitelisted(context) && settings.getBoolean(HINT_BATTERY_OPTIMIZATIONS) != false) || + (Model.evilManufacturer && settings.getBoolean(HINT_AUTOSTART_PERMISSION) != false)) + IIntroFragmentFactory.ShowMode.SHOW + else + IIntroFragmentFactory.ShowMode.DONT_SHOW + + override fun create() = BatteryOptimizationsFragment() + } + +} \ No newline at end of file diff --git a/app/src/main/java/at/bitfire/davdroid/ui/intro/IIntroFragmentFactory.kt b/app/src/main/java/at/bitfire/davdroid/ui/intro/IIntroFragmentFactory.kt new file mode 100644 index 000000000..d04090c41 --- /dev/null +++ b/app/src/main/java/at/bitfire/davdroid/ui/intro/IIntroFragmentFactory.kt @@ -0,0 +1,37 @@ +package at.bitfire.davdroid.ui.intro + +import android.content.Context +import androidx.fragment.app.Fragment +import at.bitfire.davdroid.settings.Settings + +interface IIntroFragmentFactory { + + enum class ShowMode { + /** show the fragment */ + SHOW, + /** show the fragment only when there is at least one other fragment with mode [SHOW] */ + SHOW_NOT_ALONE, + /** don't show the fragment */ + DONT_SHOW + } + + /** + * Used to determine whether an intro fragment of this type (for instance, + * the [IntroActivity.BatteryOptimizationsFragment]) should be shown. + * + * @param context used to determine whether the fragment shall be shown + * @param settings used to determine whether the fragment shall be shown + * + * @return whether an instance of this fragment type shall be created and shown + */ + fun shouldBeShown(context: Context, settings: Settings): ShowMode + + /** + * Creates an instance of this intro fragment type. Will only be called when + * [shouldBeShown] is true. + * + * @return the fragment (for instance, a [IntroActivity.BatteryOptimizationsFragment]]) + */ + fun create(): Fragment + +} \ No newline at end of file diff --git a/app/src/main/java/at/bitfire/davdroid/ui/intro/IntroActivity.kt b/app/src/main/java/at/bitfire/davdroid/ui/intro/IntroActivity.kt new file mode 100644 index 000000000..9d36af3c9 --- /dev/null +++ b/app/src/main/java/at/bitfire/davdroid/ui/intro/IntroActivity.kt @@ -0,0 +1,55 @@ +package at.bitfire.davdroid.ui.intro + +import android.app.Activity +import android.content.Context +import android.os.Bundle +import androidx.fragment.app.Fragment +import at.bitfire.davdroid.R +import at.bitfire.davdroid.settings.Settings +import at.bitfire.davdroid.ui.intro.IIntroFragmentFactory.ShowMode +import com.github.paolorotolo.appintro.AppIntro2 +import java.util.* + +class IntroActivity: AppIntro2() { + + companion object { + + private val serviceLoader = ServiceLoader.load(IIntroFragmentFactory::class.java)!! + private val introFragmentFactories = serviceLoader.toList() + + fun shouldShowIntroActivity(context: Context): Boolean { + val settings = Settings.getInstance(context) + return introFragmentFactories.any { it.shouldBeShown(context, settings) == ShowMode.SHOW } + } + + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + val settings = Settings.getInstance(this) + + val factoriesWithMode = introFragmentFactories.associate { Pair(it, it.shouldBeShown(this, settings)) } + val showAll = factoriesWithMode.values.any { it == ShowMode.SHOW } + factoriesWithMode.forEach { factory, mode -> + if (mode == ShowMode.SHOW || (mode == ShowMode.SHOW_NOT_ALONE && showAll)) + addSlide(factory.create()) + } + + setBarColor(resources.getColor(R.color.primaryDarkColor)) + showSkipButton(false) + } + + + override fun onBackPressed() { + if (pager.isFirstSlide(fragments.size)) + setResult(Activity.RESULT_CANCELED) + super.onBackPressed() + } + + override fun onDonePressed(currentFragment: Fragment?) { + super.onDonePressed(currentFragment) + setResult(Activity.RESULT_OK) + finish() + } + +} \ No newline at end of file diff --git a/app/src/main/java/at/bitfire/davdroid/ui/intro/OpenSourceFragment.kt b/app/src/main/java/at/bitfire/davdroid/ui/intro/OpenSourceFragment.kt new file mode 100644 index 000000000..ec2f94596 --- /dev/null +++ b/app/src/main/java/at/bitfire/davdroid/ui/intro/OpenSourceFragment.kt @@ -0,0 +1,76 @@ +package at.bitfire.davdroid.ui.intro + +import android.app.Application +import android.content.Context +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.databinding.ObservableBoolean +import androidx.fragment.app.Fragment +import androidx.lifecycle.AndroidViewModel +import androidx.lifecycle.ViewModelProvider +import at.bitfire.davdroid.App +import at.bitfire.davdroid.R +import at.bitfire.davdroid.databinding.IntroOpenSourceBinding +import at.bitfire.davdroid.settings.Settings +import at.bitfire.davdroid.ui.UiUtils +import at.bitfire.davdroid.ui.intro.OpenSourceFragment.Model.Companion.SETTING_NEXT_DONATION_POPUP + +class OpenSourceFragment: Fragment() { + + lateinit var model: Model + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + model = ViewModelProvider(this).get(Model::class.java) + } + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { + val binding = IntroOpenSourceBinding.inflate(inflater, container, false) + binding.lifecycleOwner = viewLifecycleOwner + binding.model = model + + binding.text.text = getString(R.string.intro_open_source_text, getString(R.string.app_name)) + binding.moreInfo.setOnClickListener { + UiUtils.launchUri(requireActivity(), App.homepageUrl(requireActivity()).buildUpon() + .appendPath("donate") + .build()) + } + + return binding.root + } + + + class Model(app: Application): AndroidViewModel(app) { + + companion object { + const val SETTING_NEXT_DONATION_POPUP = "time_nextDonationPopup" + } + + val dontShow = object: ObservableBoolean() { + val settings = Settings(getApplication()) + override fun set(dontShowAgain: Boolean) { + if (dontShowAgain) { + val nextReminder = System.currentTimeMillis() + 90*86400000L // 90 days (~ 3 months) + settings.putLong(SETTING_NEXT_DONATION_POPUP, nextReminder) + } else + settings.remove(SETTING_NEXT_DONATION_POPUP) + super.set(dontShowAgain) + } + } + } + + class Factory: IIntroFragmentFactory { + + override fun shouldBeShown(context: Context, settings: Settings) = + if (System.currentTimeMillis() > (settings.getLong(SETTING_NEXT_DONATION_POPUP) ?: 0)) + IIntroFragmentFactory.ShowMode.SHOW + else + IIntroFragmentFactory.ShowMode.DONT_SHOW + + override fun create() = OpenSourceFragment() + + } + +} \ No newline at end of file diff --git a/app/src/main/java/at/bitfire/davdroid/ui/intro/OpenTasksFragment.kt b/app/src/main/java/at/bitfire/davdroid/ui/intro/OpenTasksFragment.kt new file mode 100644 index 000000000..26e1b6f49 --- /dev/null +++ b/app/src/main/java/at/bitfire/davdroid/ui/intro/OpenTasksFragment.kt @@ -0,0 +1,143 @@ +package at.bitfire.davdroid.ui.intro + +import android.app.Application +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.content.IntentFilter +import android.net.Uri +import android.os.Bundle +import android.text.method.LinkMovementMethod +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.core.text.HtmlCompat +import androidx.databinding.ObservableBoolean +import androidx.fragment.app.Fragment +import androidx.lifecycle.AndroidViewModel +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.Observer +import androidx.lifecycle.ViewModelProvider +import at.bitfire.davdroid.App +import at.bitfire.davdroid.R +import at.bitfire.davdroid.databinding.IntroOpentasksBinding +import at.bitfire.davdroid.resource.LocalTaskList +import at.bitfire.davdroid.settings.Settings +import at.bitfire.davdroid.ui.UiUtils +import at.bitfire.davdroid.ui.intro.OpenTasksFragment.Model.Companion.HINT_OPENTASKS_NOT_INSTALLED +import com.google.android.material.snackbar.Snackbar + +class OpenTasksFragment: Fragment() { + + lateinit var model: Model + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + model = ViewModelProvider(this).get(Model::class.java) + } + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { + val binding = IntroOpentasksBinding.inflate(inflater, container, false) + binding.lifecycleOwner = viewLifecycleOwner + binding.model = model + + model.shallBeInstalled.observe(viewLifecycleOwner, Observer { shallBeInstalled -> + if (shallBeInstalled && model.isInstalled.value == false) { + // uncheck switch for the moment (until the app is installed) + model.shallBeInstalled.value = false + + // prompt to install OpenTasks + val uri = Uri.parse("market://details?id=org.dmfs.tasks") + val intent = Intent(Intent.ACTION_VIEW, uri) + if (intent.resolveActivity(requireActivity().packageManager) != null) + startActivity(intent) + else + Snackbar.make(binding.root, R.string.intro_tasks_no_app_store, Snackbar.LENGTH_LONG).show() + } + }) + + binding.text1.apply { + text = HtmlCompat.fromHtml(getString(R.string.intro_tasks_text1, getString(R.string.app_name)), 0) + movementMethod = LinkMovementMethod.getInstance() + } + + binding.moreInfo.setOnClickListener { + val context = requireActivity() + UiUtils.launchUri(context, App.homepageUrl(context).buildUpon() + .appendEncodedPath("faq/tasks/advanced-task-features") + .build(), toastInstallBrowser = true) + } + binding.infoLeaveUnchecked.text = getString(R.string.intro_leave_unchecked, getString(R.string.app_settings_reset_hints)) + + return binding.root + } + + + class Model(app: Application) : AndroidViewModel(app) { + + companion object { + + /** + * Whether this fragment (which asks for OpenTasks installation) shall be shown. + * If this setting is true or null/not set, the notice shall be shown. Only if this + * setting is false, the notice shall not be shown. + */ + const val HINT_OPENTASKS_NOT_INSTALLED = "hint_OpenTasksNotInstalled" + + } + + var isInstalled = MutableLiveData() + val shallBeInstalled = MutableLiveData() + val openTasksInstalledReceiver = object: BroadcastReceiver() { + override fun onReceive(context: Context, intent: Intent) { + checkInstalled() + } + } + + val dontShow = object: ObservableBoolean() { + val settings = Settings(getApplication()) + override fun get() = settings.getBoolean(HINT_OPENTASKS_NOT_INSTALLED) == false + override fun set(dontShowAgain: Boolean) { + if (dontShowAgain) + settings.putBoolean(HINT_OPENTASKS_NOT_INSTALLED, false) + else + settings.remove(HINT_OPENTASKS_NOT_INSTALLED) + } + } + + init { + val filter = IntentFilter(Intent.ACTION_PACKAGE_ADDED).apply { + addAction(Intent.ACTION_PACKAGE_CHANGED) + addAction(Intent.ACTION_PACKAGE_REMOVED) + addDataScheme("package") + } + app.registerReceiver(openTasksInstalledReceiver, filter) + checkInstalled() + } + + override fun onCleared() { + getApplication().unregisterReceiver(openTasksInstalledReceiver) + } + + fun checkInstalled() { + val installed = LocalTaskList.tasksProviderAvailable(getApplication()) + isInstalled.postValue(installed) + shallBeInstalled.postValue(installed) + } + + } + + + class Factory: IIntroFragmentFactory { + + override fun shouldBeShown(context: Context, settings: Settings) = + if (!LocalTaskList.tasksProviderAvailable(context) && settings.getBoolean(HINT_OPENTASKS_NOT_INSTALLED) != false) + IIntroFragmentFactory.ShowMode.SHOW + else + IIntroFragmentFactory.ShowMode.DONT_SHOW + + override fun create() = OpenTasksFragment() + + } + +} \ No newline at end of file diff --git a/app/src/main/java/at/bitfire/davdroid/ui/intro/WelcomeFragment.kt b/app/src/main/java/at/bitfire/davdroid/ui/intro/WelcomeFragment.kt new file mode 100644 index 000000000..d478a0f09 --- /dev/null +++ b/app/src/main/java/at/bitfire/davdroid/ui/intro/WelcomeFragment.kt @@ -0,0 +1,26 @@ +package at.bitfire.davdroid.ui.intro + +import android.content.Context +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.Fragment +import at.bitfire.davdroid.R +import at.bitfire.davdroid.settings.Settings + +class WelcomeFragment: Fragment() { + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View = + inflater.inflate(R.layout.intro_welcome, container, false) + + + class Factory : IIntroFragmentFactory { + + override fun shouldBeShown(context: Context, settings: Settings) = IIntroFragmentFactory.ShowMode.SHOW_NOT_ALONE + + override fun create() = WelcomeFragment() + + } + +} \ No newline at end of file diff --git a/app/src/main/java/at/bitfire/davdroid/ui/widget/CropImageView.kt b/app/src/main/java/at/bitfire/davdroid/ui/widget/CropImageView.kt new file mode 100644 index 000000000..dcbd2ccf6 --- /dev/null +++ b/app/src/main/java/at/bitfire/davdroid/ui/widget/CropImageView.kt @@ -0,0 +1,99 @@ +package at.bitfire.davdroid.ui.widget + +import android.content.Context +import android.graphics.Matrix +import android.graphics.RectF +import android.util.AttributeSet +import androidx.annotation.AttrRes +import androidx.appcompat.widget.AppCompatImageView +import at.bitfire.davdroid.R +import at.bitfire.davdroid.log.Logger.log + +/** + * [android.widget.ImageView] that supports directional cropping in both vertical and + * horizontal directions instead of being restricted to center-crop. Automatically sets [ ] to MATRIX and defaults to center-crop. + * + * @author Based on source code found on https://stackoverflow.com/a/26031741, by qix (CC BY-SA 4.0). + */ +class CropImageView @JvmOverloads constructor( + context: Context, + attrs: AttributeSet? = null, + @AttrRes defStyleAttr: Int = 0) + : AppCompatImageView(context, attrs, defStyleAttr) { + + companion object { + private const val DEFAULT_HORIZONTAL_OFFSET = 0.5f + private const val DEFAULT_VERTICAL_OFFSET = 0.5f + } + + private var mHorizontalOffsetPercent = DEFAULT_HORIZONTAL_OFFSET + private var mVerticalOffsetPercent = DEFAULT_VERTICAL_OFFSET + + init { + scaleType = ScaleType.MATRIX + + if (attrs != null) { + context.theme.obtainStyledAttributes(attrs, R.styleable.CropImageView, 0, 0).apply { + mHorizontalOffsetPercent = getFloat(R.styleable.CropImageView_horizontalOffsetPercent, DEFAULT_HORIZONTAL_OFFSET) + mVerticalOffsetPercent = getFloat(R.styleable.CropImageView_verticalOffsetPercent, DEFAULT_VERTICAL_OFFSET) + } + applyCropOffset() + } + } + + override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) { + super.onSizeChanged(w, h, oldw, oldh) + applyCropOffset() + } + + /** + * Sets the crop box offset by the specified percentage values. For example, a center-crop would + * be (0.5, 0.5), a top-left crop would be (0, 0), and a bottom-center crop would be (0.5, 1) + */ + fun setCropOffset(horizontalOffsetPercent: Float, verticalOffsetPercent: Float) { + require(!(mHorizontalOffsetPercent < 0 || mVerticalOffsetPercent < 0 || mHorizontalOffsetPercent > 1 || mVerticalOffsetPercent > 1)) { "Offset values must be a float between 0.0 and 1.0" } + mHorizontalOffsetPercent = horizontalOffsetPercent + mVerticalOffsetPercent = verticalOffsetPercent + applyCropOffset() + } + + private fun applyCropOffset() { + val matrix = imageMatrix + val scale: Float + val viewWidth = width - paddingLeft - paddingRight + val viewHeight = height - paddingTop - paddingBottom + var drawableWidth = 0 + var drawableHeight = 0 + // Allow for setting the drawable later in code by guarding ourselves here. + if (drawable != null) { + drawableWidth = drawable.intrinsicWidth + drawableHeight = drawable.intrinsicHeight + } + + // Get the scale. + scale = if (drawableWidth * viewHeight > drawableHeight * viewWidth) { + // Drawable is flatter than view. Scale it to fill the view height. + // A Top/Bottom crop here should be identical in this case. + viewHeight.toFloat() / drawableHeight.toFloat() + } else { + // Drawable is taller than view. Scale it to fill the view width. + // Left/Right crop here should be identical in this case. + viewWidth.toFloat() / drawableWidth.toFloat() + } + val viewToDrawableWidth = viewWidth / scale + val viewToDrawableHeight = viewHeight / scale + val xOffset = mHorizontalOffsetPercent * (drawableWidth - viewToDrawableWidth) + val yOffset = mVerticalOffsetPercent * (drawableHeight - viewToDrawableHeight) + + // Define the rect from which to take the image portion. + val drawableRect = RectF( + xOffset, + yOffset, + xOffset + viewToDrawableWidth, + yOffset + viewToDrawableHeight) + val viewRect = RectF(0f, 0f, viewWidth.toFloat(), viewHeight.toFloat()) + matrix.setRectToRect(drawableRect, viewRect, Matrix.ScaleToFit.FILL) + imageMatrix = matrix + } + +} \ No newline at end of file diff --git a/app/src/main/res/drawable/accounts_background.xml b/app/src/main/res/drawable/accounts_background.xml new file mode 100644 index 000000000..a6410f109 --- /dev/null +++ b/app/src/main/res/drawable/accounts_background.xml @@ -0,0 +1,6 @@ + + diff --git a/app/src/main/res/drawable/intro_logo_background.xml b/app/src/main/res/drawable/intro_logo_background.xml new file mode 100644 index 000000000..33c05e6ae --- /dev/null +++ b/app/src/main/res/drawable/intro_logo_background.xml @@ -0,0 +1,3 @@ + + diff --git a/app/src/main/res/drawable/intro_open_source.xml b/app/src/main/res/drawable/intro_open_source.xml new file mode 100644 index 000000000..1e33e65b1 --- /dev/null +++ b/app/src/main/res/drawable/intro_open_source.xml @@ -0,0 +1,6 @@ + + diff --git a/app/src/main/res/drawable/intro_tasks.xml b/app/src/main/res/drawable/intro_tasks.xml new file mode 100644 index 000000000..1e33e65b1 --- /dev/null +++ b/app/src/main/res/drawable/intro_tasks.xml @@ -0,0 +1,6 @@ + + diff --git a/app/src/main/res/layout-land/intro_welcome.xml b/app/src/main/res/layout-land/intro_welcome.xml new file mode 100644 index 000000000..a3b8d9f87 --- /dev/null +++ b/app/src/main/res/layout-land/intro_welcome.xml @@ -0,0 +1,71 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/about.xml b/app/src/main/res/layout/about.xml index 71c6a80ea..fdce371fe 100644 --- a/app/src/main/res/layout/about.xml +++ b/app/src/main/res/layout/about.xml @@ -12,7 +12,8 @@ android:layout_width="match_parent" android:layout_height="match_parent" xmlns:app="http://schemas.android.com/apk/res-auto" - android:gravity="center_horizontal"> + android:gravity="center_horizontal" + style="@style/Theme.MaterialComponents.Light"> - + android:layout_height="match_parent"> + + + + + + diff --git a/app/src/main/res/layout/intro_battery_optimizations.xml b/app/src/main/res/layout/intro_battery_optimizations.xml new file mode 100644 index 000000000..4f5f3e703 --- /dev/null +++ b/app/src/main/res/layout/intro_battery_optimizations.xml @@ -0,0 +1,158 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +