diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/activities/EventActivity.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/activities/EventActivity.kt index c27b1b2a7..655ba0d49 100644 --- a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/activities/EventActivity.kt +++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/activities/EventActivity.kt @@ -77,6 +77,7 @@ class EventActivity : SimpleActivity() { private var mOriginalTimeZone = DateTimeZone.getDefault().id private var mOriginalStartTS = 0L private var mOriginalEndTS = 0L + private var mIsNewEvent = true private lateinit var mEventStartDateTime: DateTime private lateinit var mEventEndDateTime: DateTime @@ -353,6 +354,9 @@ class EventActivity : SimpleActivity() { putLong(EVENT_TYPE_ID, mEventTypeId) putInt(EVENT_CALENDAR_ID, mEventCalendarId) + putBoolean(IS_NEW_EVENT, mIsNewEvent) + putLong(ORIGINAL_START_TS, mOriginalStartTS) + putLong(ORIGINAL_END_TS, mOriginalEndTS) } } @@ -389,6 +393,9 @@ class EventActivity : SimpleActivity() { mEventTypeId = getLong(EVENT_TYPE_ID) mEventCalendarId = getInt(EVENT_CALENDAR_ID) + mIsNewEvent = getBoolean(IS_NEW_EVENT) + mOriginalStartTS = getLong(ORIGINAL_START_TS) + mOriginalEndTS = getLong(ORIGINAL_END_TS) } checkRepeatTexts(mRepeatInterval) @@ -397,6 +404,7 @@ class EventActivity : SimpleActivity() { updateEventType() updateCalDAVCalendar() checkAttendees() + updateActionBarTitle() } override fun onActivityResult(requestCode: Int, resultCode: Int, resultData: Intent?) { @@ -420,6 +428,7 @@ class EventActivity : SimpleActivity() { } private fun setupEditEvent() { + mIsNewEvent = false val realStart = if (mEventOccurrenceTS == 0L) mEvent.startTS else mEventOccurrenceTS val duration = mEvent.endTS - mEvent.startTS mOriginalStartTS = realStart @@ -1105,15 +1114,15 @@ class EventActivity : SimpleActivity() { val reminders = getReminders() if (!event_all_day.isChecked) { - if (reminders.getOrNull(2)?.minutes ?: 0 < -1) { + if ((reminders.getOrNull(2)?.minutes ?: 0) < -1) { reminders.removeAt(2) } - if (reminders.getOrNull(1)?.minutes ?: 0 < -1) { + if ((reminders.getOrNull(1)?.minutes ?: 0) < -1) { reminders.removeAt(1) } - if (reminders.getOrNull(0)?.minutes ?: 0 < -1) { + if ((reminders.getOrNull(0)?.minutes ?: 0) < -1) { reminders.removeAt(0) } } @@ -1701,4 +1710,12 @@ class EventActivity : SimpleActivity() { it.applyColorFilter(textColor) } } + + private fun updateActionBarTitle() { + if (mIsNewEvent) { + updateActionBarTitle(getString(R.string.new_event)) + } else { + updateActionBarTitle(getString(R.string.edit_event)) + } + } } diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/activities/TaskActivity.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/activities/TaskActivity.kt index c86b28d64..c27e66858 100644 --- a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/activities/TaskActivity.kt +++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/activities/TaskActivity.kt @@ -11,25 +11,28 @@ import android.view.WindowManager import androidx.core.app.NotificationManagerCompat import androidx.core.content.ContextCompat import com.simplemobiletools.calendar.pro.R -import com.simplemobiletools.calendar.pro.dialogs.ReminderWarningDialog -import com.simplemobiletools.calendar.pro.dialogs.SelectEventTypeDialog +import com.simplemobiletools.calendar.pro.dialogs.* import com.simplemobiletools.calendar.pro.extensions.* import com.simplemobiletools.calendar.pro.helpers.* import com.simplemobiletools.calendar.pro.helpers.Formatter 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.dialogs.ConfirmationAdvancedDialog import com.simplemobiletools.commons.dialogs.ConfirmationDialog +import com.simplemobiletools.commons.dialogs.RadioGroupDialog import com.simplemobiletools.commons.extensions.* -import com.simplemobiletools.commons.helpers.ensureBackgroundThread +import com.simplemobiletools.commons.helpers.* +import com.simplemobiletools.commons.models.RadioItem import kotlinx.android.synthetic.main.activity_task.* import org.joda.time.DateTime import java.util.* +import kotlin.math.pow class TaskActivity : SimpleActivity() { private var mEventTypeId = REGULAR_EVENT_TYPE_ID private lateinit var mTaskDateTime: DateTime private lateinit var mTask: Event - private var mIsAllDayEvent = false private var mReminder1Minutes = REMINDER_OFF private var mReminder2Minutes = REMINDER_OFF @@ -37,6 +40,14 @@ class TaskActivity : SimpleActivity() { private var mReminder1Type = REMINDER_NOTIFICATION private var mReminder2Type = REMINDER_NOTIFICATION private var mReminder3Type = REMINDER_NOTIFICATION + private var mRepeatInterval = 0 + private var mRepeatLimit = 0L + private var mRepeatRule = 0 + private var mTaskOccurrenceTS = 0L + private var mOriginalStartTS = 0L + private var mTaskCompleted = false + private var mLastSavePromptTS = 0L + private var mIsNewTask = true override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -57,9 +68,11 @@ class TaskActivity : SimpleActivity() { return@ensureBackgroundThread } + val storedEventTypes = eventTypesDB.getEventTypes().toMutableList() as ArrayList + val localEventType = storedEventTypes.firstOrNull { it.id == config.lastUsedLocalEventTypeId } runOnUiThread { if (!isDestroyed && !isFinishing) { - gotTask(savedInstanceState, task) + gotTask(savedInstanceState, localEventType, task) } } } @@ -78,7 +91,7 @@ class TaskActivity : SimpleActivity() { override fun onOptionsItemSelected(item: MenuItem): Boolean { when (item.itemId) { - R.id.save -> saveTask() + R.id.save -> saveCurrentTask() R.id.delete -> deleteTask() R.id.duplicate -> duplicateTask() else -> return super.onOptionsItemSelected(item) @@ -86,6 +99,49 @@ class TaskActivity : SimpleActivity() { return true } + private fun isTaskChanged(): Boolean { + if (!this::mTask.isInitialized) { + return false + } + + val newStartTS: Long = mTaskDateTime.seconds() + val hasTimeChanged = if (mOriginalStartTS == 0L) { + mTask.startTS != newStartTS + } else { + mOriginalStartTS != newStartTS + } + + val reminders = getReminders() + val originalReminders = mTask.getReminders() + if (task_title.text.toString() != mTask.title || + task_description.text.toString() != mTask.description || + reminders != originalReminders || + mRepeatInterval != mTask.repeatInterval || + mRepeatRule != mTask.repeatRule || + mEventTypeId != mTask.eventType || + hasTimeChanged + ) { + return true + } + + return false + } + + override fun onBackPressed() { + if (System.currentTimeMillis() - mLastSavePromptTS > SAVE_DISCARD_PROMPT_INTERVAL && isTaskChanged()) { + mLastSavePromptTS = System.currentTimeMillis() + ConfirmationAdvancedDialog(this, "", R.string.save_before_closing, R.string.save, R.string.discard) { + if (it) { + saveCurrentTask() + } else { + super.onBackPressed() + } + } + } else { + super.onBackPressed() + } + } + override fun onSaveInstanceState(outState: Bundle) { super.onSaveInstanceState(outState) if (!::mTask.isInitialized) { @@ -100,6 +156,14 @@ class TaskActivity : SimpleActivity() { putInt(REMINDER_1_MINUTES, mReminder1Minutes) putInt(REMINDER_2_MINUTES, mReminder2Minutes) putInt(REMINDER_3_MINUTES, mReminder3Minutes) + + putInt(REPEAT_INTERVAL, mRepeatInterval) + putInt(REPEAT_RULE, mRepeatRule) + putLong(REPEAT_LIMIT, mRepeatLimit) + + putLong(EVENT_TYPE_ID, mEventTypeId) + putBoolean(IS_NEW_EVENT, mIsNewTask) + putLong(ORIGINAL_START_TS, mOriginalStartTS) } } @@ -119,19 +183,34 @@ class TaskActivity : SimpleActivity() { mReminder1Minutes = getInt(REMINDER_1_MINUTES) mReminder2Minutes = getInt(REMINDER_2_MINUTES) mReminder3Minutes = getInt(REMINDER_3_MINUTES) + + mRepeatInterval = getInt(REPEAT_INTERVAL) + mRepeatRule = getInt(REPEAT_RULE) + mRepeatLimit = getLong(REPEAT_LIMIT) + mEventTypeId = getLong(EVENT_TYPE_ID) + mIsNewTask = getBoolean(IS_NEW_EVENT) + mOriginalStartTS = getLong(ORIGINAL_START_TS) } updateEventType() - updateDateText() - updateTimeText() + updateTexts() + setupMarkCompleteButton() + checkRepeatTexts(mRepeatInterval) + checkRepeatRule() + updateActionBarTitle() } - private fun gotTask(savedInstanceState: Bundle?, task: Event?) { + private fun gotTask(savedInstanceState: Bundle?, localEventType: EventType?, task: Event?) { + if (localEventType == null) { + config.lastUsedLocalEventTypeId = REGULAR_EVENT_TYPE_ID + } + mEventTypeId = if (config.defaultEventTypeId == -1L) config.lastUsedLocalEventTypeId else config.defaultEventTypeId if (task != null) { mTask = task - + mTaskOccurrenceTS = intent.getLongExtra(EVENT_OCCURRENCE_TS, 0L) + mTaskCompleted = intent.getBooleanExtra(IS_TASK_COMPLETED, false) if (intent.getBooleanExtra(IS_DUPLICATE_INTENT, false)) { mTask.id = null updateActionBarTitle(getString(R.string.new_task)) @@ -160,9 +239,12 @@ class TaskActivity : SimpleActivity() { task_date.setOnClickListener { setupDate() } task_time.setOnClickListener { setupTime() } - event_type_holder.setOnClickListener { showEventTypeDialog() } + task_type_holder.setOnClickListener { showEventTypeDialog() } + task_repetition.setOnClickListener { showRepeatIntervalDialog() } + task_repetition_rule_holder.setOnClickListener { showRepetitionRuleDialog() } + task_repetition_limit_holder.setOnClickListener { showRepetitionTypePicker() } - event_reminder_1.setOnClickListener { + task_reminder_1.setOnClickListener { handleNotificationAvailability { if (config.wasAlarmWarningShown) { showReminder1Dialog() @@ -175,19 +257,20 @@ class TaskActivity : SimpleActivity() { } } - event_reminder_2.setOnClickListener { showReminder2Dialog() } - event_reminder_3.setOnClickListener { showReminder3Dialog() } + task_reminder_2.setOnClickListener { showReminder2Dialog() } + task_reminder_3.setOnClickListener { showReminder3Dialog() } if (savedInstanceState == null) { updateEventType() - updateDateText() - updateTimeText() - updateReminderTexts() + updateTexts() } } private fun setupEditTask() { - mTaskDateTime = Formatter.getDateTimeFromTS(mTask.startTS) + mIsNewTask = false + val realStart = if (mTaskOccurrenceTS == 0L) mTask.startTS else mTaskOccurrenceTS + mOriginalStartTS = realStart + mTaskDateTime = Formatter.getDateTimeFromTS(realStart) window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN) updateActionBarTitle(getString(R.string.edit_task)) @@ -198,12 +281,16 @@ class TaskActivity : SimpleActivity() { mReminder1Type = mTask.reminder1Type mReminder2Type = mTask.reminder2Type mReminder3Type = mTask.reminder3Type + mRepeatInterval = mTask.repeatInterval + mRepeatLimit = mTask.repeatLimit + mRepeatRule = mTask.repeatRule task_title.setText(mTask.title) task_description.setText(mTask.description) task_all_day.isChecked = mTask.getIsAllDay() toggleAllDay(mTask.getIsAllDay()) setupMarkCompleteButton() + checkRepeatTexts(mRepeatInterval) } private fun setupNewTask() { @@ -214,6 +301,33 @@ class TaskActivity : SimpleActivity() { window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE) task_title.requestFocus() updateActionBarTitle(getString(R.string.new_task)) + + mTask.apply { + this.startTS = mTaskDateTime.seconds() + this.endTS = mTaskDateTime.seconds() + reminder1Minutes = mReminder1Minutes + reminder1Type = mReminder1Type + reminder2Minutes = mReminder2Minutes + reminder2Type = mReminder2Type + reminder3Minutes = mReminder3Minutes + reminder3Type = mReminder3Type + eventType = mEventTypeId + } + } + + private fun saveCurrentTask() { + if (config.wasAlarmWarningShown || (mReminder1Minutes == REMINDER_OFF && mReminder2Minutes == REMINDER_OFF && mReminder3Minutes == REMINDER_OFF)) { + ensureBackgroundThread { + saveTask() + } + } else { + ReminderWarningDialog(this) { + config.wasAlarmWarningShown = true + ensureBackgroundThread { + saveTask() + } + } + } } private fun saveTask() { @@ -226,6 +340,7 @@ class TaskActivity : SimpleActivity() { return } + val wasRepeatable = mTask.repeatInterval > 0 val reminders = getReminders() if (!task_all_day.isChecked) { @@ -260,21 +375,37 @@ class TaskActivity : SimpleActivity() { endTS = startTS title = newTitle description = task_description.value + + // migrate completed task to the new completed tasks db + if (!wasRepeatable && mTask.isTaskCompleted()) { + mTask.flags = mTask.flags.removeBit(FLAG_TASK_COMPLETED) + ensureBackgroundThread { + updateTaskCompletion(copy(startTS = mOriginalStartTS), true) + } + } flags = mTask.flags.addBitIf(task_all_day.isChecked, FLAG_ALL_DAY) lastUpdated = System.currentTimeMillis() eventType = mEventTypeId type = TYPE_TASK - reminder1Minutes = mReminder1Minutes + reminder1Minutes = reminder1.minutes reminder1Type = mReminder1Type - reminder2Minutes = mReminder2Minutes + reminder2Minutes = reminder2.minutes reminder2Type = mReminder2Type - reminder3Minutes = mReminder3Minutes + reminder3Minutes = reminder3.minutes reminder3Type = mReminder3Type + + repeatInterval = mRepeatInterval + repeatLimit = if (repeatInterval == 0) 0 else mRepeatLimit + repeatRule = mRepeatRule } - ensureBackgroundThread { - EventsHelper(this).insertTask(mTask, true) { + storeTask(wasRepeatable) + } + + private fun storeTask(wasRepeatable: Boolean) { + if (mTask.id == null) { + eventsHelper.insertTask(mTask, true) { hideKeyboard() if (DateTime.now().isAfter(mTaskDateTime.millis)) { @@ -285,13 +416,76 @@ class TaskActivity : SimpleActivity() { finish() } + } else { + if (mRepeatInterval > 0 && wasRepeatable) { + runOnUiThread { + showEditRepeatingTaskDialog() + } + } else { + hideKeyboard() + eventsHelper.updateEvent(mTask, updateAtCalDAV = false, showToasts = true) { + finish() + } + } + } + } + + private fun showEditRepeatingTaskDialog() { + EditRepeatingEventDialog(this, isTask = true) { + hideKeyboard() + when (it) { + 0 -> { + ensureBackgroundThread { + eventsHelper.addEventRepetitionException(mTask.id!!, mTaskOccurrenceTS, true) + mTask.apply { + parentId = id!!.toLong() + id = null + repeatRule = 0 + repeatInterval = 0 + repeatLimit = 0 + } + + eventsHelper.insertTask(mTask, showToasts = true) { + finish() + } + } + } + 1 -> { + ensureBackgroundThread { + eventsHelper.addEventRepeatLimit(mTask.id!!, mTaskOccurrenceTS) + mTask.apply { + id = null + } + + eventsHelper.insertTask(mTask, showToasts = true) { + finish() + } + } + } + 2 -> { + ensureBackgroundThread { + eventsHelper.addEventRepeatLimit(mTask.id!!, mTaskOccurrenceTS) + eventsHelper.updateEvent(mTask, updateAtCalDAV = false, showToasts = true) { + finish() + } + } + } + } } } private fun deleteTask() { - ConfirmationDialog(this) { + if (mTask.id == null) { + return + } + + DeleteEventDialog(this, arrayListOf(mTask.id!!), mTask.repeatInterval > 0, isTask = true) { ensureBackgroundThread { - eventsHelper.deleteEvent(mTask.id!!, false) + when (it) { + DELETE_SELECTED_OCCURRENCE -> eventsHelper.addEventRepetitionException(mTask.id!!, mTaskOccurrenceTS, true) + DELETE_FUTURE_OCCURRENCES -> eventsHelper.addEventRepeatLimit(mTask.id!!, mTaskOccurrenceTS) + DELETE_ALL_OCCURRENCES -> eventsHelper.deleteEvent(mTask.id!!, true) + } runOnUiThread { hideKeyboard() @@ -340,6 +534,7 @@ class TaskActivity : SimpleActivity() { private fun dateSet(year: Int, month: Int, day: Int) { mTaskDateTime = mTaskDateTime.withDate(year, month + 1, day) updateDateText() + checkRepeatRule() } private fun timeSet(hours: Int, minutes: Int) { @@ -347,6 +542,28 @@ class TaskActivity : SimpleActivity() { updateTimeText() } + private fun updateTexts() { + updateDateText() + updateTimeText() + updateReminderTexts() + updateRepetitionText() + } + + private fun checkRepeatRule() { + if (mRepeatInterval.isXWeeklyRepetition()) { + val day = mRepeatRule + if (day == MONDAY_BIT || day == TUESDAY_BIT || day == WEDNESDAY_BIT || day == THURSDAY_BIT || day == FRIDAY_BIT || day == SATURDAY_BIT || day == SUNDAY_BIT) { + setRepeatRule(2.0.pow((mTaskDateTime.dayOfWeek - 1).toDouble()).toInt()) + } + } else if (mRepeatInterval.isXMonthlyRepetition() || mRepeatInterval.isXYearlyRepetition()) { + if (mRepeatRule == REPEAT_LAST_DAY && !isLastDayOfTheMonth()) { + mRepeatRule = REPEAT_SAME_DAY + } + checkRepetitionRuleText() + } + } + + private fun updateDateText() { task_date.text = Formatter.getDate(this, mTaskDateTime) } @@ -363,7 +580,18 @@ class TaskActivity : SimpleActivity() { private fun setupMarkCompleteButton() { toggle_mark_complete.setOnClickListener { toggleCompletion() } toggle_mark_complete.beVisibleIf(mTask.id != null) - if (mTask.isTaskCompleted()) { + updateTaskCompletedButton() + ensureBackgroundThread { + // the stored value might be incorrect so update it (e.g. user completed the task via notification action before editing) + mTaskCompleted = isTaskCompleted(mTask.copy(startTS = mOriginalStartTS)) + runOnUiThread { + updateTaskCompletedButton() + } + } + } + + private fun updateTaskCompletedButton() { + if (mTaskCompleted) { toggle_mark_complete.background = ContextCompat.getDrawable(this, R.drawable.button_background_stroke) toggle_mark_complete.setText(R.string.mark_incomplete) toggle_mark_complete.setTextColor(getProperTextColor()) @@ -378,14 +606,9 @@ class TaskActivity : SimpleActivity() { } private fun toggleCompletion() { - if (mTask.isTaskCompleted()) { - mTask.flags = mTask.flags.removeBit(FLAG_TASK_COMPLETED) - } else { - mTask.flags = mTask.flags or FLAG_TASK_COMPLETED - } - ensureBackgroundThread { - eventsDB.updateTaskCompletion(mTask.id!!, mTask.flags) + val task = mTask.copy(startTS = mOriginalStartTS) + updateTaskCompletion(task, completed = !mTaskCompleted) hideKeyboard() finish() } @@ -398,12 +621,12 @@ class TaskActivity : SimpleActivity() { } private fun updateReminder1Text() { - event_reminder_1.text = getFormattedMinutes(mReminder1Minutes) + task_reminder_1.text = getFormattedMinutes(mReminder1Minutes) } private fun updateReminder2Text() { - event_reminder_2.apply { - beGoneIf(event_reminder_2.isGone() && mReminder1Minutes == REMINDER_OFF) + task_reminder_2.apply { + beGoneIf(task_reminder_2.isGone() && mReminder1Minutes == REMINDER_OFF) if (mReminder2Minutes == REMINDER_OFF) { text = resources.getString(R.string.add_another_reminder) alpha = 0.4f @@ -415,8 +638,8 @@ class TaskActivity : SimpleActivity() { } private fun updateReminder3Text() { - event_reminder_3.apply { - beGoneIf(event_reminder_3.isGone() && (mReminder2Minutes == REMINDER_OFF || mReminder1Minutes == REMINDER_OFF)) + task_reminder_3.apply { + beGoneIf(task_reminder_3.isGone() && (mReminder2Minutes == REMINDER_OFF || mReminder1Minutes == REMINDER_OFF)) if (mReminder3Minutes == REMINDER_OFF) { text = resources.getString(R.string.add_another_reminder) alpha = 0.4f @@ -438,21 +661,21 @@ class TaskActivity : SimpleActivity() { } private fun showReminder1Dialog() { - showPickSecondsDialogHelper(mReminder1Minutes, showDuringDayOption = mIsAllDayEvent) { + showPickSecondsDialogHelper(mReminder1Minutes) { mReminder1Minutes = if (it == -1 || it == 0) it else it / 60 updateReminderTexts() } } private fun showReminder2Dialog() { - showPickSecondsDialogHelper(mReminder2Minutes, showDuringDayOption = mIsAllDayEvent) { + showPickSecondsDialogHelper(mReminder2Minutes) { mReminder2Minutes = if (it == -1 || it == 0) it else it / 60 updateReminderTexts() } } private fun showReminder3Dialog() { - showPickSecondsDialogHelper(mReminder3Minutes, showDuringDayOption = mIsAllDayEvent) { + showPickSecondsDialogHelper(mReminder3Minutes) { mReminder3Minutes = if (it == -1 || it == 0) it else it / 60 updateReminderTexts() } @@ -488,8 +711,8 @@ class TaskActivity : SimpleActivity() { val eventType = eventTypesDB.getEventTypeWithId(mEventTypeId) if (eventType != null) { runOnUiThread { - event_type.text = eventType.title - event_type_color.setFillWithStroke(eventType.color, getProperBackgroundColor()) + task_type.text = eventType.title + task_type_color.setFillWithStroke(eventType.color, getProperBackgroundColor()) } } } @@ -499,9 +722,234 @@ class TaskActivity : SimpleActivity() { updateTextColors(task_scrollview) val textColor = getProperTextColor() arrayOf( - task_time_image, event_reminder_image, event_type_image + task_time_image, task_reminder_image, task_type_image ).forEach { it.applyColorFilter(textColor) } } + + + private fun showRepeatIntervalDialog() { + showEventRepeatIntervalDialog(mRepeatInterval) { + setRepeatInterval(it) + } + } + + private fun setRepeatInterval(interval: Int) { + mRepeatInterval = interval + updateRepetitionText() + checkRepeatTexts(interval) + + when { + mRepeatInterval.isXWeeklyRepetition() -> setRepeatRule(2.0.pow((mTaskDateTime.dayOfWeek - 1).toDouble()).toInt()) + mRepeatInterval.isXMonthlyRepetition() -> setRepeatRule(REPEAT_SAME_DAY) + mRepeatInterval.isXYearlyRepetition() -> setRepeatRule(REPEAT_SAME_DAY) + } + } + + private fun checkRepeatTexts(limit: Int) { + task_repetition_limit_holder.beGoneIf(limit == 0) + checkRepetitionLimitText() + + task_repetition_rule_holder.beVisibleIf(mRepeatInterval.isXWeeklyRepetition() || mRepeatInterval.isXMonthlyRepetition() || mRepeatInterval.isXYearlyRepetition()) + checkRepetitionRuleText() + } + + private fun showRepetitionTypePicker() { + hideKeyboard() + RepeatLimitTypePickerDialog(this, mRepeatLimit, mTaskDateTime.seconds()) { + setRepeatLimit(it) + } + } + + private fun setRepeatLimit(limit: Long) { + mRepeatLimit = limit + checkRepetitionLimitText() + } + + private fun checkRepetitionLimitText() { + task_repetition_limit.text = when { + mRepeatLimit == 0L -> { + task_repetition_limit_label.text = getString(R.string.repeat) + resources.getString(R.string.forever) + } + mRepeatLimit > 0 -> { + task_repetition_limit_label.text = getString(R.string.repeat_till) + val repeatLimitDateTime = Formatter.getDateTimeFromTS(mRepeatLimit) + Formatter.getFullDate(this, repeatLimitDateTime) + } + else -> { + task_repetition_limit_label.text = getString(R.string.repeat) + "${-mRepeatLimit} ${getString(R.string.times)}" + } + } + } + + private fun showRepetitionRuleDialog() { + hideKeyboard() + when { + mRepeatInterval.isXWeeklyRepetition() -> RepeatRuleWeeklyDialog(this, mRepeatRule) { + setRepeatRule(it) + } + mRepeatInterval.isXMonthlyRepetition() -> { + val items = getAvailableMonthlyRepetitionRules() + RadioGroupDialog(this, items, mRepeatRule) { + setRepeatRule(it as Int) + } + } + mRepeatInterval.isXYearlyRepetition() -> { + val items = getAvailableYearlyRepetitionRules() + RadioGroupDialog(this, items, mRepeatRule) { + setRepeatRule(it as Int) + } + } + } + } + + private fun getAvailableMonthlyRepetitionRules(): ArrayList { + val items = arrayListOf(RadioItem(REPEAT_SAME_DAY, getString(R.string.repeat_on_the_same_day_monthly))) + + items.add(RadioItem(REPEAT_ORDER_WEEKDAY, getRepeatXthDayString(true, REPEAT_ORDER_WEEKDAY))) + if (isLastWeekDayOfMonth()) { + items.add(RadioItem(REPEAT_ORDER_WEEKDAY_USE_LAST, getRepeatXthDayString(true, REPEAT_ORDER_WEEKDAY_USE_LAST))) + } + + if (isLastDayOfTheMonth()) { + items.add(RadioItem(REPEAT_LAST_DAY, getString(R.string.repeat_on_the_last_day_monthly))) + } + return items + } + + private fun getAvailableYearlyRepetitionRules(): ArrayList { + val items = arrayListOf(RadioItem(REPEAT_SAME_DAY, getString(R.string.repeat_on_the_same_day_yearly))) + + items.add(RadioItem(REPEAT_ORDER_WEEKDAY, getRepeatXthDayInMonthString(true, REPEAT_ORDER_WEEKDAY))) + if (isLastWeekDayOfMonth()) { + items.add(RadioItem(REPEAT_ORDER_WEEKDAY_USE_LAST, getRepeatXthDayInMonthString(true, REPEAT_ORDER_WEEKDAY_USE_LAST))) + } + + return items + } + + private fun isLastDayOfTheMonth() = mTaskDateTime.dayOfMonth == mTaskDateTime.dayOfMonth().withMaximumValue().dayOfMonth + + private fun isLastWeekDayOfMonth() = mTaskDateTime.monthOfYear != mTaskDateTime.plusDays(7).monthOfYear + + private fun getRepeatXthDayString(includeBase: Boolean, repeatRule: Int): String { + val dayOfWeek = mTaskDateTime.dayOfWeek + val base = getBaseString(dayOfWeek) + val order = getOrderString(repeatRule) + val dayString = getDayString(dayOfWeek) + return if (includeBase) { + "$base $order $dayString" + } else { + val everyString = getString(if (isMaleGender(mTaskDateTime.dayOfWeek)) R.string.every_m else R.string.every_f) + "$everyString $order $dayString" + } + } + + private fun getBaseString(day: Int): String { + return getString( + if (isMaleGender(day)) { + R.string.repeat_every_m + } else { + R.string.repeat_every_f + } + ) + } + + private fun isMaleGender(day: Int) = day == 1 || day == 2 || day == 4 || day == 5 + + private fun getOrderString(repeatRule: Int): String { + val dayOfMonth = mTaskDateTime.dayOfMonth + var order = (dayOfMonth - 1) / 7 + 1 + if (isLastWeekDayOfMonth() && repeatRule == REPEAT_ORDER_WEEKDAY_USE_LAST) { + order = -1 + } + + val isMale = isMaleGender(mTaskDateTime.dayOfWeek) + return getString( + when (order) { + 1 -> if (isMale) R.string.first_m else R.string.first_f + 2 -> if (isMale) R.string.second_m else R.string.second_f + 3 -> if (isMale) R.string.third_m else R.string.third_f + 4 -> if (isMale) R.string.fourth_m else R.string.fourth_f + 5 -> if (isMale) R.string.fifth_m else R.string.fifth_f + else -> if (isMale) R.string.last_m else R.string.last_f + } + ) + } + + private fun getDayString(day: Int): String { + return getString( + when (day) { + 1 -> R.string.monday_alt + 2 -> R.string.tuesday_alt + 3 -> R.string.wednesday_alt + 4 -> R.string.thursday_alt + 5 -> R.string.friday_alt + 6 -> R.string.saturday_alt + else -> R.string.sunday_alt + } + ) + } + + private fun getRepeatXthDayInMonthString(includeBase: Boolean, repeatRule: Int): String { + val weekDayString = getRepeatXthDayString(includeBase, repeatRule) + val monthString = resources.getStringArray(R.array.in_months)[mTaskDateTime.monthOfYear - 1] + return "$weekDayString $monthString" + } + + private fun setRepeatRule(rule: Int) { + mRepeatRule = rule + checkRepetitionRuleText() + if (rule == 0) { + setRepeatInterval(0) + } + } + + private fun checkRepetitionRuleText() { + when { + mRepeatInterval.isXWeeklyRepetition() -> { + task_repetition_rule.text = if (mRepeatRule == EVERY_DAY_BIT) getString(R.string.every_day) else getSelectedDaysString(mRepeatRule) + } + mRepeatInterval.isXMonthlyRepetition() -> { + val repeatString = if (mRepeatRule == REPEAT_ORDER_WEEKDAY_USE_LAST || mRepeatRule == REPEAT_ORDER_WEEKDAY) + R.string.repeat else R.string.repeat_on + + task_repetition_rule_label.text = getString(repeatString) + task_repetition_rule.text = getMonthlyRepetitionRuleText() + } + mRepeatInterval.isXYearlyRepetition() -> { + val repeatString = if (mRepeatRule == REPEAT_ORDER_WEEKDAY_USE_LAST || mRepeatRule == REPEAT_ORDER_WEEKDAY) + R.string.repeat else R.string.repeat_on + + task_repetition_rule_label.text = getString(repeatString) + task_repetition_rule.text = getYearlyRepetitionRuleText() + } + } + } + + private fun getMonthlyRepetitionRuleText() = when (mRepeatRule) { + REPEAT_SAME_DAY -> getString(R.string.the_same_day) + REPEAT_LAST_DAY -> getString(R.string.the_last_day) + else -> getRepeatXthDayString(false, mRepeatRule) + } + + private fun getYearlyRepetitionRuleText() = when (mRepeatRule) { + REPEAT_SAME_DAY -> getString(R.string.the_same_day) + else -> getRepeatXthDayInMonthString(false, mRepeatRule) + } + + private fun updateRepetitionText() { + task_repetition.text = getRepetitionText(mRepeatInterval) + } + + private fun updateActionBarTitle() { + if (mIsNewTask) { + updateActionBarTitle(getString(R.string.new_task)) + } else { + updateActionBarTitle(getString(R.string.edit_task)) + } + } } diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/adapters/EventListWidgetAdapter.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/adapters/EventListWidgetAdapter.kt index 2b018d17c..d79eb09c9 100644 --- a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/adapters/EventListWidgetAdapter.kt +++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/adapters/EventListWidgetAdapter.kt @@ -191,27 +191,27 @@ class EventListWidgetAdapter(val context: Context, val intent: Intent) : RemoteV context.eventsHelper.getEventsSync(fromTS, toTS, applyTypeFilter = true) { val listItems = ArrayList(it.size) val replaceDescription = context.config.replaceDescription - val sorted = it.sortedWith(compareBy { - if (it.getIsAllDay()) { - Formatter.getDayStartTS(Formatter.getDayCodeFromTS(it.startTS)) - 1 + val sorted = it.sortedWith(compareBy { event -> + if (event.getIsAllDay()) { + Formatter.getDayStartTS(Formatter.getDayCodeFromTS(event.startTS)) - 1 } else { - it.startTS + event.startTS } - }.thenBy { - if (it.getIsAllDay()) { - Formatter.getDayEndTS(Formatter.getDayCodeFromTS(it.endTS)) + }.thenBy { event -> + if (event.getIsAllDay()) { + Formatter.getDayEndTS(Formatter.getDayCodeFromTS(event.endTS)) } else { - it.endTS + event.endTS } - }.thenBy { it.title }.thenBy { if (replaceDescription) it.location else it.description }) + }.thenBy { event -> event.title }.thenBy { event -> if (replaceDescription) event.location else event.description }) var prevCode = "" var prevMonthLabel = "" val now = getNowSeconds() val today = Formatter.getDayTitle(context, Formatter.getDayCodeFromTS(now)) - sorted.forEach { - val code = Formatter.getDayCodeFromTS(it.startTS) + sorted.forEach { event -> + val code = Formatter.getDayCodeFromTS(event.startTS) val monthLabel = Formatter.getLongMonthYear(context, code) if (monthLabel != prevMonthLabel) { val listSectionMonth = ListSectionMonth(monthLabel) @@ -222,24 +222,24 @@ class EventListWidgetAdapter(val context: Context, val intent: Intent) : RemoteV if (code != prevCode) { val day = Formatter.getDateDayTitle(code) val isToday = day == today - val listSection = ListSectionDay(day, code, isToday, !isToday && it.startTS < now) + val listSection = ListSectionDay(day, code, isToday, !isToday && event.startTS < now) listItems.add(listSection) prevCode = code } val listEvent = ListEvent( - it.id!!, - it.startTS, - it.endTS, - it.title, - it.description, - it.getIsAllDay(), - it.color, - it.location, - it.isPastEvent, - it.repeatInterval > 0, - it.isTask(), - it.isTaskCompleted() + event.id!!, + event.startTS, + event.endTS, + event.title, + event.description, + event.getIsAllDay(), + event.color, + event.location, + event.isPastEvent, + event.repeatInterval > 0, + event.isTask(), + event.isTaskCompleted() ) listItems.add(listEvent) } 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 fec106e73..65c9e1307 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 @@ -13,14 +13,16 @@ import com.simplemobiletools.calendar.pro.helpers.Converters import com.simplemobiletools.calendar.pro.helpers.REGULAR_EVENT_TYPE_ID import com.simplemobiletools.calendar.pro.interfaces.EventTypesDao import com.simplemobiletools.calendar.pro.interfaces.EventsDao +import com.simplemobiletools.calendar.pro.interfaces.TasksDao import com.simplemobiletools.calendar.pro.interfaces.WidgetsDao import com.simplemobiletools.calendar.pro.models.Event import com.simplemobiletools.calendar.pro.models.EventType +import com.simplemobiletools.calendar.pro.models.Task import com.simplemobiletools.calendar.pro.models.Widget import com.simplemobiletools.commons.extensions.getProperPrimaryColor import java.util.concurrent.Executors -@Database(entities = [Event::class, EventType::class, Widget::class], version = 6) +@Database(entities = [Event::class, EventType::class, Widget::class, Task::class], version = 7) @TypeConverters(Converters::class) abstract class EventsDatabase : RoomDatabase() { @@ -30,6 +32,8 @@ abstract class EventsDatabase : RoomDatabase() { abstract fun WidgetsDao(): WidgetsDao + abstract fun TasksDao(): TasksDao + companion object { private var db: EventsDatabase? = null @@ -49,6 +53,7 @@ abstract class EventsDatabase : RoomDatabase() { .addMigrations(MIGRATION_3_4) .addMigrations(MIGRATION_4_5) .addMigrations(MIGRATION_5_6) + .addMigrations(MIGRATION_6_7) .build() db!!.openHelper.setWriteAheadLoggingEnabled(true) } @@ -113,5 +118,14 @@ abstract class EventsDatabase : RoomDatabase() { } } } + + private val MIGRATION_6_7 = object : Migration(6, 7) { + override fun migrate(database: SupportSQLiteDatabase) { + database.apply { + execSQL("CREATE TABLE IF NOT EXISTS `tasks` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `task_id` INTEGER NOT NULL, `start_ts` INTEGER NOT NULL, `flags` INTEGER NOT NULL)") + execSQL("CREATE UNIQUE INDEX IF NOT EXISTS `index_tasks_id_task_id` ON `tasks` (`id`, `task_id`)") + } + } + } } } diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/dialogs/DeleteEventDialog.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/dialogs/DeleteEventDialog.kt index a9b5954cd..a5aac08d0 100644 --- a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/dialogs/DeleteEventDialog.kt +++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/dialogs/DeleteEventDialog.kt @@ -11,7 +11,13 @@ import com.simplemobiletools.commons.extensions.beVisibleIf import com.simplemobiletools.commons.extensions.setupDialogStuff import kotlinx.android.synthetic.main.dialog_delete_event.view.* -class DeleteEventDialog(val activity: Activity, eventIds: List, hasRepeatableEvent: Boolean, val callback: (deleteRule: Int) -> Unit) { +class DeleteEventDialog( + val activity: Activity, + eventIds: List, + hasRepeatableEvent: Boolean, + isTask: Boolean = false, + val callback: (deleteRule: Int) -> Unit +) { val dialog: AlertDialog? init { @@ -23,16 +29,22 @@ class DeleteEventDialog(val activity: Activity, eventIds: List, hasRepeata } if (eventIds.size > 1) { - delete_event_repeat_description.text = resources.getString(R.string.selection_contains_repetition) + delete_event_repeat_description.setText(R.string.selection_contains_repetition) + } + + if (isTask) { + delete_event_repeat_description.setText(R.string.task_is_repeatable) + } else { + delete_event_repeat_description.setText(R.string.event_is_repeatable) } } dialog = AlertDialog.Builder(activity) - .setPositiveButton(R.string.yes) { dialog, which -> dialogConfirmed(view as ViewGroup) } - .setNegativeButton(R.string.no, null) - .create().apply { - activity.setupDialogStuff(view, this) - } + .setPositiveButton(R.string.yes) { dialog, which -> dialogConfirmed(view as ViewGroup) } + .setNegativeButton(R.string.no, null) + .create().apply { + activity.setupDialogStuff(view, this) + } } private fun dialogConfirmed(view: ViewGroup) { diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/dialogs/EditRepeatingEventDialog.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/dialogs/EditRepeatingEventDialog.kt index adab8087f..74f07f89c 100644 --- a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/dialogs/EditRepeatingEventDialog.kt +++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/dialogs/EditRepeatingEventDialog.kt @@ -8,22 +8,28 @@ import com.simplemobiletools.commons.extensions.hideKeyboard import com.simplemobiletools.commons.extensions.setupDialogStuff import kotlinx.android.synthetic.main.dialog_edit_repeating_event.view.* -class EditRepeatingEventDialog(val activity: SimpleActivity, val callback: (allOccurrences: Int) -> Unit) { +class EditRepeatingEventDialog(val activity: SimpleActivity, val isTask: Boolean = false, val callback: (allOccurrences: Int) -> Unit) { var dialog: AlertDialog init { val view = (activity.layoutInflater.inflate(R.layout.dialog_edit_repeating_event, null) as ViewGroup).apply { edit_repeating_event_one_only.setOnClickListener { sendResult(0) } - edit_repeating_event_this_and_future_occurences.setOnClickListener { sendResult(1)} + edit_repeating_event_this_and_future_occurences.setOnClickListener { sendResult(1) } edit_repeating_event_all_occurrences.setOnClickListener { sendResult(2) } + + if (isTask) { + edit_repeating_event_title.setText(R.string.task_is_repeatable) + } else { + edit_repeating_event_title.setText(R.string.event_is_repeatable) + } } dialog = AlertDialog.Builder(activity) - .create().apply { - activity.setupDialogStuff(view, this) { - hideKeyboard() - } + .create().apply { + activity.setupDialogStuff(view, this) { + hideKeyboard() } + } } private fun sendResult(allOccurrences: Int) { 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 90fbe1b89..42722770b 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 @@ -33,6 +33,7 @@ import com.simplemobiletools.calendar.pro.helpers.* import com.simplemobiletools.calendar.pro.helpers.Formatter import com.simplemobiletools.calendar.pro.interfaces.EventTypesDao import com.simplemobiletools.calendar.pro.interfaces.EventsDao +import com.simplemobiletools.calendar.pro.interfaces.TasksDao import com.simplemobiletools.calendar.pro.interfaces.WidgetsDao import com.simplemobiletools.calendar.pro.models.* import com.simplemobiletools.calendar.pro.receivers.CalDAVSyncReceiver @@ -51,6 +52,7 @@ val Context.config: Config get() = Config.newInstance(applicationContext) val Context.eventsDB: EventsDao get() = EventsDatabase.getInstance(applicationContext).EventsDao() val Context.eventTypesDB: EventTypesDao get() = EventsDatabase.getInstance(applicationContext).EventTypesDao() val Context.widgetsDB: WidgetsDao get() = EventsDatabase.getInstance(applicationContext).WidgetsDao() +val Context.completedTasksDB: TasksDao get() = EventsDatabase.getInstance(applicationContext).TasksDao() val Context.eventsHelper: EventsHelper get() = EventsHelper(this) val Context.calDAVHelper: CalDAVHelper get() = CalDAVHelper(this) @@ -237,6 +239,7 @@ fun Context.notifyEvent(originalEvent: Event) { val descriptionOrLocation = if (config.replaceDescription) event.location else event.description val content = "$displayedStartDate $timeRange $descriptionOrLocation".trim() ensureBackgroundThread { + if (event.isTask()) eventsHelper.updateIsTaskCompleted(event) val notification = getNotification(pendingIntent, event, content) val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager try { @@ -634,6 +637,7 @@ fun Context.editEvent(event: ListEvent) { Intent(this, getActivityToOpen(event.isTask)).apply { putExtra(EVENT_ID, event.id) putExtra(EVENT_OCCURRENCE_TS, event.startTS) + putExtra(IS_TASK_COMPLETED, event.isTaskCompleted) startActivity(this) } } @@ -653,3 +657,23 @@ fun Context.getDatesWeekDateTime(date: DateTime): String { date.withZone(DateTimeZone.UTC).toString() } } + +fun Context.isTaskCompleted(event: Event): Boolean { + if (event.id == null) return false + val originalEvent = eventsDB.getTaskWithId(event.id!!) + val task = completedTasksDB.getTaskWithIdAndTs(event.id!!, event.startTS) + return originalEvent?.isTaskCompleted() == true || task?.isTaskCompleted() == true +} + +fun Context.updateTaskCompletion(event: Event, completed: Boolean) { + if (completed) { + event.flags = event.flags or FLAG_TASK_COMPLETED + val task = Task(null, event.id!!, event.startTS, event.flags) + completedTasksDB.insertOrUpdate(task) + } else { + event.flags = event.flags.removeBit(FLAG_TASK_COMPLETED) + completedTasksDB.deleteTaskWithIdAndTs(event.id!!, event.startTS) + } + // mark event as "incomplete" in the main events db + eventsDB.updateTaskCompletion(event.id!!, event.flags.removeBit(FLAG_TASK_COMPLETED)) +} 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 9b23f70af..9fa293f48 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 @@ -17,13 +17,11 @@ import com.simplemobiletools.calendar.pro.extensions.eventsHelper import com.simplemobiletools.calendar.pro.extensions.getViewBitmap import com.simplemobiletools.calendar.pro.extensions.printBitmap import com.simplemobiletools.calendar.pro.helpers.* -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.* import kotlinx.android.synthetic.main.fragment_day.view.* import kotlinx.android.synthetic.main.top_navigation.view.* -import java.util.* class DayFragment : Fragment() { var mListener: NavigationListener? = null @@ -129,6 +127,7 @@ class DayFragment : Fragment() { Intent(context, getActivityToOpen(event.isTask())).apply { putExtra(EVENT_ID, event.id) putExtra(EVENT_OCCURRENCE_TS, event.startTS) + putExtra(IS_TASK_COMPLETED, event.isTaskCompleted()) startActivity(this) } } 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 dabfb44fb..11819c88d 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 @@ -610,6 +610,7 @@ class WeekFragment : Fragment(), WeeklyCalendar { Intent(context, getActivityToOpen(event.isTask())).apply { putExtra(EVENT_ID, event.id!!) putExtra(EVENT_OCCURRENCE_TS, event.startTS) + putExtra(IS_TASK_COMPLETED, event.isTaskCompleted()) startActivity(this) } } @@ -813,6 +814,7 @@ class WeekFragment : Fragment(), WeeklyCalendar { Intent(context, getActivityToOpen(event.isTask())).apply { putExtra(EVENT_ID, event.id) putExtra(EVENT_OCCURRENCE_TS, event.startTS) + putExtra(IS_TASK_COMPLETED, event.isTaskCompleted()) startActivity(this) } } 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 8f65ee49e..43519c25d 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 @@ -13,6 +13,7 @@ const val YEAR_LABEL = "year" const val EVENT_ID = "event_id" const val IS_DUPLICATE_INTENT = "is_duplicate_intent" const val EVENT_OCCURRENCE_TS = "event_occurrence_ts" +const val IS_TASK_COMPLETED = "is_task_completed" const val NEW_EVENT_START_TS = "new_event_start_ts" const val WEEK_START_TIMESTAMP = "week_start_timestamp" const val NEW_EVENT_SET_HOUR_DURATION = "new_event_set_hour_duration" @@ -206,6 +207,8 @@ const val EVENT = "EVENT" const val TASK = "TASK" const val START_TS = "START_TS" const val END_TS = "END_TS" +const val ORIGINAL_START_TS = "ORIGINAL_START_TS" +const val ORIGINAL_END_TS = "ORIGINAL_END_TS" const val REMINDER_1_MINUTES = "REMINDER_1_MINUTES" const val REMINDER_2_MINUTES = "REMINDER_2_MINUTES" const val REMINDER_3_MINUTES = "REMINDER_3_MINUTES" @@ -219,6 +222,7 @@ const val ATTENDEES = "ATTENDEES" const val AVAILABILITY = "AVAILABILITY" const val EVENT_TYPE_ID = "EVENT_TYPE_ID" const val EVENT_CALENDAR_ID = "EVENT_CALENDAR_ID" +const val IS_NEW_EVENT = "IS_NEW_EVENT" // actions const val ACTION_MARK_COMPLETED = "ACTION_MARK_COMPLETED" 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 64e3eb982..ae55f19db 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 @@ -252,7 +252,7 @@ class EventsHelper(val context: Context) { fun addEventRepetitionException(parentEventId: Long, occurrenceTS: Long, addToCalDAV: Boolean) { ensureBackgroundThread { - val parentEvent = eventsDB.getEventWithId(parentEventId) ?: return@ensureBackgroundThread + val parentEvent = eventsDB.getEventOrTaskWithId(parentEventId) ?: return@ensureBackgroundThread var repetitionExceptions = parentEvent.repetitionExceptions repetitionExceptions.add(Formatter.getDayCodeFromTS(occurrenceTS)) repetitionExceptions = repetitionExceptions.distinct().toMutableList() as ArrayList @@ -285,7 +285,6 @@ class EventsHelper(val context: Context) { } else { try { val typesList = context.config.getDisplayEventTypessAsList() - events.addAll(eventsDB.getTasksFromTo(fromTS, toTS, typesList)) events.addAll(eventsDB.getOneTimeEventsFromToWithTypes(toTS, fromTS, typesList).toMutableList() as ArrayList) } catch (e: Exception) { @@ -317,6 +316,7 @@ class EventsHelper(val context: Context) { } events.forEach { + if (it.isTask()) updateIsTaskCompleted(it) it.updateIsPastEvent() val originalEvent = eventsDB.getEventWithId(it.id!!) if (originalEvent != null && @@ -364,13 +364,13 @@ class EventsHelper(val context: Context) { if (displayEventTypes.isEmpty()) { return ArrayList() } else { - eventsDB.getRepeatableEventsFromToWithTypes(toTS, context.config.getDisplayEventTypessAsList()).toMutableList() as ArrayList + eventsDB.getRepeatableEventsOrTasksWithTypes(toTS, context.config.getDisplayEventTypessAsList()).toMutableList() as ArrayList } } else { if (eventId == -1L) { - eventsDB.getRepeatableEventsFromToWithTypes(toTS).toMutableList() as ArrayList + eventsDB.getRepeatableEventsOrTasksWithTypes(toTS).toMutableList() as ArrayList } else { - eventsDB.getRepeatableEventFromToWithId(eventId, toTS).toMutableList() as ArrayList + eventsDB.getRepeatableEventsOrTasksWithId(eventId, toTS).toMutableList() as ArrayList } } @@ -482,10 +482,18 @@ class EventsHelper(val context: Context) { return events } + fun updateIsTaskCompleted(event: Event) { + val task = context.completedTasksDB.getTaskWithIdAndTs(event.id!!, startTs = event.startTS) + event.flags = task?.flags ?: event.flags + } + fun getRunningEventsOrTasks(): List { val ts = getNowSeconds() val events = eventsDB.getOneTimeEventsOrTasksFromTo(ts, ts).toMutableList() as ArrayList events.addAll(getRepeatableEventsFor(ts, ts)) + events.forEach { + if (it.isTask()) updateIsTaskCompleted(it) + } return events } diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/interfaces/EventsDao.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/interfaces/EventsDao.kt index 9d04e9757..791ec4667 100644 --- a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/interfaces/EventsDao.kt +++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/interfaces/EventsDao.kt @@ -42,14 +42,14 @@ interface EventsDao { @Query("SELECT * FROM events WHERE end_ts > :toTS AND repeat_interval = 0 AND event_type IN (:eventTypeIds) AND type = $TYPE_EVENT") fun getOneTimeFutureEventsWithTypes(toTS: Long, eventTypeIds: List): List - @Query("SELECT * FROM events WHERE start_ts <= :toTS AND repeat_interval != 0 AND type = $TYPE_EVENT") - fun getRepeatableEventsFromToWithTypes(toTS: Long): List + @Query("SELECT * FROM events WHERE start_ts <= :toTS AND repeat_interval != 0 AND (type = $TYPE_EVENT OR type = $TYPE_TASK)") + fun getRepeatableEventsOrTasksWithTypes(toTS: Long): List - @Query("SELECT * FROM events WHERE id = :id AND start_ts <= :toTS AND repeat_interval != 0 AND type = $TYPE_EVENT") - fun getRepeatableEventFromToWithId(id: Long, toTS: Long): List + @Query("SELECT * FROM events WHERE id = :id AND start_ts <= :toTS AND repeat_interval != 0 AND (type = $TYPE_EVENT OR type = $TYPE_TASK)") + fun getRepeatableEventsOrTasksWithId(id: Long, toTS: Long): List - @Query("SELECT * FROM events WHERE start_ts <= :toTS AND start_ts != 0 AND repeat_interval != 0 AND event_type IN (:eventTypeIds) AND type = $TYPE_EVENT") - fun getRepeatableEventsFromToWithTypes(toTS: Long, eventTypeIds: List): List + @Query("SELECT * FROM events WHERE start_ts <= :toTS AND start_ts != 0 AND repeat_interval != 0 AND event_type IN (:eventTypeIds) AND (type = $TYPE_EVENT OR type = $TYPE_TASK)") + fun getRepeatableEventsOrTasksWithTypes(toTS: Long, eventTypeIds: List): List @Query("SELECT * FROM events WHERE repeat_interval != 0 AND (repeat_limit == 0 OR repeat_limit > :currTS) AND event_type IN (:eventTypeIds) AND type = $TYPE_EVENT") fun getRepeatableFutureEventsWithTypes(currTS: Long, eventTypeIds: List): List @@ -106,12 +106,13 @@ interface EventsDao { @Query("UPDATE events SET import_id = :importId, source = :source WHERE id = :id AND type = $TYPE_EVENT") fun updateEventImportIdAndSource(importId: String, source: String, id: Long) - @Query("UPDATE events SET repeat_limit = :repeatLimit WHERE id = :id AND type = $TYPE_EVENT") + @Query("UPDATE events SET repeat_limit = :repeatLimit WHERE id = :id AND (type = $TYPE_EVENT OR type = $TYPE_TASK)") fun updateEventRepetitionLimit(repeatLimit: Long, id: Long) - @Query("UPDATE events SET repetition_exceptions = :repetitionExceptions WHERE id = :id AND type = $TYPE_EVENT") + @Query("UPDATE events SET repetition_exceptions = :repetitionExceptions WHERE id = :id AND (type = $TYPE_EVENT OR type = $TYPE_TASK)") fun updateEventRepetitionExceptions(repetitionExceptions: String, id: Long) + @Deprecated("Use Context.updateTaskCompletion() instead unless you know what you are doing.") @Query("UPDATE events SET flags = :newFlags WHERE id = :id") fun updateTaskCompletion(id: Long, newFlags: Int) diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/interfaces/TasksDao.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/interfaces/TasksDao.kt new file mode 100644 index 000000000..d8abb4f96 --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/interfaces/TasksDao.kt @@ -0,0 +1,19 @@ +package com.simplemobiletools.calendar.pro.interfaces + +import androidx.room.Dao +import androidx.room.Insert +import androidx.room.OnConflictStrategy +import androidx.room.Query +import com.simplemobiletools.calendar.pro.models.Task + +@Dao +interface TasksDao { + @Insert(onConflict = OnConflictStrategy.REPLACE) + fun insertOrUpdate(task: Task): Long + + @Query("SELECT * FROM tasks WHERE task_id = :id AND start_ts = :startTs") + fun getTaskWithIdAndTs(id: Long, startTs: Long): Task? + + @Query("DELETE FROM tasks WHERE task_id = :id AND start_ts = :startTs") + fun deleteTaskWithIdAndTs(id: Long, startTs: Long) +} diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/models/Task.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/models/Task.kt new file mode 100644 index 000000000..3e8ab5eaa --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/models/Task.kt @@ -0,0 +1,17 @@ +package com.simplemobiletools.calendar.pro.models + +import androidx.room.ColumnInfo +import androidx.room.Entity +import androidx.room.Index +import androidx.room.PrimaryKey +import com.simplemobiletools.calendar.pro.helpers.FLAG_TASK_COMPLETED + +@Entity(tableName = "tasks", indices = [(Index(value = ["id", "task_id"], unique = true))]) +data class Task( + @PrimaryKey(autoGenerate = true) var id: Long?, + @ColumnInfo(name = "task_id") var task_id: Long, + @ColumnInfo(name = "start_ts") var startTS: Long = 0L, + @ColumnInfo(name = "flags") var flags: Int = 0, +) { + fun isTaskCompleted() = flags and FLAG_TASK_COMPLETED != 0 +} diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/services/MarkCompletedService.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/services/MarkCompletedService.kt index f4a3b0980..f64afda7d 100644 --- a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/services/MarkCompletedService.kt +++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/services/MarkCompletedService.kt @@ -5,9 +5,9 @@ import android.content.Intent import com.simplemobiletools.calendar.pro.extensions.cancelNotification import com.simplemobiletools.calendar.pro.extensions.cancelPendingIntent import com.simplemobiletools.calendar.pro.extensions.eventsDB +import com.simplemobiletools.calendar.pro.extensions.updateTaskCompletion import com.simplemobiletools.calendar.pro.helpers.ACTION_MARK_COMPLETED import com.simplemobiletools.calendar.pro.helpers.EVENT_ID -import com.simplemobiletools.calendar.pro.helpers.FLAG_TASK_COMPLETED class MarkCompletedService : IntentService("MarkCompleted") { @@ -17,8 +17,7 @@ class MarkCompletedService : IntentService("MarkCompleted") { val taskId = intent.getLongExtra(EVENT_ID, 0L) val task = eventsDB.getTaskWithId(taskId) if (task != null) { - task.flags = task.flags or FLAG_TASK_COMPLETED - eventsDB.updateTaskCompletion(task.id!!, task.flags) + updateTaskCompletion(task, true) cancelPendingIntent(task.id!!) cancelNotification(task.id!!) } diff --git a/app/src/main/res/layout/activity_task.xml b/app/src/main/res/layout/activity_task.xml index e4fa3d126..2f7206e29 100644 --- a/app/src/main/res/layout/activity_task.xml +++ b/app/src/main/res/layout/activity_task.xml @@ -114,7 +114,7 @@ tools:text="00:00" /> + + + + + + + + + + + + + + + + + + + + @@ -255,7 +352,7 @@ style="@style/ColoredButtonStyle" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_below="@+id/event_type_divider" + android:layout_below="@+id/task_type_divider" android:layout_centerHorizontal="true" android:layout_marginTop="@dimen/activity_margin" android:text="@string/mark_completed" diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml index c8811f9d1..b10880905 100644 --- a/app/src/main/res/values-ar/strings.xml +++ b/app/src/main/res/values-ar/strings.xml @@ -59,6 +59,7 @@ تكرار حتى للأبد الحدث متكرر + The task is repeatable الإختيار يحتوى على احداث مكررة حذف النسخ المحددة فقط حذف هذه النسخة وكل النسخ المستقبلية diff --git a/app/src/main/res/values-az/strings.xml b/app/src/main/res/values-az/strings.xml index 1e2b02721..fdeca179c 100644 --- a/app/src/main/res/values-az/strings.xml +++ b/app/src/main/res/values-az/strings.xml @@ -59,6 +59,7 @@ Bu vaxta qədər təkrarla Sonsuz Bu hadisə təkrarlanabilər + The task is repeatable Seçim təkrarlanan hadisələr ehtiva edir Yalnız seçilmiş hadisəni sil Bunu və bütün gələcək hadisələri sil @@ -279,4 +280,4 @@ Haven't found some strings? There's more at https://github.com/SimpleMobileTools/Simple-Commons/tree/master/commons/src/main/res --> - \ No newline at end of file + diff --git a/app/src/main/res/values-be/strings.xml b/app/src/main/res/values-be/strings.xml index a5e1de315..0838f232f 100644 --- a/app/src/main/res/values-be/strings.xml +++ b/app/src/main/res/values-be/strings.xml @@ -59,6 +59,7 @@ Repeat till Forever The event is repeatable + The task is repeatable The selection contains repeating events Delete the selected occurrence only Delete this and all future occurrences @@ -285,4 +286,4 @@ Haven't found some strings? There's more at https://github.com/SimpleMobileTools/Simple-Commons/tree/master/commons/src/main/res --> - \ No newline at end of file + diff --git a/app/src/main/res/values-bg/strings.xml b/app/src/main/res/values-bg/strings.xml index 59f3bb830..129fbf0c7 100644 --- a/app/src/main/res/values-bg/strings.xml +++ b/app/src/main/res/values-bg/strings.xml @@ -59,6 +59,7 @@ Повтаряй до Завинаги Събитието се повтаря + The task is repeatable Изборът съдържа само повтарящи се събития Изтрийте само избраното събитие Изтрийте това събитие и всички бъдещи събития @@ -279,4 +280,4 @@ Haven't found some strings? There's more at https://github.com/SimpleMobileTools/Simple-Commons/tree/master/commons/src/main/res --> - \ No newline at end of file + diff --git a/app/src/main/res/values-bn/strings.xml b/app/src/main/res/values-bn/strings.xml index ecabf3b88..9f97e6cbb 100644 --- a/app/src/main/res/values-bn/strings.xml +++ b/app/src/main/res/values-bn/strings.xml @@ -58,6 +58,7 @@ পুনরাবৃত্তি ততক্ষণ পর্যন্ত চিরতরে ঘটনাটি পুনরাবৃত্তিযোগ্য + The task is repeatable সিলেকশনটিতে পুনরাবৃত্তি ইভেন্টগুলি রয়েছে কেবলমাত্র সিলেক্টেড ঘটনা মুছুন এটি এবং ভবিষ্যতের সমস্ত ঘটনা মুছুন @@ -278,4 +279,4 @@ Haven't found some strings? There's more at https://github.com/SimpleMobileTools/Simple-Commons/tree/master/commons/src/main/res --> - \ No newline at end of file + diff --git a/app/src/main/res/values-br/strings.xml b/app/src/main/res/values-br/strings.xml index 014f84bcc..663dd20a7 100644 --- a/app/src/main/res/values-br/strings.xml +++ b/app/src/main/res/values-br/strings.xml @@ -59,6 +59,7 @@ addegouezhout betek Da viken An darvoud a c\'hall addegouezhout + The task is repeatable Darvoudoù a c\'hall addegouezhout a zo en diuzad Dilemel an degouezh bremanel nemetken Delete this and all future occurrences diff --git a/app/src/main/res/values-ca/strings.xml b/app/src/main/res/values-ca/strings.xml index 3fe5728bf..c3fbb159a 100644 --- a/app/src/main/res/values-ca/strings.xml +++ b/app/src/main/res/values-ca/strings.xml @@ -59,6 +59,7 @@ Repeteix fins Per sempre L\'esdeveniment és repetible + The task is repeatable La selecció conté esdeveniments que es repeteixen Suprimeix només l\'ocurrència seleccionada Suprimeix aquesta i totes les ocurrències futures @@ -279,4 +280,4 @@ Haven't found some strings? There's more at https://github.com/SimpleMobileTools/Simple-Commons/tree/master/commons/src/main/res --> - \ No newline at end of file + diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index 4cdf06d63..d510b7746 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -59,6 +59,7 @@ Opakovat do Navždy Událost se opakuje + The task is repeatable Výběr zahrnuje opakující se události Vymazat pouze vybrané výskyty Vymazat tento a jakékoliv budoucí výskyty @@ -282,4 +283,4 @@ Haven't found some strings? There's more at https://github.com/SimpleMobileTools/Simple-Commons/tree/master/commons/src/main/res --> - \ No newline at end of file + diff --git a/app/src/main/res/values-da/strings.xml b/app/src/main/res/values-da/strings.xml index fa561cbf9..98706f5ea 100644 --- a/app/src/main/res/values-da/strings.xml +++ b/app/src/main/res/values-da/strings.xml @@ -59,6 +59,7 @@ Gentag indtil Ingen slutdato Begivenheden kan gentages + The task is repeatable Valget indeholder gentagne begivenheder Slet kun denne forekomst Slet denne og alle fremtidige forekomster @@ -279,4 +280,4 @@ Haven't found some strings? There's more at https://github.com/SimpleMobileTools/Simple-Commons/tree/master/commons/src/main/res --> - \ No newline at end of file + diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index f6fdf1fd6..92625862c 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -59,6 +59,7 @@ Wiederholen bis unendlich Termin ist wiederholbar + The task is repeatable Die Auswahl enthält wiederkehrende Termine Nur die ausgewählte Wiederholung löschen Diese und zukünftige Wiederholungen löschen @@ -279,4 +280,4 @@ Haven't found some strings? There's more at https://github.com/SimpleMobileTools/Simple-Commons/tree/master/commons/src/main/res --> - \ No newline at end of file + diff --git a/app/src/main/res/values-el/strings.xml b/app/src/main/res/values-el/strings.xml index 500d55f0d..9b86e0917 100644 --- a/app/src/main/res/values-el/strings.xml +++ b/app/src/main/res/values-el/strings.xml @@ -59,6 +59,7 @@ Επανάληψη μέχρι Για πάντα Η εκδήλωση είναι επαναλαμβανόμενη + The task is repeatable Η επιλογή περιέχει επαναλαμβανόμενες εκδηλώσεις Διαγράψτε μόνο το επιλεγμένο περιστατικό Διαγράψτε αυτό και όλα τα μελλοντικά συμβάντα @@ -279,4 +280,4 @@ Haven't found some strings? There's more at https://github.com/SimpleMobileTools/Simple-Commons/tree/master/commons/src/main/res --> - \ No newline at end of file + diff --git a/app/src/main/res/values-eo/strings.xml b/app/src/main/res/values-eo/strings.xml index 089bb27ee..eaac5016b 100644 --- a/app/src/main/res/values-eo/strings.xml +++ b/app/src/main/res/values-eo/strings.xml @@ -59,6 +59,7 @@ Ripeti ĝis Forever The event is repeatable + The task is repeatable The selection contains repeating events Delete the selected occurrence only Delete this and all future occurrences @@ -279,4 +280,4 @@ Haven't found some strings? There's more at https://github.com/SimpleMobileTools/Simple-Commons/tree/master/commons/src/main/res --> - \ No newline at end of file + diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index 87a1e1684..587c7ec2d 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -59,6 +59,7 @@ Repetir hasta Siempre Este evento se repite + The task is repeatable La selección contiene repetición de eventos Eliminar solo el evento seleccionado Eliminar el evento y repeticiones futuras @@ -279,4 +280,4 @@ Haven't found some strings? There's more at https://github.com/SimpleMobileTools/Simple-Commons/tree/master/commons/src/main/res --> - \ No newline at end of file + diff --git a/app/src/main/res/values-et/strings.xml b/app/src/main/res/values-et/strings.xml index 22099edcc..a07d46407 100644 --- a/app/src/main/res/values-et/strings.xml +++ b/app/src/main/res/values-et/strings.xml @@ -59,6 +59,7 @@ Korda kuni Igavesti Sündmus on korratav + The task is repeatable Valik sisaldab korduvaid sündmusi Delete the selected occurrence only Delete this and all future occurrences @@ -279,4 +280,4 @@ Haven't found some strings? There's more at https://github.com/SimpleMobileTools/Simple-Commons/tree/master/commons/src/main/res --> - \ No newline at end of file + diff --git a/app/src/main/res/values-eu/strings.xml b/app/src/main/res/values-eu/strings.xml index 23c9a3b3e..9a26e91e3 100644 --- a/app/src/main/res/values-eu/strings.xml +++ b/app/src/main/res/values-eu/strings.xml @@ -59,6 +59,7 @@ Noiz arte errepikatu Betiko Gertaera errepikagarria da + The task is repeatable Hautaketak errepikatzen diren gertaerak ditu Ezabatu hautatutako gertaera soilik Ezabatu gertaera hau eta datozen guztiak @@ -279,4 +280,4 @@ Haven't found some strings? There's more at https://github.com/SimpleMobileTools/Simple-Commons/tree/master/commons/src/main/res --> - \ No newline at end of file + diff --git a/app/src/main/res/values-fi/strings.xml b/app/src/main/res/values-fi/strings.xml index 00dc6a3aa..3544e96f8 100644 --- a/app/src/main/res/values-fi/strings.xml +++ b/app/src/main/res/values-fi/strings.xml @@ -59,6 +59,7 @@ Toista kunnes Aina Tapahtuma on toistettavissa + The task is repeatable Valinta sisältää toistettavia tapahtumia Poista vain valittu esiintymä Poista tämä ja seuraavat esiintymät @@ -279,4 +280,4 @@ Haven't found some strings? There's more at https://github.com/SimpleMobileTools/Simple-Commons/tree/master/commons/src/main/res --> - \ No newline at end of file + diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 9f416969d..f6c49da2e 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -59,6 +59,7 @@ Répéter jusqu\'à Éternellement L\'évènement est périodique + The task is repeatable La sélection contient des évènements périodiques Supprimer seulement l\'occurrence sélectionnée Supprimer cette occurrence et toutes les occurrences futures @@ -279,4 +280,4 @@ Haven't found some strings? There's more at https://github.com/SimpleMobileTools/Simple-Commons/tree/master/commons/src/main/res --> - \ No newline at end of file + diff --git a/app/src/main/res/values-gl/strings.xml b/app/src/main/res/values-gl/strings.xml index f45aac64e..26cb97e28 100644 --- a/app/src/main/res/values-gl/strings.xml +++ b/app/src/main/res/values-gl/strings.xml @@ -59,6 +59,7 @@ Repetir ata Sempre O evento é repetible + The task is repeatable A selección contén eventos recurrentes Eliminar só o evento seleccionado Eliminar este e todos os eventos futuros @@ -279,4 +280,4 @@ Haven't found some strings? There's more at https://github.com/SimpleMobileTools/Simple-Commons/tree/master/commons/src/main/res --> - \ No newline at end of file + diff --git a/app/src/main/res/values-hi-rIN/strings.xml b/app/src/main/res/values-hi-rIN/strings.xml index 4febd8c56..c16f25ce0 100644 --- a/app/src/main/res/values-hi-rIN/strings.xml +++ b/app/src/main/res/values-hi-rIN/strings.xml @@ -59,6 +59,7 @@ Repeat till Forever The event is repeatable + The task is repeatable The selection contains repeating events Delete the selected occurrence only Delete this and all future occurrences @@ -279,4 +280,4 @@ Haven't found some strings? There's more at https://github.com/SimpleMobileTools/Simple-Commons/tree/master/commons/src/main/res --> - \ No newline at end of file + diff --git a/app/src/main/res/values-hr/strings.xml b/app/src/main/res/values-hr/strings.xml index bedec14c4..03ecd9d2c 100644 --- a/app/src/main/res/values-hr/strings.xml +++ b/app/src/main/res/values-hr/strings.xml @@ -59,6 +59,7 @@ Ponavljaj do Zauvijek Događaj se ponavlja + The task is repeatable Odabir sadrži ponavljajuće događaje Izbriši samo odabrano ponavljanje Izbriši ovo i sva buduća ponavljanja @@ -282,4 +283,4 @@ Haven't found some strings? There's more at https://github.com/SimpleMobileTools/Simple-Commons/tree/master/commons/src/main/res --> - \ No newline at end of file + diff --git a/app/src/main/res/values-hu/strings.xml b/app/src/main/res/values-hu/strings.xml index e84dd32c6..5847e8ed0 100644 --- a/app/src/main/res/values-hu/strings.xml +++ b/app/src/main/res/values-hu/strings.xml @@ -59,6 +59,7 @@ Ismétlés eddig: Örökké Az esemény ismétlődik + The task is repeatable A kiválasztás ismétlődő eseményeket tartalmaz Csak a kiválasztott előfordulás törlése Ez és az összes jövőbeli előfordulás törlése @@ -279,4 +280,4 @@ Haven't found some strings? There's more at https://github.com/SimpleMobileTools/Simple-Commons/tree/master/commons/src/main/res --> - \ No newline at end of file + diff --git a/app/src/main/res/values-in/strings.xml b/app/src/main/res/values-in/strings.xml index cd6117de0..f2fa485cd 100644 --- a/app/src/main/res/values-in/strings.xml +++ b/app/src/main/res/values-in/strings.xml @@ -59,6 +59,7 @@ Ulangi sampai Selamanya Acara berulang + The task is repeatable Acara yang dipilih berisi acara yang berulang Hapus acara ini saja Hapus acara ini dan semua perulangannya diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index 58cfe2d4f..ae870197e 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -59,6 +59,7 @@ Ripeti fino a Per sempre L\'evento è ripetibile + The task is repeatable La selezione contiene eventi ripetuti Elimina solamente l\'occorenza selezionata Rimuovi questo e tutte le future occorrenze @@ -279,4 +280,4 @@ Haven't found some strings? There's more at https://github.com/SimpleMobileTools/Simple-Commons/tree/master/commons/src/main/res --> - \ No newline at end of file + diff --git a/app/src/main/res/values-iw/strings.xml b/app/src/main/res/values-iw/strings.xml index 85b5e327e..ceccb0cb7 100644 --- a/app/src/main/res/values-iw/strings.xml +++ b/app/src/main/res/values-iw/strings.xml @@ -59,6 +59,7 @@ חזרה עד לנצח האירוע ניתן לחזרה + The task is repeatable הבחירה כוללת אירועים חוזרים מחיקת אירוע בודד מחיקת כל האירועים העתידיים בסדרה diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index 1127693ad..a45469abe 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -60,6 +60,7 @@ Repeat till Forever The event is repeatable + The task is repeatable The selection contains repeating events Delete the selected occurrence only Delete this and all future occurrences @@ -277,4 +278,4 @@ Haven't found some strings? There's more at https://github.com/SimpleMobileTools/Simple-Commons/tree/master/commons/src/main/res --> - \ No newline at end of file + diff --git a/app/src/main/res/values-ko/strings.xml b/app/src/main/res/values-ko/strings.xml index 75407f4af..71c0e4f65 100644 --- a/app/src/main/res/values-ko/strings.xml +++ b/app/src/main/res/values-ko/strings.xml @@ -59,6 +59,7 @@ 까지 반복 영원히 반복 일정입니다 + The task is repeatable 선택한 항목에 반복되는 일정들이 있습니다 선택한 항목만 삭제 반복되는 일정까지 삭제 diff --git a/app/src/main/res/values-lt/strings.xml b/app/src/main/res/values-lt/strings.xml index 7e088e52d..d27f5a1a1 100644 --- a/app/src/main/res/values-lt/strings.xml +++ b/app/src/main/res/values-lt/strings.xml @@ -59,6 +59,7 @@ Kartoti iki Amžinai Įvykis yra pasikartojantis + The task is repeatable Žymėjime yra pasikartojančių įvykių Ištrinti tik pasirinktą pasikartojimą Ištrinti šį ir visus būsimus pasikartojimus @@ -282,4 +283,4 @@ Haven't found some strings? There's more at https://github.com/SimpleMobileTools/Simple-Commons/tree/master/commons/src/main/res --> - \ No newline at end of file + diff --git a/app/src/main/res/values-lv/strings.xml b/app/src/main/res/values-lv/strings.xml index f6ab44703..bb9dec332 100644 --- a/app/src/main/res/values-lv/strings.xml +++ b/app/src/main/res/values-lv/strings.xml @@ -59,6 +59,7 @@ Atkārtot līdz Bezgalīgi Notikums ir atkārtojošs + The task is repeatable Atlasītajos ir atkārtojoši/periodiski notikumi Dzēst tikai šo atlasīto notikumu Dzēst šo un visus turpmākos notikumus diff --git a/app/src/main/res/values-nb-rNO/strings.xml b/app/src/main/res/values-nb-rNO/strings.xml index ed858136c..8932e944a 100644 --- a/app/src/main/res/values-nb-rNO/strings.xml +++ b/app/src/main/res/values-nb-rNO/strings.xml @@ -59,6 +59,7 @@ Gjenta til For alltid Hendelsen er repeterbar + The task is repeatable Markeringen inneholder gjentagende hendelser Slett bare den merkede forekomsten Slett denne og alle framtidige forekomster @@ -279,4 +280,4 @@ Haven't found some strings? There's more at https://github.com/SimpleMobileTools/Simple-Commons/tree/master/commons/src/main/res --> - \ No newline at end of file + diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml index 13ffca983..9534f518b 100644 --- a/app/src/main/res/values-nl/strings.xml +++ b/app/src/main/res/values-nl/strings.xml @@ -55,6 +55,7 @@ Herhalen tot Blijven herhalen De afspraak wordt herhaald + The task is repeatable De selectie bevat herhaalde afspraken Alleen huidige afspraak verwijderen Deze afspraak en hierop volgende herhalingen verwijderen @@ -275,4 +276,4 @@ Haven't found some strings? There's more at https://github.com/SimpleMobileTools/Simple-Commons/tree/master/commons/src/main/res --> - \ No newline at end of file + diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index 97b9907e6..65812afd2 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -59,6 +59,7 @@ Powtarzaj do Zawsze Wydarzenie jest cykliczne + The task is repeatable Wybór zawiera powtarzające się wydarzenia Usuń tylko wybrane wystąpienie Usuń to i wszystkie przyszłe wystąpienia @@ -285,4 +286,4 @@ Haven't found some strings? There's more at https://github.com/SimpleMobileTools/Simple-Commons/tree/master/commons/src/main/res --> - \ No newline at end of file + diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index 8bd5cd34f..a60302f94 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -59,6 +59,7 @@ Repetir até Eternamente O evento é repetitivo + The task is repeatable A seleção contém eventos recorrentes Apagar a ocorrência selecionada Exclua essa e todas as ocorrências futuras @@ -280,4 +281,4 @@ Não encontrou todas as cadeias a traduzir? Existem mais algumas em: https://github.com/SimpleMobileTools/Simple-Commons/tree/master/commons/src/main/res --> - \ No newline at end of file + diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml index 70f6aa635..a13273d9f 100644 --- a/app/src/main/res/values-pt/strings.xml +++ b/app/src/main/res/values-pt/strings.xml @@ -59,6 +59,7 @@ Repetir até Eternamente O evento é recorrente + The task is repeatable A seleção contém eventos recorrentes Apagar a ocorrência selecionada Apagar esta e todas as ocorrências futuras @@ -279,4 +280,4 @@ Não encontrou todas as cadeias a traduzir? Existem mais algumas em: https://github.com/SimpleMobileTools/Simple-Commons/tree/master/commons/src/main/res --> - \ No newline at end of file + diff --git a/app/src/main/res/values-ro/strings.xml b/app/src/main/res/values-ro/strings.xml index 607f3fba7..26f618140 100644 --- a/app/src/main/res/values-ro/strings.xml +++ b/app/src/main/res/values-ro/strings.xml @@ -59,6 +59,7 @@ Repetă până pe Pentru totdeauna Evenimentul este repetabil + The task is repeatable Selecția conține evenimente repetitive Șterge numai evenimentul repetitiv selectat Șterge acest și toate evenimentele repetitive viitoare diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 88be2cf29..996eb0fd2 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -59,6 +59,7 @@ Повторять до Бесконечно Это событие может повторяться + The task is repeatable В выбранном есть повторяющиеся события Удалить только это событие Удалить это и все будущие события @@ -285,4 +286,4 @@ Haven't found some strings? There's more at https://github.com/SimpleMobileTools/Simple-Commons/tree/master/commons/src/main/res --> - \ No newline at end of file + diff --git a/app/src/main/res/values-sk/strings.xml b/app/src/main/res/values-sk/strings.xml index 478b292d4..30fcc115a 100644 --- a/app/src/main/res/values-sk/strings.xml +++ b/app/src/main/res/values-sk/strings.xml @@ -59,6 +59,7 @@ Opakovať do Navždy Udalosť je opakujúca sa + Úloha je opakujúca sa Výber obsahuje opakujúce sa udalosti Vymazať iba označené opakovania Vymazať toto a všetky budúce opakovania @@ -282,4 +283,4 @@ Haven't found some strings? There's more at https://github.com/SimpleMobileTools/Simple-Commons/tree/master/commons/src/main/res --> - \ No newline at end of file + diff --git a/app/src/main/res/values-sv/strings.xml b/app/src/main/res/values-sv/strings.xml index ebd59eaee..514734a3c 100644 --- a/app/src/main/res/values-sv/strings.xml +++ b/app/src/main/res/values-sv/strings.xml @@ -59,6 +59,7 @@ Upprepa tills Alltid Händelsen är återkommande + The task is repeatable Markeringen innehåller återkommande händelser Ta bara bort den valda förekomsten Ta bort denna och alla framtida förekomster @@ -279,4 +280,4 @@ Haven't found some strings? There's more at https://github.com/SimpleMobileTools/Simple-Commons/tree/master/commons/src/main/res --> - \ No newline at end of file + diff --git a/app/src/main/res/values-th/strings.xml b/app/src/main/res/values-th/strings.xml index a95549d0c..6ed6bc404 100644 --- a/app/src/main/res/values-th/strings.xml +++ b/app/src/main/res/values-th/strings.xml @@ -59,6 +59,7 @@ Repeat till Forever The event is repeatable + The task is repeatable The selection contains repeating events Delete the selected occurrence only Delete this and all future occurrences diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index a1855feb2..3a559010f 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -59,6 +59,7 @@ Şu kadar tekrarla: Sonsuza kadar Etkinlik tekrarlanabilir + The task is repeatable Seçim tekrarlanan etkinlikleri içeriyor Yalnızca seçilen etkinlikleri sil Bu ve gelecekteki tüm etkinlikleri sil @@ -279,4 +280,4 @@ Haven't found some strings? There's more at https://github.com/SimpleMobileTools/Simple-Commons/tree/master/commons/src/main/res --> - \ No newline at end of file + diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index 14c96b278..42fccc7d9 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -59,6 +59,7 @@ Повторювати до Безкінечно Ця подія є повторюваною + The task is repeatable Вибране містить повторювані події Видалити лише обране повторення Видалити це і всі наступні повторення @@ -285,4 +286,4 @@ Haven't found some strings? There's more at https://github.com/SimpleMobileTools/Simple-Commons/tree/master/commons/src/main/res --> - \ No newline at end of file + diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index b1a4f65be..36e7a2c6e 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -59,6 +59,7 @@ 重复直到 永远 这是个重复活动 + The task is repeatable 选择的项目含有重复活动 只删除选择的事件 删除这个及全部未来的事件 @@ -276,4 +277,4 @@ Haven't found some strings? There's more at https://github.com/SimpleMobileTools/Simple-Commons/tree/master/commons/src/main/res --> - \ No newline at end of file + diff --git a/app/src/main/res/values-zh-rHK/strings.xml b/app/src/main/res/values-zh-rHK/strings.xml index 2fb3b3d21..725fb285e 100644 --- a/app/src/main/res/values-zh-rHK/strings.xml +++ b/app/src/main/res/values-zh-rHK/strings.xml @@ -59,6 +59,7 @@ 重複直到 永遠 這是個重複活動 + The task is repeatable 選擇的項目含有重複活動 只刪除選擇的事件 刪除這個及全部未來的事件 @@ -276,4 +277,4 @@ Haven't found some strings? There's more at https://github.com/SimpleMobileTools/Simple-Commons/tree/master/commons/src/main/res --> - \ No newline at end of file + diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index bfe002d63..c4ff6325d 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -59,6 +59,7 @@ 重複直到 永遠 這是個重複活動 + The task is repeatable 選擇的項目含有重複活動 只刪除選擇的事件 刪除這個及全部未來的事件 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 340127875..42e33acdc 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -57,6 +57,7 @@ Repeat till Forever The event is repeatable + The task is repeatable The selection contains repeating events Delete the selected occurrence only Delete this and all future occurrences