diff --git a/CHANGELOG.md b/CHANGELOG.md index 9a1108280..f584eb633 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,27 @@ Changelog ========== +Version 6.3.2 *(2019-03-07)* +---------------------------- + + * Added a "Go to date" to most views for easy jumping between dates + * Added an app shortcut for creating new events quickly, from Android 7.1+ + +Version 6.3.1 *(2019-02-23)* +---------------------------- + + * Allow adding event reminders to birthdays/anniversaries + * Filled content description of some views to improve the UX of visually impaired people + * A few more stability and UX improvements here and there + +Version 6.3.0 *(2019-02-14)* +---------------------------- + + * Allow setting default start time/duration/event type for new events + * Allow exporting/importing settings + * Fixed a glitch with repeating events older than from year 2001 + * Fixed some glitches related to overlapping events on the monthly and weekly view + Version 6.2.2 *(2019-01-25)* ---------------------------- diff --git a/README.md b/README.md index b1003e56a..decb2dc13 100644 --- a/README.md +++ b/README.md @@ -15,8 +15,8 @@ The Contacts permission is used only at importing contact birthdays and annivers This app is just one piece of a bigger series of apps. You can find the rest of them at https://www.simplemobiletools.com -Get it on Google Play -Get it on F-Droid +Get it on Google Play +Get it on F-Droid
App image diff --git a/app/build.gradle b/app/build.gradle index 467093211..58c7e298c 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -2,6 +2,7 @@ apply plugin: 'com.android.application' apply plugin: 'kotlin-android' apply plugin: 'kotlin-android-extensions' apply plugin: 'kotlin-kapt' +apply plugin: 'de.timfreiheit.resourceplaceholders' def keystorePropertiesFile = rootProject.file("keystore.properties") def keystoreProperties = new Properties() @@ -15,8 +16,8 @@ android { applicationId "com.simplemobiletools.calendar.pro" minSdkVersion 21 targetSdkVersion 28 - versionCode 142 - versionName "6.2.2" + versionCode 145 + versionName "6.3.2" multiDexEnabled true setProperty("archivesBaseName", "calendar") } @@ -49,12 +50,17 @@ android { checkReleaseBuilds false abortOnError false } + + resourcePlaceholders { + files = ['xml/shortcuts.xml'] + } } dependencies { - implementation 'com.simplemobiletools:commons:5.7.7' + implementation 'com.simplemobiletools:commons:5.10.10' implementation 'joda-time:joda-time:2.10.1' implementation 'androidx.multidex:multidex:2.0.1' + implementation 'androidx.constraintlayout:constraintlayout:2.0.0-alpha3' kapt 'androidx.room:room-compiler:2.0.0' implementation 'androidx.room:room-runtime:2.0.0' diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index d2c6f9b07..00a9a0f36 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -16,14 +16,18 @@ android:name="android.permission.USE_FINGERPRINT" tools:node="remove"/> + + + android:supportsRtl="true" + android:theme="@style/AppTheme"> () + private var mAttendeeViews = ArrayList() + private var mAvailableContacts = ArrayList() private lateinit var mEventStartDateTime: DateTime private lateinit var mEventEndDateTime: DateTime @@ -65,6 +87,7 @@ class EventActivity : SimpleActivity() { supportActionBar?.setHomeAsUpIndicator(R.drawable.ic_cross) val intent = intent ?: return mDialogTheme = getDialogTheme() + mWasContactsPermissionChecked = hasPermission(PERMISSION_READ_CONTACTS) val eventId = intent.getLongExtra(EVENT_ID, 0L) Thread { @@ -78,6 +101,7 @@ class EventActivity : SimpleActivity() { runOnUiThread { gotEvent(savedInstanceState, localEventType, event) } + fillAvailableContacts() }.start() } @@ -117,6 +141,7 @@ class EventActivity : SimpleActivity() { updateTexts() updateEventType() updateCalDAVCalendar() + updateAttendees() } event_show_on_map.setOnClickListener { showOnMap() } @@ -146,19 +171,41 @@ class EventActivity : SimpleActivity() { event_reminder_2.setOnClickListener { showReminder2Dialog() } event_reminder_3.setOnClickListener { showReminder3Dialog() } - event_type_holder.setOnClickListener { showEventTypeDialog() } + event_reminder_1_type.setOnClickListener { + showReminderTypePicker(mReminder1Type) { + mReminder1Type = it + updateReminderTypeImage(event_reminder_1_type, Reminder(mReminder1Minutes, mReminder1Type)) + } + } - if (mEvent.flags and FLAG_ALL_DAY != 0) - event_all_day.toggle() + event_reminder_2_type.setOnClickListener { + showReminderTypePicker(mReminder2Type) { + mReminder2Type = it + updateReminderTypeImage(event_reminder_2_type, Reminder(mReminder2Minutes, mReminder2Type)) + } + } + + event_reminder_3_type.setOnClickListener { + showReminderTypePicker(mReminder3Type) { + mReminder3Type = it + updateReminderTypeImage(event_reminder_3_type, Reminder(mReminder3Minutes, mReminder3Type)) + } + } + + event_type_holder.setOnClickListener { showEventTypeDialog() } + event_all_day.apply { + isChecked = mEvent.flags and FLAG_ALL_DAY != 0 + jumpDrawablesToCurrentState() + } updateTextColors(event_scrollview) updateIconColors() - wasActivityInitialized = true + mWasActivityInitialized = true } override fun onCreateOptionsMenu(menu: Menu): Boolean { menuInflater.inflate(R.menu.menu_event, menu) - if (wasActivityInitialized) { + if (mWasActivityInitialized) { menu.findItem(R.id.delete).isVisible = mEvent.id != null menu.findItem(R.id.share).isVisible = mEvent.id != null menu.findItem(R.id.duplicate).isVisible = mEvent.id != null @@ -179,7 +226,7 @@ class EventActivity : SimpleActivity() { override fun onSaveInstanceState(outState: Bundle) { super.onSaveInstanceState(outState) - if (!wasActivityInitialized) { + if (!mWasActivityInitialized) { return } @@ -192,10 +239,16 @@ class EventActivity : SimpleActivity() { putInt(REMINDER_2_MINUTES, mReminder2Minutes) putInt(REMINDER_3_MINUTES, mReminder3Minutes) + putInt(REMINDER_1_TYPE, mReminder1Type) + putInt(REMINDER_2_TYPE, mReminder2Type) + putInt(REMINDER_3_TYPE, mReminder3Type) + putInt(REPEAT_INTERVAL, mRepeatInterval) putInt(REPEAT_RULE, mRepeatRule) putLong(REPEAT_LIMIT, mRepeatLimit) + putString(ATTENDEES, getAllAttendees()) + putLong(EVENT_TYPE_ID, mEventTypeId) putInt(EVENT_CALENDAR_ID, mEventCalendarId) } @@ -217,10 +270,17 @@ class EventActivity : SimpleActivity() { mReminder2Minutes = getInt(REMINDER_2_MINUTES) mReminder3Minutes = getInt(REMINDER_3_MINUTES) + mReminder1Type = getInt(REMINDER_1_TYPE) + mReminder2Type = getInt(REMINDER_2_TYPE) + mReminder3Type = getInt(REMINDER_3_TYPE) + mRepeatInterval = getInt(REPEAT_INTERVAL) mRepeatRule = getInt(REPEAT_RULE) mRepeatLimit = getLong(REPEAT_LIMIT) + mAttendees = Gson().fromJson>(getString(ATTENDEES), object : TypeToken>() {}.type) + ?: ArrayList() + mEventTypeId = getLong(EVENT_TYPE_ID) mEventCalendarId = getInt(EVENT_CALENDAR_ID) } @@ -230,6 +290,7 @@ class EventActivity : SimpleActivity() { updateTexts() updateEventType() updateCalDAVCalendar() + updateAttendees() } private fun updateTexts() { @@ -237,6 +298,7 @@ class EventActivity : SimpleActivity() { checkReminderTexts() updateStartTexts() updateEndTexts() + updateAttendeesVisibility() } private fun setupEditEvent() { @@ -253,11 +315,15 @@ class EventActivity : SimpleActivity() { mReminder1Minutes = mEvent.reminder1Minutes mReminder2Minutes = mEvent.reminder2Minutes mReminder3Minutes = mEvent.reminder3Minutes + mReminder1Type = mEvent.reminder1Type + mReminder2Type = mEvent.reminder2Type + mReminder3Type = mEvent.reminder3Type mRepeatInterval = mEvent.repeatInterval mRepeatLimit = mEvent.repeatLimit mRepeatRule = mEvent.repeatRule mEventTypeId = mEvent.eventType mEventCalendarId = mEvent.getCalDAVCalendarId() + mAttendees = Gson().fromJson>(mEvent.attendees, object : TypeToken>() {}.type) ?: ArrayList() checkRepeatTexts(mRepeatInterval) } @@ -558,6 +624,7 @@ class EventActivity : SimpleActivity() { updateReminder1Text() updateReminder2Text() updateReminder3Text() + updateReminderTypeImages() } private fun updateReminder1Text() { @@ -590,6 +657,36 @@ class EventActivity : SimpleActivity() { } } + private fun showReminderTypePicker(currentValue: Int, callback: (Int) -> Unit) { + val items = arrayListOf( + RadioItem(REMINDER_NOTIFICATION, getString(R.string.notification)), + RadioItem(REMINDER_EMAIL, getString(R.string.email)) + ) + RadioGroupDialog(this, items, currentValue) { + callback(it as Int) + } + } + + private fun updateReminderTypeImages() { + updateReminderTypeImage(event_reminder_1_type, Reminder(mReminder1Minutes, mReminder1Type)) + updateReminderTypeImage(event_reminder_2_type, Reminder(mReminder2Minutes, mReminder2Type)) + updateReminderTypeImage(event_reminder_3_type, Reminder(mReminder3Minutes, mReminder3Type)) + } + + private fun updateAttendeesVisibility() { + val isSyncedEvent = mEventCalendarId != STORED_LOCALLY_ONLY + event_attendees_image.beVisibleIf(isSyncedEvent) + event_attendees_holder.beVisibleIf(isSyncedEvent) + event_attendees_divider.beVisibleIf(isSyncedEvent) + } + + private fun updateReminderTypeImage(view: ImageView, reminder: Reminder) { + view.beVisibleIf(reminder.minutes != REMINDER_OFF && mEventCalendarId != STORED_LOCALLY_ONLY) + val drawable = if (reminder.type == REMINDER_NOTIFICATION) R.drawable.ic_bell else R.drawable.ic_email + val icon = resources.getColoredDrawableWithColor(drawable, config.textColor) + view.setImageDrawable(icon) + } + private fun updateRepetitionText() { event_repetition.text = getRepetitionText(mRepeatInterval) } @@ -627,6 +724,8 @@ class EventActivity : SimpleActivity() { mEventCalendarId = it config.lastUsedCaldavCalendarId = it updateCurrentCalendarInfo(getCalendarWithId(calendars, it)) + updateReminderTypeImages() + updateAttendeesVisibility() } } } else { @@ -765,16 +864,26 @@ class EventActivity : SimpleActivity() { "$CALDAV-$mEventCalendarId" } - val reminders = sortedSetOf(mReminder1Minutes, mReminder2Minutes, mReminder3Minutes).filter { it != REMINDER_OFF } - val reminder1 = reminders.getOrElse(0) { REMINDER_OFF } - val reminder2 = reminders.getOrElse(1) { REMINDER_OFF } - val reminder3 = reminders.getOrElse(2) { REMINDER_OFF } + var reminders = arrayListOf( + Reminder(mReminder1Minutes, mReminder1Type), + Reminder(mReminder2Minutes, mReminder2Type), + Reminder(mReminder3Minutes, mReminder3Type) + ) + reminders = reminders.filter { it.minutes != REMINDER_OFF }.sortedBy { it.minutes }.toMutableList() as ArrayList + + val reminder1 = reminders.getOrNull(0) ?: Reminder(REMINDER_OFF, REMINDER_NOTIFICATION) + val reminder2 = reminders.getOrNull(1) ?: Reminder(REMINDER_OFF, REMINDER_NOTIFICATION) + val reminder3 = reminders.getOrNull(2) ?: Reminder(REMINDER_OFF, REMINDER_NOTIFICATION) + + mReminder1Type = if (mEventCalendarId == STORED_LOCALLY_ONLY) REMINDER_NOTIFICATION else reminder1.type + mReminder2Type = if (mEventCalendarId == STORED_LOCALLY_ONLY) REMINDER_NOTIFICATION else reminder2.type + mReminder3Type = if (mEventCalendarId == STORED_LOCALLY_ONLY) REMINDER_NOTIFICATION else reminder3.type config.apply { if (usePreviousEventReminders) { - lastEventReminderMinutes1 = reminder1 - lastEventReminderMinutes2 = reminder2 - lastEventReminderMinutes3 = reminder3 + lastEventReminderMinutes1 = reminder1.minutes + lastEventReminderMinutes2 = reminder2.minutes + lastEventReminderMinutes3 = reminder3.minutes } } @@ -783,14 +892,18 @@ class EventActivity : SimpleActivity() { endTS = newEndTS title = newTitle description = event_description.value - reminder1Minutes = reminder1 - reminder2Minutes = reminder2 - reminder3Minutes = reminder3 + reminder1Minutes = reminder1.minutes + reminder2Minutes = reminder2.minutes + reminder3Minutes = reminder3.minutes + reminder1Type = mReminder1Type + reminder2Type = mReminder2Type + reminder3Type = mReminder3Type repeatInterval = mRepeatInterval importId = newImportId flags = mEvent.flags.addBitIf(event_all_day.isChecked, FLAG_ALL_DAY) repeatLimit = if (repeatInterval == 0) 0 else mRepeatLimit repeatRule = mRepeatRule + attendees = if (mEventCalendarId == STORED_LOCALLY_ONLY) "" else getAllAttendees() eventType = newEventType lastUpdated = System.currentTimeMillis() source = newSource @@ -810,7 +923,7 @@ class EventActivity : SimpleActivity() { if (mEvent.id == null || mEvent.id == null) { eventsHelper.insertEvent(mEvent, true, true) { if (DateTime.now().isAfter(mEventStartDateTime.millis)) { - if (mEvent.repeatInterval == 0 && mEvent.getReminders().isNotEmpty()) { + if (mEvent.repeatInterval == 0 && mEvent.getReminders().any { it.type == REMINDER_NOTIFICATION }) { notifyEvent(mEvent) } } @@ -1006,6 +1119,154 @@ class EventActivity : SimpleActivity() { } } + private fun fillAvailableContacts() { + mAvailableContacts = getEmails() + + val names = getNames() + mAvailableContacts.forEach { + val contactId = it.contactId + val contact = names.firstOrNull { it.contactId == contactId } + val name = contact?.name + if (name != null) { + it.name = name + } + + val photoUri = contact?.photoUri + if (photoUri != null) { + it.photoUri = photoUri + } + } + } + + private fun updateAttendees() { + mAttendees.forEach { + addAttendee(it.getPublicName()) + } + addAttendee() + + val imageHeight = event_repetition_image.height + if (imageHeight > 0) { + event_attendees_image.layoutParams.height = imageHeight + } else { + event_repetition_image.onGlobalLayout { + event_attendees_image.layoutParams.height = event_repetition_image.height + } + } + } + + private fun addAttendee(value: String? = null) { + val attendeeHolder = layoutInflater.inflate(R.layout.item_attendee, event_attendees_holder, false) as RelativeLayout + mAttendeeViews.add(attendeeHolder.event_attendee) + attendeeHolder.event_attendee.onTextChangeListener { + if (mWasContactsPermissionChecked && value == null) { + checkNewAttendeeField(value) + } else { + handlePermission(PERMISSION_READ_CONTACTS) { + checkNewAttendeeField(value) + mWasContactsPermissionChecked = true + } + } + } + + event_attendees_holder.addView(attendeeHolder) + attendeeHolder.event_attendee.setColors(config.textColor, getAdjustedPrimaryColor(), config.backgroundColor) + + if (value != null) { + attendeeHolder.event_attendee.setText(value) + } + + val adapter = AutoCompleteTextViewAdapter(this, mAvailableContacts) + attendeeHolder.event_attendee.setAdapter(adapter) + attendeeHolder.event_attendee.setOnItemClickListener { parent, view, position, id -> + val currAttendees = (attendeeHolder.event_attendee.adapter as AutoCompleteTextViewAdapter).resultList + val selectedAttendee = currAttendees[position] + } + } + + private fun checkNewAttendeeField(value: String?) { + if (value == null && mAttendeeViews.none { it.value.isEmpty() }) { + addAttendee() + } + } + + private fun getAllAttendees(): String { + val attendeeEmails = mAttendeeViews.map { it.value }.filter { it.isNotEmpty() }.toMutableList() as ArrayList + val attendees = ArrayList() + attendeeEmails.mapTo(attendees) { + Attendee(0, "", it, CalendarContract.Attendees.ATTENDEE_STATUS_INVITED, "") + } + return Gson().toJson(attendees) + } + + private fun getNames(): List { + val contacts = ArrayList() + val uri = ContactsContract.Data.CONTENT_URI + val projection = arrayOf( + ContactsContract.Data.CONTACT_ID, + ContactsContract.CommonDataKinds.StructuredName.PREFIX, + ContactsContract.CommonDataKinds.StructuredName.GIVEN_NAME, + ContactsContract.CommonDataKinds.StructuredName.MIDDLE_NAME, + ContactsContract.CommonDataKinds.StructuredName.FAMILY_NAME, + ContactsContract.CommonDataKinds.StructuredName.SUFFIX, + ContactsContract.CommonDataKinds.StructuredName.PHOTO_THUMBNAIL_URI) + + val selection = "${ContactsContract.Data.MIMETYPE} = ?" + val selectionArgs = arrayOf(ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE) + + var cursor: Cursor? = null + try { + cursor = contentResolver.query(uri, projection, selection, selectionArgs, null) + if (cursor?.moveToFirst() == true) { + do { + val id = cursor.getIntValue(ContactsContract.Data.CONTACT_ID) + val prefix = cursor.getStringValue(ContactsContract.CommonDataKinds.StructuredName.PREFIX) ?: "" + val firstName = cursor.getStringValue(ContactsContract.CommonDataKinds.StructuredName.GIVEN_NAME) ?: "" + val middleName = cursor.getStringValue(ContactsContract.CommonDataKinds.StructuredName.MIDDLE_NAME) ?: "" + val surname = cursor.getStringValue(ContactsContract.CommonDataKinds.StructuredName.FAMILY_NAME) ?: "" + val suffix = cursor.getStringValue(ContactsContract.CommonDataKinds.StructuredName.SUFFIX) ?: "" + val photoUri = cursor.getStringValue(ContactsContract.CommonDataKinds.StructuredName.PHOTO_THUMBNAIL_URI) ?: "" + + val names = arrayListOf(prefix, firstName, middleName, surname, suffix).filter { it.trim().isNotEmpty() } + val fullName = TextUtils.join("", names) + if (fullName.isNotEmpty() || photoUri.isNotEmpty()) { + val contact = Attendee(id, fullName, "", 0, photoUri) + contacts.add(contact) + } + } while (cursor.moveToNext()) + } + } catch (ignored: Exception) { + } finally { + cursor?.close() + } + return contacts + } + + private fun getEmails(): ArrayList { + val contacts = ArrayList() + val uri = ContactsContract.CommonDataKinds.Email.CONTENT_URI + val projection = arrayOf( + ContactsContract.Data.CONTACT_ID, + ContactsContract.CommonDataKinds.Email.DATA + ) + + var cursor: Cursor? = null + try { + cursor = contentResolver.query(uri, projection, null, null, null) + if (cursor?.moveToFirst() == true) { + do { + val id = cursor.getIntValue(ContactsContract.Data.CONTACT_ID) + val email = cursor.getStringValue(ContactsContract.CommonDataKinds.Email.DATA) ?: continue + val contact = Attendee(id, "", email, 0, "") + contacts.add(contact) + } while (cursor.moveToNext()) + } + } catch (ignored: Exception) { + } finally { + cursor?.close() + } + return contacts + } + private fun updateIconColors() { val textColor = config.textColor event_time_image.applyColorFilter(textColor) @@ -1014,5 +1275,9 @@ class EventActivity : SimpleActivity() { event_type_image.applyColorFilter(textColor) event_caldav_calendar_image.applyColorFilter(textColor) event_show_on_map.applyColorFilter(getAdjustedPrimaryColor()) + event_reminder_1_type.applyColorFilter(textColor) + event_reminder_2_type.applyColorFilter(textColor) + event_reminder_3_type.applyColorFilter(textColor) + event_attendees_image.applyColorFilter(textColor) } } diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/activities/MainActivity.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/activities/MainActivity.kt index e48a0e349..489e81dcf 100644 --- a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/activities/MainActivity.kt +++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/activities/MainActivity.kt @@ -1,11 +1,16 @@ package com.simplemobiletools.calendar.pro.activities +import android.annotation.SuppressLint import android.app.SearchManager import android.content.Context import android.content.Intent import android.content.pm.ActivityInfo +import android.content.pm.ShortcutInfo +import android.content.pm.ShortcutManager import android.database.Cursor import android.graphics.drawable.ColorDrawable +import android.graphics.drawable.Icon +import android.graphics.drawable.LayerDrawable import android.net.Uri import android.os.Bundle import android.provider.ContactsContract @@ -21,6 +26,7 @@ import com.simplemobiletools.calendar.pro.databases.EventsDatabase import com.simplemobiletools.calendar.pro.dialogs.ExportEventsDialog import com.simplemobiletools.calendar.pro.dialogs.FilterEventTypesDialog import com.simplemobiletools.calendar.pro.dialogs.ImportEventsDialog +import com.simplemobiletools.calendar.pro.dialogs.SetRemindersDialog import com.simplemobiletools.calendar.pro.extensions.* import com.simplemobiletools.calendar.pro.fragments.* import com.simplemobiletools.calendar.pro.helpers.* @@ -129,6 +135,7 @@ class MainActivity : SimpleActivity(), RefreshRecyclerViewListener { calendar_fab.setColors(config.textColor, getAdjustedPrimaryColor(), config.backgroundColor) search_holder.background = ColorDrawable(config.backgroundColor) checkSwipeRefreshAvailability() + checkShortcuts() } override fun onPause() { @@ -154,6 +161,7 @@ class MainActivity : SimpleActivity(), RefreshRecyclerViewListener { goToTodayButton = findItem(R.id.go_to_today) findItem(R.id.filter).isVisible = mShouldFilterBeVisible findItem(R.id.go_to_today).isVisible = shouldGoToTodayBeVisible && config.storedView != EVENTS_LIST_VIEW + findItem(R.id.go_to_date).isVisible = config.storedView != EVENTS_LIST_VIEW } setupSearch(menu) @@ -172,6 +180,7 @@ class MainActivity : SimpleActivity(), RefreshRecyclerViewListener { when (item.itemId) { R.id.change_view -> showViewDialog() R.id.go_to_today -> goToToday() + R.id.go_to_date -> showGoToDateDialog() R.id.filter -> showFilterDialog() R.id.refresh_caldav_calendars -> refreshCalDAVCalendars(true) R.id.add_holidays -> addHolidays() @@ -256,6 +265,33 @@ class MainActivity : SimpleActivity(), RefreshRecyclerViewListener { mSearchMenuItem?.collapseActionView() } + @SuppressLint("NewApi") + private fun checkShortcuts() { + val appIconColor = config.appIconColor + if (isNougatMR1Plus() && config.lastHandledShortcutColor != appIconColor) { + val newEvent = getString(R.string.new_event) + val manager = getSystemService(ShortcutManager::class.java) + val drawable = resources.getDrawable(R.drawable.shortcut_plus) + (drawable as LayerDrawable).findDrawableByLayerId(R.id.shortcut_plus_background).applyColorFilter(appIconColor) + val bmp = drawable.convertToBitmap() + + val intent = Intent(this, SplashActivity::class.java) + intent.action = SHORTCUT_NEW_EVENT + val shortcut = ShortcutInfo.Builder(this, "new_event") + .setShortLabel(newEvent) + .setLongLabel(newEvent) + .setIcon(Icon.createWithBitmap(bmp)) + .setIntent(intent) + .build() + + try { + manager.dynamicShortcuts = Arrays.asList(shortcut) + config.lastHandledShortcutColor = appIconColor + } catch (ignored: Exception) { + } + } + } + private fun checkIsOpenIntent(): Boolean { val dayCodeToOpen = intent.getStringExtra(DAY_CODE) ?: "" val viewToOpen = intent.getIntExtra(VIEW_TO_OPEN, DAILY_VIEW) @@ -339,6 +375,10 @@ class MainActivity : SimpleActivity(), RefreshRecyclerViewListener { currentFragments.last().goToToday() } + fun showGoToDateDialog() { + currentFragments.last().showGoToDateDialog() + } + private fun resetActionBarTitle() { updateActionBarTitle(getString(R.string.app_launcher_name)) updateActionBarSubtitle("") @@ -407,16 +447,19 @@ class MainActivity : SimpleActivity(), RefreshRecyclerViewListener { private fun tryAddBirthdays() { handlePermission(PERMISSION_READ_CONTACTS) { if (it) { - Thread { - addContactEvents(true) { - if (it > 0) { - toast(R.string.birthdays_added) - updateViewPager() - } else { - toast(R.string.no_birthdays) + SetRemindersDialog(this) { + val reminders = it + Thread { + addContactEvents(true, reminders) { + if (it > 0) { + toast(R.string.birthdays_added) + updateViewPager() + } else { + toast(R.string.no_birthdays) + } } - } - }.start() + }.start() + } } else { toast(R.string.no_contacts_permission) } @@ -426,16 +469,19 @@ class MainActivity : SimpleActivity(), RefreshRecyclerViewListener { private fun tryAddAnniversaries() { handlePermission(PERMISSION_READ_CONTACTS) { if (it) { - Thread { - addContactEvents(false) { - if (it > 0) { - toast(R.string.anniversaries_added) - updateViewPager() - } else { - toast(R.string.no_anniversaries) + SetRemindersDialog(this) { + val reminders = it + Thread { + addContactEvents(false, reminders) { + if (it > 0) { + toast(R.string.anniversaries_added) + updateViewPager() + } else { + toast(R.string.no_anniversaries) + } } - } - }.start() + }.start() + } } else { toast(R.string.no_contacts_permission) } @@ -450,7 +496,7 @@ class MainActivity : SimpleActivity(), RefreshRecyclerViewListener { }, Toast.LENGTH_LONG) } - private fun addContactEvents(birthdays: Boolean, callback: (Int) -> Unit) { + private fun addContactEvents(birthdays: Boolean, reminders: ArrayList, callback: (Int) -> Unit) { var eventsAdded = 0 val uri = ContactsContract.Data.CONTENT_URI val projection = arrayOf(ContactsContract.Contacts.DISPLAY_NAME, @@ -486,7 +532,8 @@ class MainActivity : SimpleActivity(), RefreshRecyclerViewListener { val timestamp = date.time / 1000L val source = if (birthdays) SOURCE_CONTACT_BIRTHDAY else SOURCE_CONTACT_ANNIVERSARY val lastUpdated = cursor.getLongValue(ContactsContract.CommonDataKinds.Event.CONTACT_LAST_UPDATED_TIMESTAMP) - val event = Event(null, timestamp, timestamp, name, importId = contactId, flags = FLAG_ALL_DAY, repeatInterval = YEAR, + val event = Event(null, timestamp, timestamp, name, reminder1Minutes = reminders[0], reminder2Minutes = reminders[1], + reminder3Minutes = reminders[2], importId = contactId, flags = FLAG_ALL_DAY, repeatInterval = YEAR, repeatRule = REPEAT_SAME_DAY, eventType = eventTypeId, source = source, lastUpdated = lastUpdated) if (!importIDs.contains(contactId)) { @@ -861,6 +908,7 @@ class MainActivity : SimpleActivity(), RefreshRecyclerViewListener { add(Release(117, R.string.release_117)) add(Release(119, R.string.release_119)) add(Release(129, R.string.release_129)) + add(Release(143, R.string.release_143)) checkWhatsNew(this, BuildConfig.VERSION_CODE) } } diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/activities/SettingsActivity.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/activities/SettingsActivity.kt index e56ab5ad1..32b8e1ebe 100644 --- a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/activities/SettingsActivity.kt +++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/activities/SettingsActivity.kt @@ -315,7 +315,6 @@ class SettingsActivity : SimpleActivity() { } } - private fun setupWeekNumbers() { settings_week_numbers.isChecked = config.showWeekNumbers settings_week_numbers_holder.setOnClickListener { @@ -692,14 +691,18 @@ class SettingsActivity : SimpleActivity() { private fun setupImportSettings() { settings_import_holder.setOnClickListener { - FilePickerDialog(this) { - Thread { - try { - parseFile(it) - } catch (e: Exception) { - showErrorToast(e) + handlePermission(PERMISSION_READ_STORAGE) { + if (it) { + FilePickerDialog(this) { + Thread { + try { + parseFile(it) + } catch (e: Exception) { + showErrorToast(e) + } + }.start() } - }.start() + } } } } diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/activities/SplashActivity.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/activities/SplashActivity.kt index c594973a9..506520e21 100644 --- a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/activities/SplashActivity.kt +++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/activities/SplashActivity.kt @@ -1,8 +1,10 @@ package com.simplemobiletools.calendar.pro.activities import android.content.Intent +import com.simplemobiletools.calendar.pro.extensions.getNewEventTimestampFromCode import com.simplemobiletools.calendar.pro.helpers.* import com.simplemobiletools.commons.activities.BaseSplashActivity +import org.joda.time.DateTime class SplashActivity : BaseSplashActivity() { override fun getAppPackageName() = packageName @@ -19,6 +21,13 @@ class SplashActivity : BaseSplashActivity() { putExtra(EVENT_OCCURRENCE_TS, intent.getLongExtra(EVENT_OCCURRENCE_TS, 0L)) startActivity(this) } + intent.action == SHORTCUT_NEW_EVENT -> { + val dayCode = Formatter.getDayCodeFromDateTime(DateTime()) + Intent(this, EventActivity::class.java).apply { + putExtra(NEW_EVENT_START_TS, getNewEventTimestampFromCode(dayCode)) + startActivity(this) + } + } else -> startActivity(Intent(this, MainActivity::class.java)) } finish() diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/activities/WidgetListConfigureActivity.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/activities/WidgetListConfigureActivity.kt index 6f8e97f6a..73a605e30 100644 --- a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/activities/WidgetListConfigureActivity.kt +++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/activities/WidgetListConfigureActivity.kt @@ -68,12 +68,7 @@ class WidgetListConfigureActivity : SimpleActivity() { updateColors() mBgColor = config.widgetBgColor - if (mBgColor == 1) { - mBgColor = Color.BLACK - mBgAlpha = .2f - } else { - mBgAlpha = Color.alpha(mBgColor) / 255.toFloat() - } + mBgAlpha = Color.alpha(mBgColor) / 255.toFloat() mBgColorWithoutTransparency = Color.rgb(Color.red(mBgColor), Color.green(mBgColor), Color.blue(mBgColor)) config_bg_seekbar.setOnSeekBarChangeListener(bgSeekbarChangeListener) diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/activities/WidgetMonthlyConfigureActivity.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/activities/WidgetMonthlyConfigureActivity.kt index df3d1e1fd..dc159f658 100644 --- a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/activities/WidgetMonthlyConfigureActivity.kt +++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/activities/WidgetMonthlyConfigureActivity.kt @@ -77,12 +77,7 @@ class WidgetMonthlyConfigureActivity : SimpleActivity(), MonthlyCalendar { updateColors() mBgColor = config.widgetBgColor - if (mBgColor == 1) { - mBgColor = Color.BLACK - mBgAlpha = .2f - } else { - mBgAlpha = Color.alpha(mBgColor) / 255.toFloat() - } + mBgAlpha = Color.alpha(mBgColor) / 255.toFloat() mBgColorWithoutTransparency = Color.rgb(Color.red(mBgColor), Color.green(mBgColor), Color.blue(mBgColor)) config_bg_seekbar.setOnSeekBarChangeListener(bgSeekbarChangeListener) diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/adapters/AutoCompleteTextViewAdapter.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/adapters/AutoCompleteTextViewAdapter.kt new file mode 100644 index 000000000..34ef234dc --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/adapters/AutoCompleteTextViewAdapter.kt @@ -0,0 +1,101 @@ +package com.simplemobiletools.calendar.pro.adapters + +import android.graphics.drawable.LayerDrawable +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.ArrayAdapter +import android.widget.Filter +import com.bumptech.glide.Glide +import com.bumptech.glide.load.engine.DiskCacheStrategy +import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions +import com.bumptech.glide.request.RequestOptions +import com.simplemobiletools.calendar.pro.R +import com.simplemobiletools.calendar.pro.activities.SimpleActivity +import com.simplemobiletools.calendar.pro.extensions.config +import com.simplemobiletools.calendar.pro.models.Attendee +import com.simplemobiletools.commons.extensions.applyColorFilter +import com.simplemobiletools.commons.extensions.normalizeString +import kotlinx.android.synthetic.main.item_autocomplete_email_name.view.* + +class AutoCompleteTextViewAdapter(val activity: SimpleActivity, val contacts: ArrayList) : ArrayAdapter(activity, 0, contacts) { + var resultList = ArrayList() + private var placeholder = activity.resources.getDrawable(R.drawable.attendee_circular_background) + + init { + (placeholder as LayerDrawable).findDrawableByLayerId(R.id.attendee_circular_background).applyColorFilter(activity.config.primaryColor) + } + + override fun getView(position: Int, convertView: View?, parent: ViewGroup): View { + val contact = resultList[position] + var listItem = convertView + if (listItem == null || listItem.tag != contact.name.isNotEmpty()) { + val layout = if (contact.name.isNotEmpty()) R.layout.item_autocomplete_email_name else R.layout.item_autocomplete_email + listItem = LayoutInflater.from(activity).inflate(layout, parent, false) + } + + listItem!!.apply { + tag = contact.name.isNotEmpty() + item_autocomplete_name?.text = contact.name + item_autocomplete_email?.text = contact.email + + if (contact.photoUri.isEmpty()) { + item_autocomplete_image.setImageDrawable(placeholder) + } else { + val options = RequestOptions() + .diskCacheStrategy(DiskCacheStrategy.RESOURCE) + .error(placeholder) + .centerCrop() + + Glide.with(activity) + .load(contact.photoUri) + .transition(DrawableTransitionOptions.withCrossFade()) + .apply(options) + .apply(RequestOptions.circleCropTransform()) + .into(item_autocomplete_image) + } + } + + return listItem + } + + override fun getFilter() = object : Filter() { + override fun performFiltering(constraint: CharSequence?): FilterResults { + val filterResults = Filter.FilterResults() + if (constraint != null) { + resultList.clear() + val searchString = constraint.toString().normalizeString() + contacts.forEach { + if (it.email.contains(searchString, true) || it.name.contains(searchString, true)) { + resultList.add(it) + } + } + + resultList.sortWith(compareBy + { it.name.startsWith(searchString, true) }.thenBy + { it.email.startsWith(searchString, true) }.thenBy + { it.name.contains(searchString, true) }.thenBy + { it.email.contains(searchString, true) }) + resultList.reverse() + + filterResults.values = resultList + filterResults.count = resultList.size + } + return filterResults + } + + override fun publishResults(constraint: CharSequence?, results: FilterResults?) { + if (results?.count ?: -1 > 0) { + notifyDataSetChanged() + } else { + notifyDataSetInvalidated() + } + } + + override fun convertResultToString(resultValue: Any?) = (resultValue as? Attendee)?.getPublicName() + } + + override fun getItem(index: Int) = resultList[index] + + override fun getCount() = resultList.size +} diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/databases/EventsDatabase.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/databases/EventsDatabase.kt index 36679424c..19440190c 100644 --- a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/databases/EventsDatabase.kt +++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/databases/EventsDatabase.kt @@ -5,6 +5,7 @@ import androidx.room.Database import androidx.room.Room import androidx.room.RoomDatabase import androidx.room.TypeConverters +import androidx.room.migration.Migration import androidx.sqlite.db.SupportSQLiteDatabase import com.simplemobiletools.calendar.pro.R import com.simplemobiletools.calendar.pro.extensions.config @@ -16,7 +17,7 @@ import com.simplemobiletools.calendar.pro.models.Event import com.simplemobiletools.calendar.pro.models.EventType import java.util.concurrent.Executors -@Database(entities = [Event::class, EventType::class], version = 1) +@Database(entities = [Event::class, EventType::class], version = 2) @TypeConverters(Converters::class) abstract class EventsDatabase : RoomDatabase() { @@ -38,6 +39,7 @@ abstract class EventsDatabase : RoomDatabase() { insertRegularEventType(context) } }) + .addMigrations(MIGRATION_1_2) .build() db!!.openHelper.setWriteAheadLoggingEnabled(true) } @@ -58,5 +60,16 @@ abstract class EventsDatabase : RoomDatabase() { context.config.addDisplayEventType(REGULAR_EVENT_TYPE_ID.toString()) } } + + private val MIGRATION_1_2 = object : Migration(1, 2) { + override fun migrate(database: SupportSQLiteDatabase) { + database.apply { + execSQL("ALTER TABLE events ADD COLUMN reminder_1_type INTEGER NOT NULL DEFAULT 0") + execSQL("ALTER TABLE events ADD COLUMN reminder_2_type INTEGER NOT NULL DEFAULT 0") + execSQL("ALTER TABLE events ADD COLUMN reminder_3_type INTEGER NOT NULL DEFAULT 0") + execSQL("ALTER TABLE events ADD COLUMN attendees TEXT NOT NULL DEFAULT ''") + } + } + } } } diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/dialogs/SetRemindersDialog.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/dialogs/SetRemindersDialog.kt new file mode 100644 index 000000000..bdf0d2805 --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/dialogs/SetRemindersDialog.kt @@ -0,0 +1,63 @@ +package com.simplemobiletools.calendar.pro.dialogs + +import android.app.Activity +import androidx.appcompat.app.AlertDialog +import com.simplemobiletools.calendar.pro.R +import com.simplemobiletools.calendar.pro.extensions.config +import com.simplemobiletools.commons.extensions.* +import kotlinx.android.synthetic.main.dialog_set_reminders.view.* + +class SetRemindersDialog(val activity: Activity, val callback: (reminders: ArrayList) -> Unit) { + private var mReminder1Minutes = -1 + private var mReminder2Minutes = -1 + private var mReminder3Minutes = -1 + + init { + val view = activity.layoutInflater.inflate(R.layout.dialog_set_reminders, null).apply { + set_reminders_image.applyColorFilter(context.config.textColor) + set_reminders_1.text = activity.getFormattedMinutes(mReminder1Minutes) + set_reminders_2.text = activity.getFormattedMinutes(mReminder1Minutes) + set_reminders_3.text = activity.getFormattedMinutes(mReminder1Minutes) + + set_reminders_1.setOnClickListener { + activity.showPickSecondsDialogHelper(mReminder1Minutes) { + mReminder1Minutes = if (it <= 0) it else it / 60 + set_reminders_1.text = activity.getFormattedMinutes(mReminder1Minutes) + if (mReminder1Minutes != -1) { + set_reminders_2.beVisible() + } + } + } + + set_reminders_2.setOnClickListener { + activity.showPickSecondsDialogHelper(mReminder2Minutes) { + mReminder2Minutes = if (it <= 0) it else it / 60 + set_reminders_2.text = activity.getFormattedMinutes(mReminder2Minutes) + if (mReminder2Minutes != -1) { + set_reminders_3.beVisible() + } + } + } + + set_reminders_3.setOnClickListener { + activity.showPickSecondsDialogHelper(mReminder3Minutes) { + mReminder3Minutes = if (it <= 0) it else it / 60 + set_reminders_3.text = activity.getFormattedMinutes(mReminder3Minutes) + } + } + } + + AlertDialog.Builder(activity) + .setPositiveButton(R.string.ok) { dialog, which -> dialogConfirmed() } + .setNegativeButton(R.string.cancel, null) + .create().apply { + activity.setupDialogStuff(view, this, R.string.event_reminders) + } + } + + private fun dialogConfirmed() { + val reminders = arrayListOf(mReminder1Minutes, mReminder2Minutes, mReminder3Minutes) + reminders.sort() + callback(reminders) + } +} diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/extensions/Context.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/extensions/Context.kt index a722cb91d..3813e37bc 100644 --- a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/extensions/Context.kt +++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/extensions/Context.kt @@ -80,7 +80,8 @@ fun Context.scheduleAllEvents() { } fun Context.scheduleNextEventReminder(event: Event, showToasts: Boolean) { - if (event.getReminders().isEmpty()) { + val validReminders = event.getReminders().filter { it.type == REMINDER_NOTIFICATION } + if (validReminders.isEmpty()) { if (showToasts) { toast(R.string.saving) } @@ -88,7 +89,7 @@ fun Context.scheduleNextEventReminder(event: Event, showToasts: Boolean) { } val now = getNowSeconds() - val reminderSeconds = event.getReminders().reversed().map { it * 60 } + val reminderSeconds = validReminders.reversed().map { it.minutes * 60 } eventsHelper.getEvents(now, now + YEAR, event.id!!, false) { if (it.isNotEmpty()) { for (curEvent in it) { @@ -159,7 +160,7 @@ fun Context.getRepetitionText(seconds: Int) = when (seconds) { } fun Context.notifyRunningEvents() { - eventsHelper.getRunningEvents().filter { it.getReminders().isNotEmpty() }.forEach { + eventsHelper.getRunningEvents().filter { it.getReminders().any { it.type == REMINDER_NOTIFICATION } }.forEach { notifyEvent(it) } } @@ -408,6 +409,7 @@ fun Context.addDayEvents(day: DayMonthly, linearLayout: LinearLayout, res: Resou text = it.title.replace(" ", "\u00A0") // allow word break by char background = backgroundDrawable layoutParams = eventLayoutParams + contentDescription = it.title linearLayout.addView(this) } } diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/fragments/DayFragment.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/fragments/DayFragment.kt index c321d965e..5ed1a9be8 100644 --- a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/fragments/DayFragment.kt +++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/fragments/DayFragment.kt @@ -5,12 +5,11 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import android.widget.DatePicker import android.widget.RelativeLayout -import androidx.appcompat.app.AlertDialog import androidx.fragment.app.Fragment import com.simplemobiletools.calendar.pro.R import com.simplemobiletools.calendar.pro.activities.EventActivity +import com.simplemobiletools.calendar.pro.activities.MainActivity import com.simplemobiletools.calendar.pro.activities.SimpleActivity import com.simplemobiletools.calendar.pro.adapters.DayEventsAdapter import com.simplemobiletools.calendar.pro.extensions.config @@ -22,11 +21,8 @@ import com.simplemobiletools.calendar.pro.helpers.Formatter import com.simplemobiletools.calendar.pro.interfaces.NavigationListener import com.simplemobiletools.calendar.pro.models.Event import com.simplemobiletools.commons.extensions.applyColorFilter -import com.simplemobiletools.commons.extensions.getDialogTheme -import com.simplemobiletools.commons.extensions.setupDialogStuff import kotlinx.android.synthetic.main.fragment_day.view.* import kotlinx.android.synthetic.main.top_navigation.view.* -import org.joda.time.DateTime import java.util.* class DayFragment : Fragment() { @@ -81,35 +77,14 @@ class DayFragment : Fragment() { val day = Formatter.getDayTitle(context!!, mDayCode) mHolder.top_value.apply { text = day - setOnClickListener { pickDay() } + contentDescription = text + setOnClickListener { + (activity as MainActivity).showGoToDateDialog() + } setTextColor(context.config.textColor) } } - private fun pickDay() { - activity!!.setTheme(context!!.getDialogTheme()) - val view = layoutInflater.inflate(R.layout.date_picker, null) - val datePicker = view.findViewById(R.id.date_picker) - - val dateTime = Formatter.getDateTimeFromCode(mDayCode) - datePicker.init(dateTime.year, dateTime.monthOfYear - 1, dateTime.dayOfMonth, null) - - AlertDialog.Builder(context!!) - .setNegativeButton(R.string.cancel, null) - .setPositiveButton(R.string.ok) { dialog, which -> positivePressed(dateTime, datePicker) } - .create().apply { - activity?.setupDialogStuff(view, this) - } - } - - private fun positivePressed(dateTime: DateTime, datePicker: DatePicker) { - val month = datePicker.month + 1 - val year = datePicker.year - val day = datePicker.dayOfMonth - val newDateTime = dateTime.withDate(year, month, day) - mListener?.goToDateTime(newDateTime) - } - fun updateCalendar() { val startTS = Formatter.getDayStartTS(mDayCode) val endTS = Formatter.getDayEndTS(mDayCode) diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/fragments/DayFragmentsHolder.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/fragments/DayFragmentsHolder.kt index f7cf5a36c..17c9d508a 100644 --- a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/fragments/DayFragmentsHolder.kt +++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/fragments/DayFragmentsHolder.kt @@ -5,6 +5,8 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import android.widget.DatePicker +import androidx.appcompat.app.AlertDialog import androidx.viewpager.widget.ViewPager import com.simplemobiletools.calendar.pro.R import com.simplemobiletools.calendar.pro.activities.MainActivity @@ -13,6 +15,8 @@ import com.simplemobiletools.calendar.pro.extensions.config import com.simplemobiletools.calendar.pro.helpers.DAY_CODE import com.simplemobiletools.calendar.pro.helpers.Formatter import com.simplemobiletools.calendar.pro.interfaces.NavigationListener +import com.simplemobiletools.commons.extensions.getDialogTheme +import com.simplemobiletools.commons.extensions.setupDialogStuff import com.simplemobiletools.commons.extensions.updateActionBarTitle import com.simplemobiletools.commons.views.MyViewPager import kotlinx.android.synthetic.main.fragment_days_holder.view.* @@ -99,6 +103,30 @@ class DayFragmentsHolder : MyFragmentHolder(), NavigationListener { setupFragment() } + override fun showGoToDateDialog() { + activity!!.setTheme(context!!.getDialogTheme()) + val view = layoutInflater.inflate(R.layout.date_picker, null) + val datePicker = view.findViewById(R.id.date_picker) + + val dateTime = Formatter.getDateTimeFromCode(currentDayCode) + datePicker.init(dateTime.year, dateTime.monthOfYear - 1, dateTime.dayOfMonth, null) + + AlertDialog.Builder(context!!) + .setNegativeButton(R.string.cancel, null) + .setPositiveButton(R.string.ok) { dialog, which -> dateSelected(dateTime, datePicker) } + .create().apply { + activity?.setupDialogStuff(view, this) + } + } + + private fun dateSelected(dateTime: DateTime, datePicker: DatePicker) { + val month = datePicker.month + 1 + val year = datePicker.year + val day = datePicker.dayOfMonth + val newDateTime = dateTime.withDate(year, month, day) + goToDateTime(newDateTime) + } + override fun refreshEvents() { (viewPager?.adapter as? MyDayPagerAdapter)?.updateCalendars(viewPager?.currentItem ?: 0) } diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/fragments/EventListFragment.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/fragments/EventListFragment.kt index f6d1d9802..9527b8eb3 100644 --- a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/fragments/EventListFragment.kt +++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/fragments/EventListFragment.kt @@ -173,8 +173,9 @@ class EventListFragment : MyFragmentHolder(), RefreshRecyclerViewListener { checkEvents() } - override fun goToToday() { - } + override fun goToToday() {} + + override fun showGoToDateDialog() {} override fun refreshEvents() { checkEvents() diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/fragments/MonthFragment.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/fragments/MonthFragment.kt index c114ef9b1..e83e21e08 100644 --- a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/fragments/MonthFragment.kt +++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/fragments/MonthFragment.kt @@ -6,9 +6,7 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import android.widget.DatePicker import android.widget.RelativeLayout -import androidx.appcompat.app.AlertDialog import androidx.fragment.app.Fragment import com.simplemobiletools.calendar.pro.R import com.simplemobiletools.calendar.pro.activities.MainActivity @@ -21,9 +19,6 @@ import com.simplemobiletools.calendar.pro.interfaces.MonthlyCalendar import com.simplemobiletools.calendar.pro.interfaces.NavigationListener import com.simplemobiletools.calendar.pro.models.DayMonthly import com.simplemobiletools.commons.extensions.applyColorFilter -import com.simplemobiletools.commons.extensions.beGone -import com.simplemobiletools.commons.extensions.getDialogTheme -import com.simplemobiletools.commons.extensions.setupDialogStuff import kotlinx.android.synthetic.main.fragment_month.view.* import kotlinx.android.synthetic.main.top_navigation.view.* import org.joda.time.DateTime @@ -100,6 +95,7 @@ class MonthFragment : Fragment(), MonthlyCalendar { activity?.runOnUiThread { mHolder.top_value.apply { text = month + contentDescription = text setTextColor(mConfig.textColor) } updateDays(days) @@ -136,35 +132,11 @@ class MonthFragment : Fragment(), MonthlyCalendar { mHolder.top_value.apply { setTextColor(mConfig.textColor) setOnClickListener { - showMonthDialog() + (activity as MainActivity).showGoToDateDialog() } } } - private fun showMonthDialog() { - activity!!.setTheme(context!!.getDialogTheme()) - val view = layoutInflater.inflate(R.layout.date_picker, null) - val datePicker = view.findViewById(R.id.date_picker) - datePicker.findViewById(Resources.getSystem().getIdentifier("day", "id", "android")).beGone() - - val dateTime = DateTime(mCalendar!!.mTargetDate.toString()) - datePicker.init(dateTime.year, dateTime.monthOfYear - 1, 1, null) - - AlertDialog.Builder(context!!) - .setNegativeButton(R.string.cancel, null) - .setPositiveButton(R.string.ok) { dialog, which -> positivePressed(dateTime, datePicker) } - .create().apply { - activity?.setupDialogStuff(view, this) - } - } - - private fun positivePressed(dateTime: DateTime, datePicker: DatePicker) { - val month = datePicker.month + 1 - val year = datePicker.year - val newDateTime = dateTime.withDate(year, month, 1) - listener?.goToDateTime(newDateTime) - } - private fun updateDays(days: ArrayList) { mHolder.month_view_wrapper.updateDays(days) { (activity as MainActivity).openDayFromMonthly(Formatter.getDateTimeFromCode(it.code)) diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/fragments/MonthFragmentsHolder.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/fragments/MonthFragmentsHolder.kt index 4fc125c97..e9eebd787 100644 --- a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/fragments/MonthFragmentsHolder.kt +++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/fragments/MonthFragmentsHolder.kt @@ -1,10 +1,13 @@ package com.simplemobiletools.calendar.pro.fragments +import android.content.res.Resources import android.graphics.drawable.ColorDrawable import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import android.widget.DatePicker +import androidx.appcompat.app.AlertDialog import androidx.viewpager.widget.ViewPager import com.simplemobiletools.calendar.pro.R import com.simplemobiletools.calendar.pro.activities.MainActivity @@ -14,6 +17,9 @@ import com.simplemobiletools.calendar.pro.extensions.getMonthCode import com.simplemobiletools.calendar.pro.helpers.DAY_CODE import com.simplemobiletools.calendar.pro.helpers.Formatter import com.simplemobiletools.calendar.pro.interfaces.NavigationListener +import com.simplemobiletools.commons.extensions.beGone +import com.simplemobiletools.commons.extensions.getDialogTheme +import com.simplemobiletools.commons.extensions.setupDialogStuff import com.simplemobiletools.commons.extensions.updateActionBarTitle import com.simplemobiletools.commons.views.MyViewPager import kotlinx.android.synthetic.main.fragment_months_holder.view.* @@ -99,6 +105,30 @@ class MonthFragmentsHolder : MyFragmentHolder(), NavigationListener { setupFragment() } + override fun showGoToDateDialog() { + activity!!.setTheme(context!!.getDialogTheme()) + val view = layoutInflater.inflate(R.layout.date_picker, null) + val datePicker = view.findViewById(R.id.date_picker) + datePicker.findViewById(Resources.getSystem().getIdentifier("day", "id", "android")).beGone() + + val dateTime = DateTime(Formatter.getDateTimeFromCode(currentDayCode).toString()) + datePicker.init(dateTime.year, dateTime.monthOfYear - 1, 1, null) + + AlertDialog.Builder(context!!) + .setNegativeButton(R.string.cancel, null) + .setPositiveButton(R.string.ok) { dialog, which -> datePicked(dateTime, datePicker) } + .create().apply { + activity?.setupDialogStuff(view, this) + } + } + + private fun datePicked(dateTime: DateTime, datePicker: DatePicker) { + val month = datePicker.month + 1 + val year = datePicker.year + val newDateTime = dateTime.withDate(year, month, 1) + goToDateTime(newDateTime) + } + override fun refreshEvents() { (viewPager?.adapter as? MyMonthPagerAdapter)?.updateCalendars(viewPager?.currentItem ?: 0) } diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/fragments/MyFragmentHolder.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/fragments/MyFragmentHolder.kt index 49dcb46a4..a7708e98e 100644 --- a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/fragments/MyFragmentHolder.kt +++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/fragments/MyFragmentHolder.kt @@ -5,6 +5,8 @@ import androidx.fragment.app.Fragment abstract class MyFragmentHolder : Fragment() { abstract fun goToToday() + abstract fun showGoToDateDialog() + abstract fun refreshEvents() abstract fun shouldGoToTodayBeVisible(): Boolean diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/fragments/WeekFragment.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/fragments/WeekFragment.kt index b5f346b6b..f67f605f2 100644 --- a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/fragments/WeekFragment.kt +++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/fragments/WeekFragment.kt @@ -322,6 +322,7 @@ class WeekFragment : Fragment(), WeeklyCalendar { background = ColorDrawable(backgroundColor) setTextColor(textColor) text = event.title + contentDescription = text layout.addView(this) y = startMinutes * minuteHeight (layoutParams as RelativeLayout.LayoutParams).apply { @@ -406,6 +407,7 @@ class WeekFragment : Fragment(), WeeklyCalendar { setTextColor(textColor) text = event.title + contentDescription = text val startDateTime = Formatter.getDateTimeFromTS(event.startTS) val endDateTime = Formatter.getDateTimeFromTS(event.endTS) diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/fragments/WeekFragmentsHolder.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/fragments/WeekFragmentsHolder.kt index f8285522f..257785003 100644 --- a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/fragments/WeekFragmentsHolder.kt +++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/fragments/WeekFragmentsHolder.kt @@ -5,7 +5,9 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import android.widget.DatePicker import android.widget.TextView +import androidx.appcompat.app.AlertDialog import androidx.viewpager.widget.ViewPager import com.simplemobiletools.calendar.pro.R import com.simplemobiletools.calendar.pro.activities.MainActivity @@ -16,6 +18,8 @@ import com.simplemobiletools.calendar.pro.helpers.Formatter import com.simplemobiletools.calendar.pro.helpers.WEEK_START_DATE_TIME import com.simplemobiletools.calendar.pro.interfaces.WeekFragmentListener import com.simplemobiletools.calendar.pro.views.MyScrollView +import com.simplemobiletools.commons.extensions.getDialogTheme +import com.simplemobiletools.commons.extensions.setupDialogStuff import com.simplemobiletools.commons.extensions.updateActionBarSubtitle import com.simplemobiletools.commons.extensions.updateActionBarTitle import com.simplemobiletools.commons.helpers.WEEK_SECONDS @@ -100,10 +104,11 @@ class WeekFragmentsHolder : MyFragmentHolder(), WeekFragmentListener { private fun getWeekTimestamps(targetSeconds: Long): List { val weekTSs = ArrayList(PREFILLED_WEEKS) - var currWeekTS = targetSeconds - (PREFILLED_WEEKS / 2 * WEEK_SECONDS) + val dateTime = Formatter.getDateTimeFromTS(targetSeconds) + var currentWeek = dateTime.minusWeeks(PREFILLED_WEEKS / 2) for (i in 0 until PREFILLED_WEEKS) { - weekTSs.add(currWeekTS) - currWeekTS += WEEK_SECONDS + weekTSs.add(currentWeek.seconds()) + currentWeek = currentWeek.plusWeeks(1) } return weekTSs } @@ -130,6 +135,42 @@ class WeekFragmentsHolder : MyFragmentHolder(), WeekFragmentListener { setupFragment() } + override fun showGoToDateDialog() { + activity!!.setTheme(context!!.getDialogTheme()) + val view = layoutInflater.inflate(R.layout.date_picker, null) + val datePicker = view.findViewById(R.id.date_picker) + + val dateTime = Formatter.getDateTimeFromTS(currentWeekTS) + datePicker.init(dateTime.year, dateTime.monthOfYear - 1, dateTime.dayOfMonth, null) + + AlertDialog.Builder(context!!) + .setNegativeButton(R.string.cancel, null) + .setPositiveButton(R.string.ok) { dialog, which -> dateSelected(dateTime, datePicker) } + .create().apply { + activity?.setupDialogStuff(view, this) + } + } + + private fun dateSelected(dateTime: DateTime, datePicker: DatePicker) { + val isSundayFirst = context!!.config.isSundayFirst + val month = datePicker.month + 1 + val year = datePicker.year + val day = datePicker.dayOfMonth + var newDateTime = dateTime.withDate(year, month, day) + + if (isSundayFirst) { + newDateTime = newDateTime.plusDays(1) + } + + var selectedWeek = newDateTime.withDayOfWeek(1).withTimeAtStartOfDay().minusDays(if (isSundayFirst) 1 else 0) + if (newDateTime.minusDays(7).seconds() > selectedWeek.seconds()) { + selectedWeek = selectedWeek.plusDays(7) + } + + currentWeekTS = selectedWeek.seconds() + setupFragment() + } + override fun refreshEvents() { (viewPager?.adapter as? MyWeekPagerAdapter)?.updateCalendars(viewPager!!.currentItem) } diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/fragments/YearFragmentsHolder.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/fragments/YearFragmentsHolder.kt index 2fd6f8478..ff3221d8f 100644 --- a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/fragments/YearFragmentsHolder.kt +++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/fragments/YearFragmentsHolder.kt @@ -1,16 +1,22 @@ package com.simplemobiletools.calendar.pro.fragments +import android.content.res.Resources import android.graphics.drawable.ColorDrawable import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import android.widget.DatePicker +import androidx.appcompat.app.AlertDialog import androidx.viewpager.widget.ViewPager import com.simplemobiletools.calendar.pro.R import com.simplemobiletools.calendar.pro.activities.MainActivity import com.simplemobiletools.calendar.pro.adapters.MyYearPagerAdapter import com.simplemobiletools.calendar.pro.extensions.config import com.simplemobiletools.calendar.pro.helpers.Formatter +import com.simplemobiletools.commons.extensions.beGone +import com.simplemobiletools.commons.extensions.getDialogTheme +import com.simplemobiletools.commons.extensions.setupDialogStuff import com.simplemobiletools.commons.extensions.updateActionBarTitle import com.simplemobiletools.commons.views.MyViewPager import kotlinx.android.synthetic.main.fragment_years_holder.view.* @@ -83,6 +89,32 @@ class YearFragmentsHolder : MyFragmentHolder() { setupFragment() } + override fun showGoToDateDialog() { + activity!!.setTheme(context!!.getDialogTheme()) + val view = layoutInflater.inflate(R.layout.date_picker, null) + val datePicker = view.findViewById(R.id.date_picker) + datePicker.findViewById(Resources.getSystem().getIdentifier("day", "id", "android")).beGone() + datePicker.findViewById(Resources.getSystem().getIdentifier("month", "id", "android")).beGone() + + val dateTime = DateTime(Formatter.getDateTimeFromCode("${currentYear}0523").toString()) + datePicker.init(dateTime.year, dateTime.monthOfYear - 1, 1, null) + + AlertDialog.Builder(context!!) + .setNegativeButton(R.string.cancel, null) + .setPositiveButton(R.string.ok) { dialog, which -> datePicked(datePicker) } + .create().apply { + activity?.setupDialogStuff(view, this) + } + } + + private fun datePicked(datePicker: DatePicker) { + val pickedYear = datePicker.year + if (currentYear != pickedYear) { + currentYear = datePicker.year + setupFragment() + } + } + override fun refreshEvents() { (viewPager?.adapter as? MyYearPagerAdapter)?.updateCalendars(viewPager?.currentItem ?: 0) } diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/helpers/CalDAVHelper.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/helpers/CalDAVHelper.kt index 55c15c003..1bc9af72f 100644 --- a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/helpers/CalDAVHelper.kt +++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/helpers/CalDAVHelper.kt @@ -8,11 +8,11 @@ import android.database.Cursor import android.provider.CalendarContract import android.provider.CalendarContract.Reminders import android.util.SparseIntArray +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken import com.simplemobiletools.calendar.pro.R import com.simplemobiletools.calendar.pro.extensions.* -import com.simplemobiletools.calendar.pro.models.CalDAVCalendar -import com.simplemobiletools.calendar.pro.models.Event -import com.simplemobiletools.calendar.pro.models.EventType +import com.simplemobiletools.calendar.pro.models.* import com.simplemobiletools.calendar.pro.objects.States.isUpdatingCalDAV import com.simplemobiletools.commons.extensions.* import com.simplemobiletools.commons.helpers.PERMISSION_READ_CALENDAR @@ -52,7 +52,7 @@ class CalDAVHelper(val context: Context) { } @SuppressLint("MissingPermission") - fun getCalDAVCalendars(ids: String, showToasts: Boolean): List { + fun getCalDAVCalendars(ids: String, showToasts: Boolean): ArrayList { val calendars = ArrayList() if (!context.hasPermission(PERMISSION_WRITE_CALENDAR) || !context.hasPermission(PERMISSION_READ_CALENDAR)) { return calendars @@ -72,7 +72,7 @@ class CalDAVHelper(val context: Context) { var cursor: Cursor? = null try { cursor = context.contentResolver.query(uri, projection, selection, null, null) - if (cursor != null && cursor.moveToFirst()) { + if (cursor?.moveToFirst() == true) { do { val id = cursor.getIntValue(CalendarContract.Calendars._ID) val displayName = cursor.getStringValue(CalendarContract.Calendars.CALENDAR_DISPLAY_NAME) @@ -144,7 +144,7 @@ class CalDAVHelper(val context: Context) { var cursor: Cursor? = null try { cursor = context.contentResolver.query(uri, projection, selection, selectionArgs, null) - if (cursor != null && cursor.moveToFirst()) { + if (cursor?.moveToFirst() == true) { do { val colorKey = cursor.getIntValue(CalendarContract.Colors.COLOR_KEY) val color = cursor.getIntValue(CalendarContract.Colors.COLOR) @@ -192,39 +192,37 @@ class CalDAVHelper(val context: Context) { var cursor: Cursor? = null try { cursor = context.contentResolver.query(uri, projection, selection, null, null) - if (cursor != null && cursor.moveToFirst()) { + if (cursor?.moveToFirst() == true) { do { val id = cursor.getLongValue(CalendarContract.Events._ID) val title = cursor.getStringValue(CalendarContract.Events.TITLE) ?: "" val description = cursor.getStringValue(CalendarContract.Events.DESCRIPTION) ?: "" - var startTS = cursor.getLongValue(CalendarContract.Events.DTSTART) - var endTS = cursor.getLongValue(CalendarContract.Events.DTEND) + val startTS = cursor.getLongValue(CalendarContract.Events.DTSTART) / 1000L + var endTS = cursor.getLongValue(CalendarContract.Events.DTEND) / 1000L val allDay = cursor.getIntValue(CalendarContract.Events.ALL_DAY) val rrule = cursor.getStringValue(CalendarContract.Events.RRULE) ?: "" val location = cursor.getStringValue(CalendarContract.Events.EVENT_LOCATION) ?: "" val originalId = cursor.getStringValue(CalendarContract.Events.ORIGINAL_ID) val originalInstanceTime = cursor.getLongValue(CalendarContract.Events.ORIGINAL_INSTANCE_TIME) val reminders = getCalDAVEventReminders(id) - - if (startTS.toString().length == 12 || startTS.toString().length == 13) { - startTS /= 1000L - } - - if (endTS.toString().length == 12 || endTS.toString().length == 13) { - endTS /= 1000L - } + val attendees = Gson().toJson(getCalDAVEventAttendees(id)) if (endTS == 0L) { val duration = cursor.getStringValue(CalendarContract.Events.DURATION) ?: "" endTS = startTS + Parser().parseDurationSeconds(duration) } + val reminder1 = reminders.getOrNull(0) + val reminder2 = reminders.getOrNull(1) + val reminder3 = reminders.getOrNull(2) val importId = getCalDAVEventImportId(calendarId, id) val source = "$CALDAV-$calendarId" val repeatRule = Parser().parseRepeatInterval(rrule, startTS) - val event = Event(null, startTS, endTS, title, location, description, reminders.getOrElse(0) { -1 }, - reminders.getOrElse(1) { -1 }, reminders.getOrElse(2) { -1 }, repeatRule.repeatInterval, repeatRule.repeatRule, - repeatRule.repeatLimit, ArrayList(), importId, allDay, eventTypeId, source = source) + val event = Event(null, startTS, endTS, title, location, description, reminder1?.minutes ?: REMINDER_OFF, + reminder2?.minutes ?: REMINDER_OFF, reminder3?.minutes ?: REMINDER_OFF, reminder1?.type + ?: REMINDER_NOTIFICATION, reminder2?.type ?: REMINDER_NOTIFICATION, reminder3?.type + ?: REMINDER_NOTIFICATION, repeatRule.repeatInterval, repeatRule.repeatRule, + repeatRule.repeatLimit, ArrayList(), attendees, importId, allDay, eventTypeId, source = source) if (event.getIsAllDay()) { event.startTS = Formatter.getShiftedImportTimestamp(event.startTS) @@ -326,6 +324,7 @@ class CalDAVHelper(val context: Context) { event.importId = getCalDAVEventImportId(calendarId, eventRemoteID) setupCalDAVEventReminders(event) + setupCalDAVEventAttendees(event) setupCalDAVEventImportId(event) refreshCalDAVCalendar(event) } @@ -340,18 +339,18 @@ class CalDAVHelper(val context: Context) { context.contentResolver.update(newUri, values, null, null) setupCalDAVEventReminders(event) + setupCalDAVEventAttendees(event) setupCalDAVEventImportId(event) refreshCalDAVCalendar(event) } - @SuppressLint("MissingPermission") private fun setupCalDAVEventReminders(event: Event) { clearEventReminders(event) event.getReminders().forEach { val contentValues = ContentValues().apply { - put(Reminders.MINUTES, it) + put(Reminders.MINUTES, it.minutes) + put(Reminders.METHOD, if (it.type == REMINDER_EMAIL) Reminders.METHOD_EMAIL else Reminders.METHOD_ALERT) put(Reminders.EVENT_ID, event.getCalDAVEventId()) - put(Reminders.METHOD, Reminders.METHOD_ALERT) } try { @@ -362,6 +361,25 @@ class CalDAVHelper(val context: Context) { } } + private fun setupCalDAVEventAttendees(event: Event) { + clearEventAttendees(event) + val attendees = Gson().fromJson>(event.attendees, object : TypeToken>() {}.type) ?: ArrayList() + attendees.forEach { + val contentValues = ContentValues().apply { + put(CalendarContract.Attendees.ATTENDEE_NAME, it.name) + put(CalendarContract.Attendees.ATTENDEE_EMAIL, it.email) + put(CalendarContract.Attendees.ATTENDEE_STATUS, CalendarContract.Attendees.ATTENDEE_STATUS_ACCEPTED) + put(CalendarContract.Attendees.EVENT_ID, event.getCalDAVEventId()) + } + + try { + context.contentResolver.insert(CalendarContract.Attendees.CONTENT_URI, contentValues) + } catch (e: Exception) { + context.toast(R.string.unknown_error_occurred) + } + } + } + private fun setupCalDAVEventImportId(event: Event) { context.eventsDB.updateEventImportIdAndSource(event.importId, "$CALDAV-${event.getCalDAVCalendarId()}", event.id!!) } @@ -397,13 +415,18 @@ class CalDAVHelper(val context: Context) { } } - @SuppressLint("MissingPermission") private fun clearEventReminders(event: Event) { val selection = "${Reminders.EVENT_ID} = ?" val selectionArgs = arrayOf(event.getCalDAVEventId().toString()) context.contentResolver.delete(Reminders.CONTENT_URI, selection, selectionArgs) } + private fun clearEventAttendees(event: Event) { + val selection = "${CalendarContract.Attendees.EVENT_ID} = ?" + val selectionArgs = arrayOf(event.getCalDAVEventId().toString()) + context.contentResolver.delete(CalendarContract.Attendees.CONTENT_URI, selection, selectionArgs) + } + private fun getDurationCode(event: Event): String { return if (event.getIsAllDay()) { val dur = Math.max(1, (event.endTS - event.startTS) / DAY) @@ -428,7 +451,6 @@ class CalDAVHelper(val context: Context) { refreshCalDAVCalendar(event) } - @SuppressLint("MissingPermission") fun insertEventRepeatException(event: Event, occurrenceTS: Long): Long { val uri = CalendarContract.Events.CONTENT_URI val values = fillEventRepeatExceptionValues(event, occurrenceTS) @@ -449,9 +471,8 @@ class CalDAVHelper(val context: Context) { } } - @SuppressLint("MissingPermission") - private fun getCalDAVEventReminders(eventId: Long): List { - val reminders = ArrayList() + private fun getCalDAVEventReminders(eventId: Long): List { + val reminders = ArrayList() val uri = CalendarContract.Reminders.CONTENT_URI val projection = arrayOf( CalendarContract.Reminders.MINUTES, @@ -460,19 +481,47 @@ class CalDAVHelper(val context: Context) { var cursor: Cursor? = null try { cursor = context.contentResolver.query(uri, projection, selection, null, null) - if (cursor != null && cursor.moveToFirst()) { + if (cursor?.moveToFirst() == true) { do { val minutes = cursor.getIntValue(CalendarContract.Reminders.MINUTES) val method = cursor.getIntValue(CalendarContract.Reminders.METHOD) - if (method == CalendarContract.Reminders.METHOD_ALERT) { - reminders.add(minutes) + if (method == CalendarContract.Reminders.METHOD_ALERT || method == CalendarContract.Reminders.METHOD_EMAIL) { + val type = if (method == CalendarContract.Reminders.METHOD_EMAIL) REMINDER_EMAIL else REMINDER_NOTIFICATION + val reminder = Reminder(minutes, type) + reminders.add(reminder) } } while (cursor.moveToNext()) } } finally { cursor?.close() } - return reminders + return reminders.sortedBy { it.minutes } + } + + private fun getCalDAVEventAttendees(eventId: Long): List { + val attendees = ArrayList() + val uri = CalendarContract.Attendees.CONTENT_URI + val projection = arrayOf( + CalendarContract.Attendees.ATTENDEE_NAME, + CalendarContract.Attendees.ATTENDEE_EMAIL, + CalendarContract.Attendees.ATTENDEE_STATUS) + val selection = "${CalendarContract.Attendees.EVENT_ID} = $eventId" + var cursor: Cursor? = null + try { + cursor = context.contentResolver.query(uri, projection, selection, null, null) + if (cursor?.moveToFirst() == true) { + do { + val name = cursor.getStringValue(CalendarContract.Attendees.ATTENDEE_NAME) + val email = cursor.getStringValue(CalendarContract.Attendees.ATTENDEE_EMAIL) + val status = cursor.getIntValue(CalendarContract.Attendees.ATTENDEE_STATUS) + val attendee = Attendee(0, name, email, status, "") + attendees.add(attendee) + } while (cursor.moveToNext()) + } + } finally { + cursor?.close() + } + return attendees } private fun getCalDAVEventImportId(calendarId: Int, eventId: Long) = "$CALDAV-$calendarId-$eventId" diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/helpers/Constants.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/helpers/Constants.kt index 9ef594fe4..4878387c7 100644 --- a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/helpers/Constants.kt +++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/helpers/Constants.kt @@ -15,6 +15,7 @@ const val NEW_EVENT_SET_HOUR_DURATION = "new_event_set_hour_duration" const val WEEK_START_DATE_TIME = "week_start_date_time" const val CALDAV = "Caldav" const val VIEW_TO_OPEN = "view_to_open" +const val SHORTCUT_NEW_EVENT = "shortcut_new_event" const val REGULAR_EVENT_TYPE_ID = 1L const val CHOPPED_LIST_DEFAULT_SIZE = 100 @@ -99,6 +100,8 @@ const val SUMMARY = "SUMMARY" const val DESCRIPTION = "DESCRIPTION:" const val UID = "UID:" const val ACTION = "ACTION:" +const val ATTENDEE = "ATTENDEE:" +const val MAILTO = "mailto:" const val TRIGGER = "TRIGGER:" const val RRULE = "RRULE:" const val CATEGORIES = "CATEGORIES:" @@ -115,6 +118,7 @@ const val SEQUENCE = "SEQUENCE" const val CATEGORY_COLOR = "CATEGORY_COLOR:" const val DISPLAY = "DISPLAY" +const val EMAIL = "EMAIL" const val FREQ = "FREQ" const val UNTIL = "UNTIL" const val COUNT = "COUNT" @@ -150,4 +154,7 @@ const val DELETE_SELECTED_OCCURRENCE = 0 const val DELETE_FUTURE_OCCURRENCES = 1 const val DELETE_ALL_OCCURRENCES = 2 +const val REMINDER_NOTIFICATION = 0 +const val REMINDER_EMAIL = 1 + fun getNowSeconds() = System.currentTimeMillis() / 1000L diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/helpers/EventsHelper.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/helpers/EventsHelper.kt index 52aa5af7f..aee17da2b 100644 --- a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/helpers/EventsHelper.kt +++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/helpers/EventsHelper.kt @@ -253,9 +253,10 @@ class EventsHelper(val context: Context) { eventTypeColors.put(it.id!!, it.color) } + val primaryColor = context.resources.getColor(R.color.color_primary) events.forEach { it.updateIsPastEvent() - it.color = eventTypeColors.get(it.eventType) ?: context.resources.getColor(R.color.color_primary) + it.color = eventTypeColors.get(it.eventType) ?: primaryColor } callback(events) diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/helpers/IcsExporter.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/helpers/IcsExporter.kt index 35a959666..1c7355b3c 100644 --- a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/helpers/IcsExporter.kt +++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/helpers/IcsExporter.kt @@ -1,8 +1,10 @@ package com.simplemobiletools.calendar.pro.helpers import com.simplemobiletools.calendar.pro.R +import com.simplemobiletools.calendar.pro.extensions.calDAVHelper import com.simplemobiletools.calendar.pro.extensions.eventTypesDB import com.simplemobiletools.calendar.pro.helpers.IcsExporter.ExportResult.* +import com.simplemobiletools.calendar.pro.models.CalDAVCalendar import com.simplemobiletools.calendar.pro.models.Event import com.simplemobiletools.commons.activities.BaseSimpleActivity import com.simplemobiletools.commons.extensions.getFileOutputStream @@ -19,6 +21,7 @@ class IcsExporter { private var eventsExported = 0 private var eventsFailed = 0 + private var calendars = ArrayList() fun exportEvents(activity: BaseSimpleActivity, file: File, events: ArrayList, showExportingToast: Boolean, callback: (result: ExportResult) -> Unit) { val fileDirItem = FileDirItem(file.absolutePath, file.name) @@ -29,6 +32,7 @@ class IcsExporter { } Thread { + calendars = activity.calDAVHelper.getCalDAVCalendars("", false) if (showExportingToast) { activity.toast(R.string.exporting) } @@ -77,17 +81,20 @@ class IcsExporter { } private fun fillReminders(event: Event, out: BufferedWriter) { - checkReminder(event.reminder1Minutes, out) - checkReminder(event.reminder2Minutes, out) - checkReminder(event.reminder3Minutes, out) - } - - private fun checkReminder(minutes: Int, out: BufferedWriter) { - if (minutes != -1) { + event.getReminders().forEach { + val reminder = it out.apply { writeLn(BEGIN_ALARM) - writeLn("$ACTION$DISPLAY") - writeLn("$TRIGGER-${Parser().getDurationCode(minutes.toLong())}") + if (reminder.type == REMINDER_NOTIFICATION) { + writeLn("$ACTION$DISPLAY") + } else { + writeLn("$ACTION$EMAIL") + val attendee = calendars.firstOrNull { it.id == event.getCalDAVCalendarId()}?.accountName + if (attendee != null) { + writeLn("$ATTENDEE$MAILTO$attendee") + } + } + writeLn("$TRIGGER-${Parser().getDurationCode(reminder.minutes.toLong())}") writeLn(END_ALARM) } } diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/helpers/IcsImporter.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/helpers/IcsImporter.kt index 2595b7669..c834bb5dd 100644 --- a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/helpers/IcsImporter.kt +++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/helpers/IcsImporter.kt @@ -8,6 +8,7 @@ import com.simplemobiletools.calendar.pro.extensions.eventsHelper import com.simplemobiletools.calendar.pro.helpers.IcsImporter.ImportResult.* import com.simplemobiletools.calendar.pro.models.Event import com.simplemobiletools.calendar.pro.models.EventType +import com.simplemobiletools.calendar.pro.models.Reminder import com.simplemobiletools.commons.extensions.areDigitsOnly import com.simplemobiletools.commons.extensions.showErrorToast import java.io.File @@ -26,6 +27,7 @@ class IcsImporter(val activity: SimpleActivity) { private var curRecurrenceDayCode = "" private var curFlags = 0 private var curReminderMinutes = ArrayList() + private var curReminderActions = ArrayList() private var curRepeatExceptions = ArrayList() private var curRepeatInterval = 0 private var curRepeatLimit = 0L @@ -37,7 +39,8 @@ class IcsImporter(val activity: SimpleActivity) { private var isProperReminderAction = false private var isDescription = false private var isSequence = false - private var curReminderTriggerMinutes = -1 + private var curReminderTriggerMinutes = REMINDER_OFF + private var curReminderTriggerAction = REMINDER_NOTIFICATION private val eventsHelper = activity.eventsHelper private var eventsImported = 0 @@ -101,7 +104,11 @@ class IcsImporter(val activity: SimpleActivity) { curRepeatLimit = repeatRule.repeatLimit } else if (line.startsWith(ACTION)) { isNotificationDescription = true - isProperReminderAction = line.substring(ACTION.length) == DISPLAY + val action = line.substring(ACTION.length) + isProperReminderAction = action == DISPLAY || action == EMAIL + if (isProperReminderAction) { + curReminderTriggerAction = if (action == DISPLAY) REMINDER_NOTIFICATION else REMINDER_EMAIL + } } else if (line.startsWith(TRIGGER)) { curReminderTriggerMinutes = Parser().parseDurationSeconds(line.substring(TRIGGER.length)) / 60 } else if (line.startsWith(CATEGORY_COLOR)) { @@ -129,8 +136,9 @@ class IcsImporter(val activity: SimpleActivity) { } else if (line.startsWith(SEQUENCE)) { isSequence = true } else if (line == END_ALARM) { - if (isProperReminderAction && curReminderTriggerMinutes != -1) { + if (isProperReminderAction && curReminderTriggerMinutes != REMINDER_OFF) { curReminderMinutes.add(curReminderTriggerMinutes) + curReminderActions.add(curReminderTriggerAction) } } else if (line == END_EVENT) { if (curStart != -1L && curEnd == -1L) { @@ -147,11 +155,18 @@ class IcsImporter(val activity: SimpleActivity) { continue } + var reminders = arrayListOf( + Reminder(curReminderMinutes.getOrElse(0) { REMINDER_OFF }, curReminderActions.getOrElse(0) { REMINDER_NOTIFICATION }), + Reminder(curReminderMinutes.getOrElse(1) { REMINDER_OFF }, curReminderActions.getOrElse(1) { REMINDER_NOTIFICATION }), + Reminder(curReminderMinutes.getOrElse(2) { REMINDER_OFF }, curReminderActions.getOrElse(2) { REMINDER_NOTIFICATION }) + ) + reminders = reminders.filter { it.minutes != REMINDER_OFF }.sortedBy { it.minutes }.toMutableList() as ArrayList + val eventType = eventTypes.firstOrNull { it.id == curEventTypeId } val source = if (calDAVCalendarId == 0 || eventType?.isSyncedEventType() == false) SOURCE_IMPORTED_ICS else "$CALDAV-$calDAVCalendarId" - val event = Event(null, curStart, curEnd, curTitle, curLocation, curDescription, curReminderMinutes.getOrElse(0) { -1 }, - curReminderMinutes.getOrElse(1) { -1 }, curReminderMinutes.getOrElse(2) { -1 }, curRepeatInterval, curRepeatRule, - curRepeatLimit, curRepeatExceptions, curImportId, curFlags, curEventTypeId, 0, curLastModified, source) + val event = Event(null, curStart, curEnd, curTitle, curLocation, curDescription, reminders[0].minutes, + reminders[1].minutes, reminders[2].minutes, reminders[0].type, reminders[1].type, reminders[2].type, curRepeatInterval, curRepeatRule, + curRepeatLimit, curRepeatExceptions, "", curImportId, curFlags, curEventTypeId, 0, curLastModified, source) if (event.getIsAllDay() && curEnd > curStart) { event.endTS -= DAY @@ -269,6 +284,7 @@ class IcsImporter(val activity: SimpleActivity) { curRecurrenceDayCode = "" curFlags = 0 curReminderMinutes = ArrayList() + curReminderActions = ArrayList() curRepeatExceptions = ArrayList() curRepeatInterval = 0 curRepeatLimit = 0L @@ -279,6 +295,7 @@ class IcsImporter(val activity: SimpleActivity) { isNotificationDescription = false isProperReminderAction = false isSequence = false - curReminderTriggerMinutes = -1 + curReminderTriggerMinutes = REMINDER_OFF + curReminderTriggerAction = REMINDER_NOTIFICATION } } diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/models/Attendee.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/models/Attendee.kt new file mode 100644 index 000000000..74ebfa8d7 --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/models/Attendee.kt @@ -0,0 +1,5 @@ +package com.simplemobiletools.calendar.pro.models + +data class Attendee(val contactId: Int, var name: String, val email: String, val status: Int, var photoUri: String) { + fun getPublicName() = if (name.isNotEmpty()) name else email +} diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/models/Event.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/models/Event.kt index 01e617bd5..72c2dd1f7 100644 --- a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/models/Event.kt +++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/models/Event.kt @@ -22,10 +22,14 @@ data class Event( @ColumnInfo(name = "reminder_1_minutes") var reminder1Minutes: Int = -1, @ColumnInfo(name = "reminder_2_minutes") var reminder2Minutes: Int = -1, @ColumnInfo(name = "reminder_3_minutes") var reminder3Minutes: Int = -1, + @ColumnInfo(name = "reminder_1_type") var reminder1Type: Int = REMINDER_NOTIFICATION, + @ColumnInfo(name = "reminder_2_type") var reminder2Type: Int = REMINDER_NOTIFICATION, + @ColumnInfo(name = "reminder_3_type") var reminder3Type: Int = REMINDER_NOTIFICATION, @ColumnInfo(name = "repeat_interval") var repeatInterval: Int = 0, @ColumnInfo(name = "repeat_rule") var repeatRule: Int = 0, @ColumnInfo(name = "repeat_limit") var repeatLimit: Long = 0L, @ColumnInfo(name = "repetition_exceptions") var repetitionExceptions: ArrayList = ArrayList(), + @ColumnInfo(name = "attendees") var attendees: String = "", @ColumnInfo(name = "import_id") var importId: String = "", @ColumnInfo(name = "flags") var flags: Int = 0, @ColumnInfo(name = "event_type") var eventType: Long = REGULAR_EVENT_TYPE_ID, @@ -80,7 +84,7 @@ data class Event( while (newDateTime.dayOfMonth().maximumValue < Formatter.getDateTimeFromTS(original.startTS).dayOfMonth().maximumValue) { newDateTime = newDateTime.plusMonths(repeatInterval / MONTH) - newDateTime = newDateTime.withDayOfMonth(newDateTime.dayOfMonth().maximumValue) + newDateTime = newDateTime.withDayOfMonth(currStart.dayOfMonth) } return newDateTime } @@ -116,7 +120,11 @@ data class Event( fun getIsAllDay() = flags and FLAG_ALL_DAY != 0 - fun getReminders() = setOf(reminder1Minutes, reminder2Minutes, reminder3Minutes).filter { it != REMINDER_OFF } + fun getReminders() = setOf( + Reminder(reminder1Minutes, reminder1Type), + Reminder(reminder2Minutes, reminder2Type), + Reminder(reminder3Minutes, reminder3Type) + ).filter { it.minutes != REMINDER_OFF } // properly return the start time of all-day events as midnight fun getEventStartTS(): Long { diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/models/Reminder.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/models/Reminder.kt new file mode 100644 index 000000000..6f9b34f5b --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/models/Reminder.kt @@ -0,0 +1,3 @@ +package com.simplemobiletools.calendar.pro.models + +data class Reminder(val minutes: Int, val type: Int) diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/receivers/NotificationReceiver.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/receivers/NotificationReceiver.kt index 37996827d..58e905633 100644 --- a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/receivers/NotificationReceiver.kt +++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/receivers/NotificationReceiver.kt @@ -10,6 +10,7 @@ import com.simplemobiletools.calendar.pro.extensions.scheduleNextEventReminder import com.simplemobiletools.calendar.pro.extensions.updateListWidget import com.simplemobiletools.calendar.pro.helpers.EVENT_ID import com.simplemobiletools.calendar.pro.helpers.Formatter +import com.simplemobiletools.calendar.pro.helpers.REMINDER_NOTIFICATION class NotificationReceiver : BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) { @@ -30,7 +31,7 @@ class NotificationReceiver : BroadcastReceiver() { context.updateListWidget() val event = context.eventsDB.getEventWithId(id) - if (event == null || event.getReminders().isEmpty()) { + if (event == null || event.getReminders().none { it.type == REMINDER_NOTIFICATION }) { return } diff --git a/app/src/main/res/drawable-hdpi/ic_people.png b/app/src/main/res/drawable-hdpi/ic_people.png new file mode 100755 index 000000000..26691bec8 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_people.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_people.png b/app/src/main/res/drawable-xhdpi/ic_people.png new file mode 100755 index 000000000..93f23280c Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_people.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_people.png b/app/src/main/res/drawable-xxhdpi/ic_people.png new file mode 100755 index 000000000..6695583fe Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_people.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_people.png b/app/src/main/res/drawable-xxxhdpi/ic_people.png new file mode 100755 index 000000000..594db41ff Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_people.png differ diff --git a/app/src/main/res/drawable/attendee_circular_background.xml b/app/src/main/res/drawable/attendee_circular_background.xml new file mode 100644 index 000000000..4b622c6eb --- /dev/null +++ b/app/src/main/res/drawable/attendee_circular_background.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + diff --git a/app/src/main/res/layout/activity_event.xml b/app/src/main/res/layout/activity_event.xml index a0e3bfe5d..7b1bfe893 100644 --- a/app/src/main/res/layout/activity_event.xml +++ b/app/src/main/res/layout/activity_event.xml @@ -50,9 +50,7 @@ android:layout_alignTop="@+id/event_location" android:layout_alignBottom="@+id/event_location" android:layout_alignParentEnd="true" - android:layout_alignParentRight="true" android:layout_marginEnd="@dimen/activity_margin" - android:layout_marginRight="@dimen/activity_margin" android:background="?attr/selectableItemBackgroundBorderless" android:paddingStart="@dimen/small_margin" android:paddingEnd="@dimen/small_margin" @@ -104,13 +102,9 @@ android:layout_height="wrap_content" android:layout_below="@+id/event_description_divider" android:layout_alignParentEnd="true" - android:layout_alignParentRight="true" android:layout_marginStart="@dimen/small_margin" - android:layout_marginLeft="@dimen/small_margin" android:layout_marginEnd="@dimen/normal_margin" - android:layout_marginRight="@dimen/normal_margin" android:layout_toEndOf="@+id/event_time_image" - android:layout_toRightOf="@+id/event_time_image" android:paddingTop="@dimen/normal_margin" android:paddingBottom="@dimen/normal_margin" android:text="@string/all_day" @@ -123,11 +117,9 @@ android:layout_height="wrap_content" android:layout_below="@+id/event_time_image" android:layout_alignStart="@+id/event_all_day" - android:layout_alignLeft="@+id/event_all_day" android:background="?attr/selectableItemBackground" android:paddingTop="@dimen/activity_margin" android:paddingEnd="@dimen/activity_margin" - android:paddingRight="@dimen/activity_margin" android:paddingBottom="@dimen/activity_margin" android:textSize="@dimen/day_text_size" tools:text="January 1 1970"/> @@ -138,7 +130,6 @@ android:layout_height="wrap_content" android:layout_below="@+id/event_time_image" android:layout_alignParentEnd="true" - android:layout_alignParentRight="true" android:background="?attr/selectableItemBackground" android:padding="@dimen/activity_margin" android:textSize="@dimen/day_text_size" @@ -150,11 +141,9 @@ android:layout_height="wrap_content" android:layout_below="@+id/event_start_date" android:layout_alignStart="@+id/event_all_day" - android:layout_alignLeft="@+id/event_all_day" android:background="?attr/selectableItemBackground" android:paddingTop="@dimen/activity_margin" android:paddingEnd="@dimen/activity_margin" - android:paddingRight="@dimen/activity_margin" android:paddingBottom="@dimen/activity_margin" android:textSize="@dimen/day_text_size" tools:text="January 1 1970"/> @@ -165,7 +154,6 @@ android:layout_height="wrap_content" android:layout_below="@+id/event_start_time" android:layout_alignParentEnd="true" - android:layout_alignParentRight="true" android:background="?attr/selectableItemBackground" android:padding="@dimen/activity_margin" android:textSize="@dimen/day_text_size" @@ -189,7 +177,6 @@ android:layout_alignTop="@+id/event_reminder_1" android:layout_alignBottom="@+id/event_reminder_1" android:layout_marginStart="@dimen/normal_margin" - android:layout_marginLeft="@dimen/normal_margin" android:alpha="0.8" android:padding="@dimen/medium_margin" android:src="@drawable/ic_bell"/> @@ -200,13 +187,28 @@ android:layout_height="wrap_content" android:layout_below="@+id/event_date_time_divider" android:layout_marginStart="@dimen/small_margin" - android:layout_marginLeft="@dimen/small_margin" + android:layout_toStartOf="@+id/event_reminder_1_type" android:layout_toEndOf="@+id/event_reminder_image" - android:layout_toRightOf="@+id/event_reminder_image" android:background="?attr/selectableItemBackground" + android:ellipsize="end" + android:lines="1" android:paddingTop="@dimen/activity_margin" android:paddingBottom="@dimen/activity_margin" - android:textSize="@dimen/day_text_size"/> + android:textSize="@dimen/day_text_size" + tools:text="@string/add_another_reminder"/> + + + android:visibility="gone" + tools:text="@string/add_another_reminder"/> + + + android:visibility="gone" + tools:text="@string/add_another_reminder"/> + + @@ -267,13 +300,12 @@ android:layout_height="wrap_content" android:layout_below="@+id/event_reminder_divider" android:layout_marginStart="@dimen/small_margin" - android:layout_marginLeft="@dimen/small_margin" android:layout_toEndOf="@+id/event_repetition_image" - android:layout_toRightOf="@+id/event_repetition_image" android:background="?attr/selectableItemBackground" android:paddingTop="@dimen/normal_margin" android:paddingBottom="@dimen/normal_margin" - android:textSize="@dimen/day_text_size"/> + android:textSize="@dimen/day_text_size" + tools:text="@string/no_repetition"/> @@ -300,9 +331,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_alignParentEnd="true" - android:layout_alignParentRight="true" android:layout_toEndOf="@+id/event_repetition_rule_label" - android:layout_toRightOf="@+id/event_repetition_rule_label" android:clickable="false" android:gravity="end" android:padding="@dimen/activity_margin" @@ -316,7 +345,6 @@ android:layout_height="wrap_content" android:layout_below="@+id/event_repetition_rule_holder" android:layout_alignStart="@+id/event_repetition" - android:layout_alignLeft="@+id/event_repetition" android:background="?attr/selectableItemBackground" android:visibility="gone"> @@ -325,7 +353,6 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_toStartOf="@+id/event_repetition_limit" - android:layout_toLeftOf="@+id/event_repetition_limit" android:clickable="false" android:paddingTop="@dimen/activity_margin" android:paddingBottom="@dimen/activity_margin" @@ -337,7 +364,6 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentEnd="true" - android:layout_alignParentRight="true" android:clickable="false" android:padding="@dimen/activity_margin" android:text="@string/forever" @@ -354,14 +380,43 @@ android:importantForAccessibility="no"/> + + + + + + @@ -382,7 +436,6 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginStart="@dimen/small_margin" - android:layout_marginLeft="@dimen/small_margin" android:layout_toStartOf="@+id/event_caldav_calendar_color" android:ellipsize="end" android:maxLines="1" @@ -398,7 +451,6 @@ android:layout_height="wrap_content" android:layout_below="@+id/event_caldav_calendar_name" android:layout_marginStart="@dimen/small_margin" - android:layout_marginLeft="@dimen/small_margin" android:layout_toStartOf="@+id/event_caldav_calendar_color" android:ellipsize="end" android:maxLines="1" @@ -412,10 +464,8 @@ android:layout_width="@dimen/color_sample_size" android:layout_height="@dimen/color_sample_size" android:layout_alignParentEnd="true" - android:layout_alignParentRight="true" android:layout_centerVertical="true" android:layout_marginEnd="@dimen/activity_margin" - android:layout_marginRight="@dimen/activity_margin" android:clickable="false"/> @@ -437,7 +487,6 @@ android:layout_alignTop="@+id/event_type_holder" android:layout_alignBottom="@+id/event_type_holder" android:layout_marginStart="@dimen/normal_margin" - android:layout_marginLeft="@dimen/normal_margin" android:alpha="0.8" android:padding="@dimen/medium_margin" android:src="@drawable/ic_color"/> @@ -450,7 +499,6 @@ android:layout_marginTop="@dimen/medium_margin" android:layout_marginBottom="@dimen/medium_margin" android:layout_toEndOf="@+id/event_type_image" - android:layout_toRightOf="@+id/event_type_image" android:background="?attr/selectableItemBackground"> diff --git a/app/src/main/res/layout/dialog_set_reminders.xml b/app/src/main/res/layout/dialog_set_reminders.xml new file mode 100644 index 000000000..09b8597af --- /dev/null +++ b/app/src/main/res/layout/dialog_set_reminders.xml @@ -0,0 +1,59 @@ + + + + + + + + + + + + diff --git a/app/src/main/res/layout/item_attendee.xml b/app/src/main/res/layout/item_attendee.xml new file mode 100644 index 000000000..1b04b5433 --- /dev/null +++ b/app/src/main/res/layout/item_attendee.xml @@ -0,0 +1,24 @@ + + + + + + diff --git a/app/src/main/res/layout/item_autocomplete_email.xml b/app/src/main/res/layout/item_autocomplete_email.xml new file mode 100644 index 000000000..56fd8b08f --- /dev/null +++ b/app/src/main/res/layout/item_autocomplete_email.xml @@ -0,0 +1,38 @@ + + + + + + + + diff --git a/app/src/main/res/layout/item_autocomplete_email_name.xml b/app/src/main/res/layout/item_autocomplete_email_name.xml new file mode 100644 index 000000000..c11ab5886 --- /dev/null +++ b/app/src/main/res/layout/item_autocomplete_email_name.xml @@ -0,0 +1,56 @@ + + + + + + + + + + diff --git a/app/src/main/res/menu/menu_main.xml b/app/src/main/res/menu/menu_main.xml index 9d011d54a..2adc71e60 100644 --- a/app/src/main/res/menu/menu_main.xml +++ b/app/src/main/res/menu/menu_main.xml @@ -27,6 +27,10 @@ android:icon="@drawable/ic_repeat" android:title="@string/refresh_caldav_calendars" app:showAsAction="ifRoom"/> + لائحة أحداث بسيطة Seems like you don\'t have any upcoming events. الذهاب الى اليوم + Go to date تقويم شهري @@ -97,6 +98,9 @@ إضافة تذكير آخر تذكيرات الحدث + + Add another attendee + إستيراد الأحداث تصدير الأحداث @@ -224,7 +228,7 @@ - Offline kalendár pre vaše udalosti bez reklám, rešpektujúca vaše súkromie. + An offline calendar for your events without ads, respecting your privacy. A simple calendar with optional CalDAV synchronization. You can easily create recurring events and setup reminders, it can also display week numbers. diff --git a/app/src/main/res/values-az/strings.xml b/app/src/main/res/values-az/strings.xml index 2e1b89cc7..6c7a3188d 100644 --- a/app/src/main/res/values-az/strings.xml +++ b/app/src/main/res/values-az/strings.xml @@ -10,6 +10,7 @@ Sadə hadisələr siyahısı Deyəsən yaxınlarda heçbir hadisə yoxdur. Bugünə get + Go to date Aylıq Təqvim @@ -97,6 +98,9 @@ Başqa bir xatırladıcı əlavə et Hadisə xatırladıcıları + + Add another attendee + Hadisələri daxil et Hadisələri xaric et diff --git a/app/src/main/res/values-br/strings.xml b/app/src/main/res/values-br/strings.xml index 447a7b0d8..4dc3ed6d6 100644 --- a/app/src/main/res/values-br/strings.xml +++ b/app/src/main/res/values-br/strings.xml @@ -10,6 +10,7 @@ Roll darvoudoù een War a-seblant n\'ho peus darvoud ebet da zont. Mont da hiziv + Go to date Deiziataer miziek @@ -97,6 +98,9 @@ Ouzhpennañ un adc\'halv all Event reminders + + Add another attendee + Enporzhiañ darvoudoù Ezporzhiañ darvoudoù diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index 7e55a66c6..3e70a3faf 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -10,6 +10,7 @@ Jednoduchý seznam událostí Nemáte žádné nadcházející události. Přejít na dnešek + Go to date Měsíční kalendář @@ -97,6 +98,9 @@ Přidat další připomínku Připomínky událostí + + Add another attendee + Import událostí Export událostí diff --git a/app/src/main/res/values-da/strings.xml b/app/src/main/res/values-da/strings.xml index 6bb1d1336..0980297f7 100644 --- a/app/src/main/res/values-da/strings.xml +++ b/app/src/main/res/values-da/strings.xml @@ -10,6 +10,7 @@ Begivenhedsliste Du ser ikke ud til at have nogen forestående begivenheder. Gå til i dag + Gå til dato Månedlig kalender @@ -97,6 +98,9 @@ Tilføj en påmindelse mere Påmindelser + + Tilføj en anden deltager + Importer begivenheder Eksporter begivenheder @@ -169,13 +173,13 @@ Påmindelse 2 Påmindelse 3 Visning til brug i widget\'en - Last view - New events - Default start time - Next full hour - Default duration - Last used one - Other time + Seneste visning + Nye begivenheder + Standard starttidspunkt + Næste hele time + Standard varighed + Senest brugte + Anden tid CalDAV diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index b452a6cfa..d00356a86 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -10,6 +10,7 @@ Einfache Terminliste Scheint so, als hättest du keine anstehenden Termine. Springe zu Heute + Go to date Monatskalender @@ -97,6 +98,9 @@ Weitere Erinnerung hinzufügen Termin-Erinnerungen + + Add another attendee + Termine importieren Termine exportieren diff --git a/app/src/main/res/values-el/strings.xml b/app/src/main/res/values-el/strings.xml index a3f2734f3..ff658773b 100644 --- a/app/src/main/res/values-el/strings.xml +++ b/app/src/main/res/values-el/strings.xml @@ -9,7 +9,8 @@ Ετήσια προβολή Απλή λίστα εκδηλώσεων Φαίνεται πως δεν έχετε επερχόμενες εκδηλώσεις. - Πηγαίνετε στο σήμερα + Μετάβαση στο σήμερα + Μετάβαση σε ημερ/νία Μηνιαίο ημερολόγιο @@ -49,7 +50,7 @@ Ενημέρωση μόνο του επιλεγμένου περιστατικού Ενημέρωση όλων των περιστατικών Επαναλάβετε μέχρι μια ημερομηνία - Stop repeating after x occurrences + Παύση επαναλήψεων μετά από x εμφανίσεις Επαναλάβετε για πάντα times Επανάληψη @@ -58,9 +59,9 @@ Σε επιλεγμένες μέρες Την ίδια μέρα Την τελευταία μέρα - Επαναλάβετε την ίδια ημέρα κάθε μήνα - Επαναλάβετε την τελευταία ημέρα του μήνα - Επαναλάβετε την τελευταία ημέρα του μήνα + Επανάληψη την ίδια ημέρα κάθε μήνα + Επανάληψη την τελευταία ημέρα του μήνα + Επανάληψη την τελευταία ημέρα του έτους Επανάληψη κάθε Κάθε πρώτη @@ -97,6 +98,9 @@ Προσθέστε μια άλλη υπενθύμιση Υπενθυμίσεις εκδηλώσεων + + Προσθήκη άλλης συμμετοχής + Εισαγωγή εκδηλώσεων Εξαγωγή εκδηλώσεων @@ -168,14 +172,14 @@ Προεπιλογή υπενθύμισης 1 Προεπιλογή υπενθύμισης 2 Προεπιλογή υπενθύμισης 3 - Προβολή για να ανοίξετε από το widget λίστας συμβάντων + Προβολή για άνοιγμα από το widget λίστας συμβάντων Τελευταία προβολή Νέα γεγονότα Προεπιλεγμένη ώρα έναρξης Την επόμενη πλήρη ώρα Προεπιλεγμένη διάρκεια Τελευταία χρήση - Other time + Άλλη ώρα CalDAV diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index 9bee7c773..5094cdd06 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -10,6 +10,7 @@ Lista de eventos sencilla Parece que no tienes ningún evento próximo. Ir al día de hoy + Go to date Calendario mensual @@ -97,6 +98,9 @@ Agregar otro recordatorio Recordatorio de eventos + + Add another attendee + Importar eventos Exportar eventos diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index f7f107fb4..061ab69bf 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -10,6 +10,7 @@ Liste simple d\'événements Il semblerait que vous n\'ayez aucun événement à venir. Aller à aujourd\'hui + Go to date Calendrier mensuel @@ -97,6 +98,9 @@ Ajouter un autre rappel Rappels d\'événements + + Add another attendee + Importer des événements Exporter des événements diff --git a/app/src/main/res/values-gl/strings.xml b/app/src/main/res/values-gl/strings.xml index 9226332ea..aa4cd2dfc 100644 --- a/app/src/main/res/values-gl/strings.xml +++ b/app/src/main/res/values-gl/strings.xml @@ -10,6 +10,7 @@ Lista de eventos simple Semella que non ten ningún evento próximo. Ir ao día de hoxe + Go to date Calendario mensual @@ -97,6 +98,9 @@ Engadir outro recordatorio Recordatorios de eventos + + Add another attendee + Importar eventos Exportar eventos diff --git a/app/src/main/res/values-hi-rIN/strings.xml b/app/src/main/res/values-hi-rIN/strings.xml index 779d42516..2f910dd7b 100644 --- a/app/src/main/res/values-hi-rIN/strings.xml +++ b/app/src/main/res/values-hi-rIN/strings.xml @@ -10,6 +10,7 @@ सरल इवेंट सूची Seems like you don\'t have any upcoming events. Go to today + Go to date Calendar monthly @@ -98,6 +99,9 @@ Add another reminder Event reminders + + Add another attendee + Import events Export events diff --git a/app/src/main/res/values-hr/strings.xml b/app/src/main/res/values-hr/strings.xml index 805eaa53b..b00c1ed47 100644 --- a/app/src/main/res/values-hr/strings.xml +++ b/app/src/main/res/values-hr/strings.xml @@ -10,6 +10,7 @@ Jednostavan popis događaja Izgleda da nemate nadolazećih događaja. Idi na danas + Go to date Mjesečni raspored @@ -97,6 +98,9 @@ Dodaj još jedan podsjetnik Podsjetnici na događaj + + Add another attendee + Uvezi događaje Izvezi događaje diff --git a/app/src/main/res/values-hu/strings.xml b/app/src/main/res/values-hu/strings.xml index 0e68b26fe..6ba16b8b4 100644 --- a/app/src/main/res/values-hu/strings.xml +++ b/app/src/main/res/values-hu/strings.xml @@ -10,6 +10,7 @@ Egyszerű bejegyzéslista Seems like you don\'t have any upcoming events. Go to today + Go to date Havi naptár @@ -97,6 +98,9 @@ Add another reminder Event reminders + + Add another attendee + Import events Export events diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index 3374a2ab6..a74e2503a 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -10,6 +10,7 @@ Lista semplice deli eventi Non c\'è nessun evento imminente. Vai a oggi + Vai alla data Calendario mensile @@ -97,6 +98,9 @@ Aggiungi un altro promemoria Promemoria eventi + + Aggiungi un partecipante + Importa eventi Esporta eventi @@ -170,12 +174,12 @@ Promemoria predefinito 3 Vista da aprire dalla lista degli eventi del widget Ultima vista - New events - Default start time - Next full hour - Default duration - Last used one - Other time + Nuovi eventi + Tempo di inizio predefinito + Prossima ora completa + Durata predefinita + L\'ultimo utilizzato + Altro periodo CalDAV diff --git a/app/src/main/res/values-iw/strings.xml b/app/src/main/res/values-iw/strings.xml index 2929d5a8f..2deca09aa 100644 --- a/app/src/main/res/values-iw/strings.xml +++ b/app/src/main/res/values-iw/strings.xml @@ -10,6 +10,7 @@ רשימת אירועים פשוטה נראה שאין לך אירועים בזמן הקרוב. Go to today + Go to date לוח השנה חודשית @@ -98,6 +99,9 @@ Add another reminder Event reminders + + Add another attendee + ייבוא אירועים Export events diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index 6febd4965..48e37bb40 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -10,6 +10,7 @@ シンプル 予定 リスト 今後の予定はありません。 今日へ移動 + Go to date カレンダー月 @@ -97,6 +98,9 @@ Add another reminder 予定のリマインダー + + Add another attendee + 予定をインポート 予定をエクスポート diff --git a/app/src/main/res/values-ko/strings.xml b/app/src/main/res/values-ko/strings.xml index 47bcc98a6..dcc3cf051 100644 --- a/app/src/main/res/values-ko/strings.xml +++ b/app/src/main/res/values-ko/strings.xml @@ -10,6 +10,7 @@ 간단한 이벤트 목록 다가올 이벤트가 없는 것 같습니다. 오늘로 이동 + Go to date 월별 달력 @@ -97,6 +98,9 @@ 다른 알림 추가 Event reminders + + Add another attendee + 이벤트들 가져 오기 이벤트들 내보내기 diff --git a/app/src/main/res/values-lt/strings.xml b/app/src/main/res/values-lt/strings.xml index be6bef018..6565d1c17 100644 --- a/app/src/main/res/values-lt/strings.xml +++ b/app/src/main/res/values-lt/strings.xml @@ -10,6 +10,7 @@ Paprastas įvykių sąrašas Atrodo jog Jūs neturite jokių įvyksiančių įvykių. Eiti į šiandieną + Go to date Mėnesio kalendorius @@ -97,6 +98,9 @@ Pridėti kitą priminimą Įvykių priminimai + + Add another attendee + Importuoti įvykius Eksportuoti įvykius diff --git a/app/src/main/res/values-nb/strings.xml b/app/src/main/res/values-nb/strings.xml index cf12f52d8..61e702c6e 100644 --- a/app/src/main/res/values-nb/strings.xml +++ b/app/src/main/res/values-nb/strings.xml @@ -10,6 +10,7 @@ Enkel hendelsesliste Ser ut som du ikke har noen kommende hendelser. Gå til idag + Go to date Månedskalender @@ -97,6 +98,9 @@ Legg til en annen påminnelse Hendelsespåminnelser + + Add another attendee + Importer hendelser Eksporter hendelser diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml index 1ef91db9c..90b682ce3 100644 --- a/app/src/main/res/values-nl/strings.xml +++ b/app/src/main/res/values-nl/strings.xml @@ -10,6 +10,7 @@ Lijst Niets gepland. Naar vandaag + Naar datum Maandweergave @@ -97,6 +98,9 @@ Herinnering toevoegen Herinneringen + + Persoon uitnodigen + Afspraken importeren Afspraken exporteren diff --git a/app/src/main/res/values-no/strings.xml b/app/src/main/res/values-no/strings.xml index c1b1eb462..bc2d43845 100644 --- a/app/src/main/res/values-no/strings.xml +++ b/app/src/main/res/values-no/strings.xml @@ -10,6 +10,7 @@ Enkel hendelsesliste Ser ut som du ikke har noen kommende hendelser. Gå til idag + Go to date Månedskalender @@ -97,6 +98,9 @@ Legg til en annen påminnelse Event reminders + + Add another attendee + Importer hendelser Eksporter hendelser diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index cbff19fce..d42131618 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -10,6 +10,7 @@ Prosta lista wydarzeń Wygląda na to, że nie masz żadnych nadchodzących wydarzeń. Przejdź do dnia dzisiejszego + Go to date Prosty kalendarz - widok miesięczny @@ -97,6 +98,9 @@ Dodaj inne przypomnienie    Przypomnienia + + Add another attendee + Importuj wydarzenia Eksportuj wydarzenia diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index 297124ae1..23d15b340 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -10,6 +10,7 @@ Lista de eventos Parece que você não tem próximos eventos. Ir para hoje + Go to date Calendário mensal @@ -97,6 +98,9 @@ Adicionar outro lembrete Lembretes de eventos + + Add another attendee + Importar eventos Exportar eventos diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml index d641c51ae..7c94ba846 100644 --- a/app/src/main/res/values-pt/strings.xml +++ b/app/src/main/res/values-pt/strings.xml @@ -10,6 +10,7 @@ Lista de eventos Parece que você não tem eventos para breve Ir para hoje + Go to date Calendário mensal @@ -97,6 +98,9 @@ Adicionar outro lembrete Lembretes para eventos + + Add another attendee + Importar eventos Exportar eventos diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index e38463045..8027936fc 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -10,6 +10,7 @@ Список событий Похоже, нет предстоящих событий. Сегодня + Go to date Календарь на месяц @@ -97,6 +98,9 @@ Добавить ещё одно напоминание Напоминания о событиях + + Add another attendee + Импорт событий Экспорт событий diff --git a/app/src/main/res/values-sk/strings.xml b/app/src/main/res/values-sk/strings.xml index 81487f4e9..5bfab56f0 100644 --- a/app/src/main/res/values-sk/strings.xml +++ b/app/src/main/res/values-sk/strings.xml @@ -10,6 +10,7 @@ Jednoduchý zoznam Nemáte žiadne naplánované udalosti. Prejsť na dnešok + Prejsť na dátum Kalendár mesačný @@ -97,6 +98,9 @@ Pridať ďalšiu pripomienku Pripomienky udalostí + + Pridať ďalšieho účastníka + Importovať udalosti Exportovať udalosti @@ -224,7 +228,7 @@ - Jednoduchý kalendár s udalosťami, upraviteľným widgetom a bez reklám. + Offline kalendár pre vaše udalosti bez reklám, rešpektujúca vaše súkromie. Jednoduchý kalendár s možnosťou CalDAV synchronizácie. Viete si jednoducho vytvoriť opakujúce sa udalosti s pripomienkami, alebo zobraziť čísla týždňov. diff --git a/app/src/main/res/values-sv/strings.xml b/app/src/main/res/values-sv/strings.xml index f7e1eb566..d706923a5 100644 --- a/app/src/main/res/values-sv/strings.xml +++ b/app/src/main/res/values-sv/strings.xml @@ -10,6 +10,7 @@ Händelselista Det verkar som att du inte har några kommande händelser. Gå till idag + Gå till datum Kalender månadsvis @@ -97,6 +98,9 @@ Lägg till en annan påminnelse Händelsepåminnelser + + Add another attendee + Importera händelser Exportera händelser @@ -136,13 +140,13 @@ Nationella helgdagar Religiösa helgdagar Helgdagarna har importerats till händelsetypen \"Helgdagar\" - Vissa händelser kunde inte importeras - Helgdagar kunde inte importeras + Importen av vissa händelser misslyckades + Importen av helgdagarna misslyckades Hantera händelsetyper - Starta veckovydagen vid - Sluta veckovydagen vid + Dagen börjar + Dagen slutar Visa veckonummer Vibrera vid påminnelseaviseringar Påminnelseljud @@ -170,12 +174,12 @@ Standardpåminnelse 3 Vy som öppnas från händelselistwidgeten Senaste vy - New events - Default start time - Next full hour - Default duration - Last used one - Other time + Nya händelser + Standardstarttid + Nästa heltimme + Standardvaraktighet + Senast använda + Annan tid CalDAV diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index fe67ecd8a..8974088ad 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -10,6 +10,7 @@ Basit etkinlik listesi Yaklaşan etkinlikleriniz yok gibi görünüyor. Bugüne git + Go to date Aylık takvim @@ -97,6 +98,9 @@ Başka hatırlatma ekle Etkinlik hatırlatıcılar + + Add another attendee + Etkinlikleri içe aktar Etkinlikleri dışa aktar @@ -170,12 +174,12 @@ Varsayılan hatırlatıcı 3 Etkinlik listesi widget\'ından açılacak görünüm Son görünüm - New events - Default start time - Next full hour - Default duration - Last used one - Other time + Yeni etkinlikler + Varsayılan başlangıç zamanı + Gelecek tam saat + Varsayılan süre + Son kullanılan + Başka zaman CalDAV @@ -189,7 +193,7 @@ Senkronize ediliyor… Senkronizasyon tamamlandı Farklı bir renk seçin (yalnızca yerel olarak uygulanabilir) - You are not allowed to write in the selected calendar + Seçili takvime yazmanıza izin verilmiyor @@ -223,7 +227,7 @@ - An offline calendar for your events without ads, respecting your privacy. + Etkinlikleriniz için reklamsız, gizliliğinizi önemseyen bir çevrimdışı takvim. İsteğe bağlı CalDAV senkronizasyonu ile basit bir takvim. Kolayca tekrarlanan etkinlikler oluşturabilir ve hatırlatıcılar ayarlayabilir, ayrıca hafta sayılarını görüntüleyebilirsiniz. @@ -235,7 +239,7 @@ Kişiler izni yalnızca doğum günlerini ve yıldönümlerini içe aktarırken kullanılır. - Bu uygulama, daha büyük bir uygulama serisinden sadece bir parça. Geri kalanı http://www.simplemobiletools.com adresinde bulabilirsiniz + Bu uygulama, daha büyük bir uygulama serisinden sadece bir parça. Geri kalanı http://www.simplemobiletools.com adresinde bulabilirsiniz. 月曆 @@ -97,6 +98,9 @@ 新增另一個提醒 活動提醒 + + Add another attendee + 匯入活動 匯出活動 @@ -170,12 +174,12 @@ 預設提醒3 從活動列表小工具開啟的檢視畫面 最後的檢視畫面 - New events - Default start time - Next full hour - Default duration - Last used one - Other time + 新活動 + 預設開始時間 + 整整下個小時 + 預設持續時間 + 最後使用 + 其他時間 CalDAV diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml index 7cd978d17..bdb6678fe 100644 --- a/app/src/main/res/values/dimens.xml +++ b/app/src/main/res/values/dimens.xml @@ -28,4 +28,6 @@ 4dp 100dp + + 40dp diff --git a/app/src/main/res/values/donottranslate.xml b/app/src/main/res/values/donottranslate.xml index ab34a38ae..e2fbb264c 100644 --- a/app/src/main/res/values/donottranslate.xml +++ b/app/src/main/res/values/donottranslate.xml @@ -2,6 +2,10 @@ + + Allow setting default start time/duration/event type for new events\n + Allow exporting/importing settings + Allow setting default event reminders, not always reuse the last events\' ones Allow changing the app launcher icon color\n diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index bfb4a1285..d76ea886e 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -10,6 +10,7 @@ Simple event list Seems like you don\'t have any upcoming events. Go to today + Go to date Calendar monthly @@ -97,6 +98,9 @@ Add another reminder Event reminders + + Add another attendee + Import events Export events @@ -224,7 +228,7 @@ - Offline kalendár pre vaše udalosti bez reklám, rešpektujúca vaše súkromie. + An offline calendar for your events without ads, respecting your privacy. A simple calendar with optional CalDAV synchronization. You can easily create recurring events and setup reminders, it can also display week numbers. diff --git a/build.gradle b/build.gradle index 0be76662a..c2453164b 100644 --- a/build.gradle +++ b/build.gradle @@ -6,11 +6,13 @@ buildscript { repositories { google() jcenter() + maven { url "https://plugins.gradle.org/m2" } } dependencies { - classpath 'com.android.tools.build:gradle:3.3.1' + classpath 'com.android.tools.build:gradle:3.3.2' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + classpath "de.timfreiheit.resourceplaceholders:placeholders:0.3" // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files diff --git a/fastlane/metadata/android/en-US/images/featureGraphic.png b/fastlane/metadata/android/en-US/images/featureGraphic.png index e328550e4..faa9471fe 100644 Binary files a/fastlane/metadata/android/en-US/images/featureGraphic.png and b/fastlane/metadata/android/en-US/images/featureGraphic.png differ diff --git a/fastlane/metadata/android/en-US/images/sevenInchScreenshots/tablet-7.png b/fastlane/metadata/android/en-US/images/sevenInchScreenshots/tablet-7.png index 81571cbde..df6f6ea8c 100644 Binary files a/fastlane/metadata/android/en-US/images/sevenInchScreenshots/tablet-7.png and b/fastlane/metadata/android/en-US/images/sevenInchScreenshots/tablet-7.png differ diff --git a/fastlane/metadata/android/en-US/images/tenInchScreenshots/tablet-10.png b/fastlane/metadata/android/en-US/images/tenInchScreenshots/tablet-10.png index fb53897ac..34d04eaab 100644 Binary files a/fastlane/metadata/android/en-US/images/tenInchScreenshots/tablet-10.png and b/fastlane/metadata/android/en-US/images/tenInchScreenshots/tablet-10.png differ