diff --git a/CHANGELOG.md b/CHANGELOG.md index fbc4a861..2eaa3685 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [1.7.0] - 2626-07-18 +### Added +- Added alarm groups (see [PR #473](https://github.com/FossifyOrg/Clock/pull/473) for more details) + ## [1.6.0] - 2026-01-30 ### Added - Added support for custom fonts diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 81bf146d..b910bbdd 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -33,6 +33,14 @@ android:roundIcon="@mipmap/ic_launcher" android:theme="@style/AppTheme"> + + + + + if (menuItem.itemId == R.id.delete_group) { + ConfirmationDialog(this, messageId = R.string.delete_group_confirmation) { + dbHelper.deleteGroup(groupRef) + EventBus.getDefault().post(AlarmEvent.Refresh) + finish() + } + true + } else { + false + } + } + + binding.groupFab.setOnClickListener { + val newAlarm = createNewAlarm(DEFAULT_ALARM_MINUTES, 0) + newAlarm.isEnabled = true + newAlarm.days = getTomorrowBit() + newAlarm.groupRef = groupRef + openEditAlarm(newAlarm) + } + } + + override fun onResume() { + super.onResume() + setupTopAppBar(binding.groupAppbar, NavigationIcon.Arrow, topBarColor = getProperBackgroundColor()) + updateTextColors(binding.groupHolder) + if (!setupGroupTitle()) { + return + } + refreshAlarms() + } + + override fun onDestroy() { + super.onDestroy() + EventBus.getDefault().unregister(this) + } + + override fun onActivityResult(requestCode: Int, resultCode: Int, resultData: Intent?) { + super.onActivityResult(requestCode, resultCode, resultData) + if (requestCode == PICK_AUDIO_FILE_INTENT_ID && resultCode == RESULT_OK && resultData != null) { + currentEditAlarmDialog?.updateSelectedAlarmSound(storeNewYourAlarmSound(resultData)) + } + } + + private fun setupGroupTitle(): Boolean { + val group = dbHelper.getGroups().firstOrNull { it.ref == groupRef } + if (group == null) { + finish() + return false + } + + binding.groupToolbarTitle.text = group.title + binding.groupToolbarTitle.setTextColor(getProperTextColor()) + binding.groupToolbarTitle.setOnClickListener { + RenameGroupDialog(this, group) { + setupGroupTitle() + } + } + return true + } + + private fun refreshAlarms() { + ensureBackgroundThread { + val alarms = dbHelper.getAlarms() + .filter { it.groupRef == groupRef } + .sortedBy { it.timeInMinutes } + + runOnUiThread { + val items = ArrayList(alarms.map { AlarmListItem.AlarmRow(it) }) + var currAdapter = binding.groupList.adapter as? AlarmsAdapter + if (currAdapter == null) { + currAdapter = AlarmsAdapter( + activity = this, + items = items, + toggleAlarmInterface = this, + recyclerView = binding.groupList + ) { + if (it is Alarm) { + openEditAlarm(it) + } + }.apply { + binding.groupList.adapter = this + } + } else { + currAdapter.apply { + updatePrimaryColor() + updateBackgroundColor(getProperBackgroundColor()) + updateTextColor(getProperTextColor()) + updateItems(items) + } + } + binding.groupPlaceholder.beVisibleIf(items.isEmpty()) + } + } + } + + private fun openEditAlarm(alarm: Alarm) { + currentEditAlarmDialog = EditAlarmDialog(this, alarm) { + alarm.id = it + currentEditAlarmDialog = null + refreshAlarms() + checkAlarmState(alarm) + } + } + + private fun checkAlarmState(alarm: Alarm) { + if (alarm.isEnabled) { + alarmController.scheduleNextOccurrence(alarm = alarm) + } else { + cancelAlarmClock(alarm) + } + } + + override fun alarmToggled(id: Int, isEnabled: Boolean) { + handleFullScreenNotificationsPermission { granted -> + if (granted) { + if (dbHelper.updateAlarmEnabledState(id, isEnabled)) { + dbHelper.getAlarmWithId(id)?.let { alarm -> + alarm.isEnabled = isEnabled + checkAlarmState(alarm) + if (!alarm.isEnabled && alarm.oneShot) { + dbHelper.deleteAlarms(arrayListOf(alarm)) + } + } + refreshAlarms() + } else { + toast(org.fossify.commons.R.string.unknown_error_occurred) + } + updateWidgets() + } else { + refreshAlarms() + } + } + } + + @Subscribe(threadMode = ThreadMode.MAIN) + fun onMessageEvent(@Suppress("unused") event: AlarmEvent.Refresh) { + refreshAlarms() + } +} diff --git a/app/src/main/kotlin/org/fossify/clock/activities/SettingsActivity.kt b/app/src/main/kotlin/org/fossify/clock/activities/SettingsActivity.kt index 66a10e18..a573bd04 100644 --- a/app/src/main/kotlin/org/fossify/clock/activities/SettingsActivity.kt +++ b/app/src/main/kotlin/org/fossify/clock/activities/SettingsActivity.kt @@ -307,12 +307,13 @@ class SettingsActivity : SimpleActivity() { private fun exportData(outputUri: Uri) { val alarms = dbHelper.getAlarms() + val groups = dbHelper.getGroups() val timers = timerDb.getTimers() if (alarms.isEmpty() && timers.isEmpty()) { toast(org.fossify.commons.R.string.no_entries_for_exporting) } else { ExportHelper(this).exportData( - backup = AlarmTimerBackup(alarms, timers), + backup = AlarmTimerBackup(alarms, groups, timers), outputUri = outputUri, ) { toast( diff --git a/app/src/main/kotlin/org/fossify/clock/adapters/AlarmsAdapter.kt b/app/src/main/kotlin/org/fossify/clock/adapters/AlarmsAdapter.kt index c29d72fe..03e92802 100644 --- a/app/src/main/kotlin/org/fossify/clock/adapters/AlarmsAdapter.kt +++ b/app/src/main/kotlin/org/fossify/clock/adapters/AlarmsAdapter.kt @@ -8,21 +8,33 @@ import android.view.ViewGroup import androidx.recyclerview.widget.ItemTouchHelper import androidx.recyclerview.widget.RecyclerView import org.fossify.clock.R +import org.fossify.clock.activities.GroupActivity import org.fossify.clock.activities.SimpleActivity import org.fossify.clock.databinding.ItemAlarmBinding +import org.fossify.clock.databinding.ItemAlarmGroupBinding +import org.fossify.clock.dialogs.AddToGroupDialog import org.fossify.clock.extensions.config import org.fossify.clock.extensions.dbHelper import org.fossify.clock.extensions.getFormattedTime +import org.fossify.clock.extensions.handleFullScreenNotificationsPermission +import org.fossify.clock.extensions.pendingReenableManager +import org.fossify.clock.extensions.showRemainingTimeMessage +import org.fossify.clock.helpers.ENTRY_TYPE_ALARM +import org.fossify.clock.helpers.ENTRY_TYPE_GROUP +import org.fossify.clock.helpers.getTimeOfNextAlarm import org.fossify.clock.helpers.updateNonRecurringAlarmDay import org.fossify.clock.interfaces.ToggleAlarmInterface import org.fossify.clock.models.Alarm import org.fossify.clock.models.AlarmEvent +import org.fossify.clock.models.AlarmListItem +import org.fossify.clock.models.Group import org.fossify.commons.adapters.MyRecyclerViewAdapter import org.fossify.commons.dialogs.ConfirmationDialog import org.fossify.commons.extensions.applyColorFilter import org.fossify.commons.extensions.beVisibleIf import org.fossify.commons.extensions.getSelectedDaysString import org.fossify.commons.extensions.move +import org.fossify.commons.extensions.moveLastItemToFront import org.fossify.commons.helpers.EVERY_DAY_BIT import org.fossify.commons.helpers.SORT_BY_CUSTOM import org.fossify.commons.interfaces.ItemMoveCallback @@ -33,12 +45,17 @@ import org.greenrobot.eventbus.EventBus class AlarmsAdapter( activity: SimpleActivity, - private var alarms: ArrayList, + private var items: ArrayList, private val toggleAlarmInterface: ToggleAlarmInterface, recyclerView: MyRecyclerView, itemClick: (Any) -> Unit, ) : MyRecyclerViewAdapter(activity, recyclerView, itemClick), ItemTouchHelperContract { + companion object { + private const val VIEW_TYPE_ALARM = 0 + private const val VIEW_TYPE_GROUP = 1 + } + private var startReorderDragListener: StartReorderDragListener init { @@ -63,17 +80,21 @@ class AlarmsAdapter( } when (id) { + R.id.cab_add_to_group -> addSelectedToGroup() R.id.cab_delete -> deleteItems() } } - override fun getSelectableItemCount() = alarms.size + override fun getSelectableItemCount() = items.count { it is AlarmListItem.AlarmRow } - override fun getIsItemSelectable(position: Int) = true + override fun getIsItemSelectable(position: Int) = items.getOrNull(position) is AlarmListItem.AlarmRow - override fun getItemSelectionKey(position: Int) = alarms.getOrNull(position)?.id + override fun getItemSelectionKey(position: Int) = + (items.getOrNull(position) as? AlarmListItem.AlarmRow)?.alarm?.id - override fun getItemKeyPosition(key: Int) = alarms.indexOfFirst { it.id == key } + override fun getItemKeyPosition(key: Int): Int { + return items.indexOfFirst { it is AlarmListItem.AlarmRow && it.alarm.id == key } + } @SuppressLint("NotifyDataSetChanged") override fun onActionModeCreated() { @@ -89,46 +110,81 @@ class AlarmsAdapter( override fun onRowSelected(myViewHolder: ViewHolder?) {} + override fun getItemViewType(position: Int): Int { + return when (items[position]) { + is AlarmListItem.GroupRow -> VIEW_TYPE_GROUP + is AlarmListItem.AlarmRow -> VIEW_TYPE_ALARM + } + } + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { - return createViewHolder(ItemAlarmBinding.inflate(layoutInflater, parent, false).root) + return if (viewType == VIEW_TYPE_GROUP) { + createViewHolder(ItemAlarmGroupBinding.inflate(layoutInflater, parent, false).root) + } else { + createViewHolder(ItemAlarmBinding.inflate(layoutInflater, parent, false).root) + } } override fun onBindViewHolder(holder: ViewHolder, position: Int) { - val alarm = alarms[position] - holder.bindView( - any = alarm, - allowSingleClick = true, - allowLongClick = true - ) { itemView, _ -> - setupView(itemView, alarm, holder) + when (val item = items[position]) { + is AlarmListItem.GroupRow -> { + holder.bindView( + any = item.group, + allowSingleClick = true, + allowLongClick = false + ) { itemView, _ -> + setupGroupView(itemView, item) + } + } + + is AlarmListItem.AlarmRow -> { + holder.bindView( + any = item.alarm, + allowSingleClick = true, + allowLongClick = true + ) { itemView, _ -> + setupView(itemView, item.alarm, holder) + } + } } bindViewHolder(holder) } - override fun getItemCount() = alarms.size + override fun getItemCount() = items.size @SuppressLint("NotifyDataSetChanged") - fun updateItems(newItems: ArrayList) { - alarms = newItems + fun updateItems(newItems: ArrayList) { + items = newItems notifyDataSetChanged() finishActMode() } - private fun deleteItems() { - val alarmsToRemove = ArrayList() - val positions = getSelectedItemPositions() - getSelectedItems().forEach { - alarmsToRemove.add(it) + private fun addSelectedToGroup() { + val selectedAlarms = getSelectedAlarms() + val selectedIds = selectedAlarms.map { it.id } + val selectedRefs = selectedAlarms.map { it.groupRef } + + AddToGroupDialog(selectedRefs, activity as SimpleActivity) { groupId -> + activity.dbHelper.assignAlarmsToGroup(selectedIds, groupId) + finishActMode() + EventBus.getDefault().post(AlarmEvent.Refresh) } + } - alarms.removeAll(alarmsToRemove) + private fun deleteItems() { + val positions = getSelectedItemPositions() + val alarmsToRemove = getSelectedAlarms() + items.removeAll { it is AlarmListItem.AlarmRow && alarmsToRemove.contains(it.alarm) } removeSelectedItems(positions) activity.dbHelper.deleteAlarms(alarmsToRemove) EventBus.getDefault().post(AlarmEvent.Refresh) } - private fun getSelectedItems(): ArrayList { - return alarms.filter { selectedKeys.contains(it.id) } as ArrayList + private fun getSelectedAlarms(): ArrayList { + return items.filterIsInstance() + .map { it.alarm } + .filter { selectedKeys.contains(it.id) } + .toCollection(ArrayList()) } @SuppressLint("ClickableViewAccessibility") @@ -163,7 +219,64 @@ class AlarmsAdapter( alarmSwitch.setOnClickListener { toggleAlarm(binding = this, alarm = alarm) } + alarmReenablePrompt.setTextColor(properPrimaryColor) + alarmReenablePrompt.beVisibleIf(activity.pendingReenableManager.isPromptVisible(ENTRY_TYPE_ALARM, alarm.id)) + alarmReenablePrompt.setOnClickListener { + onReenableClicked(ENTRY_TYPE_ALARM, alarm.id, listOf(alarm)) + } + } + } + + private fun setupGroupView(view: View, groupRow: AlarmListItem.GroupRow) { + ItemAlarmGroupBinding.bind(view).apply { + val group = groupRow.group + val alarms = groupRow.alarms + + groupIcon.applyColorFilter(textColor) + groupTitle.text = group.title + groupTitle.setTextColor(textColor) + + groupSubtitle.text = resources.getQuantityString( + R.plurals.group_alarm_count, alarms.size, alarms.size + ) + groupSubtitle.setTextColor(textColor) + + val anyEnabled = alarms.isNotEmpty() && alarms.any { it.isEnabled } + groupSwitch.setColors(textColor, properPrimaryColor, backgroundColor) + groupSwitch.isChecked = anyEnabled + groupSwitch.setOnClickListener { + toggleGroup(group.ref, groupSwitch.isChecked) + } + + groupReenablePrompt.setTextColor(properPrimaryColor) + groupReenablePrompt.beVisibleIf(activity.pendingReenableManager.isPromptVisible(ENTRY_TYPE_GROUP, group.ref)) + groupReenablePrompt.setOnClickListener { + onReenableClicked(ENTRY_TYPE_GROUP, group.ref, alarms) + } + } + } + + private fun toggleGroup(groupId: Int, isEnabled: Boolean) { + (activity as SimpleActivity).handleFullScreenNotificationsPermission { granted -> + if (granted) { + val updatedAlarms = activity.dbHelper.updateGroupEnabledState(groupId, isEnabled) + updatedAlarms.forEach { toggleAlarmInterface.alarmToggled(it.id, isEnabled) } + if (isEnabled) + showEarliestTriggerToast(updatedAlarms) + + notifyManualToggle(ENTRY_TYPE_GROUP, groupId, isEnabled) + } + } + } + + @SuppressLint("NotifyDataSetChanged") + private fun notifyManualToggle(entryType: Int, entryId: Int, isEnabled: Boolean) { + if (isEnabled) { + activity.pendingReenableManager.onManuallyEnabled(entryType, entryId) + } else { + activity.pendingReenableManager.onManuallyDisabled(entryType, entryId) } + notifyDataSetChanged() } private fun toggleAlarm(binding: ItemAlarmBinding, alarm: Alarm) { @@ -171,6 +284,8 @@ class AlarmsAdapter( alarm.isRecurring() -> { if (activity.config.wasAlarmWarningShown) { toggleAlarmInterface.alarmToggled(alarm.id, binding.alarmSwitch.isChecked) + + notifyManualToggle(ENTRY_TYPE_ALARM, alarm.id, binding.alarmSwitch.isChecked) } else { ConfirmationDialog( activity = activity, @@ -180,8 +295,12 @@ class AlarmsAdapter( ) { activity.config.wasAlarmWarningShown = true toggleAlarmInterface.alarmToggled(alarm.id, binding.alarmSwitch.isChecked) + + notifyManualToggle(ENTRY_TYPE_ALARM, alarm.id, binding.alarmSwitch.isChecked) } } + if (binding.alarmSwitch.isChecked) + showEarliestTriggerToast(listOf(alarm)) } else -> { @@ -191,10 +310,24 @@ class AlarmsAdapter( alarm = alarm, isEnabled = binding.alarmSwitch.isChecked ) toggleAlarmInterface.alarmToggled(alarm.id, binding.alarmSwitch.isChecked) + if (binding.alarmSwitch.isChecked) + showEarliestTriggerToast(listOf(alarm)) + notifyManualToggle(ENTRY_TYPE_ALARM, alarm.id, binding.alarmSwitch.isChecked) } } } + private fun showEarliestTriggerToast(alarms: List) { + val earliest = alarms.mapNotNull { getTimeOfNextAlarm(it)?.timeInMillis }.minOrNull() ?: return + activity.showRemainingTimeMessage(earliest - System.currentTimeMillis()) + } + + @SuppressLint("NotifyDataSetChanged") + private fun onReenableClicked(entryType: Int, entryId: Int, affectedAlarms: List) { + activity.pendingReenableManager.confirmReenable(entryType, entryId, affectedAlarms) + notifyDataSetChanged() + } + private fun getAlarmSelectedDaysString( alarm: Alarm, isEnabled: Boolean = alarm.isEnabled, ): String { @@ -202,7 +335,6 @@ class AlarmsAdapter( return if (alarm.days == EVERY_DAY_BIT) { activity.getString(org.fossify.commons.R.string.every_day) } else { - // TODO: This does not respect config.firstDayOfWeek activity.getSelectedDaysString(alarm.days) } } @@ -215,17 +347,16 @@ class AlarmsAdapter( } override fun onRowMoved(fromPosition: Int, toPosition: Int) { - alarms.move(fromPosition, toPosition) + items.move(fromPosition, toPosition) notifyItemMoved(fromPosition, toPosition) - saveAlarmsCustomOrder(alarms) + saveAlarmsCustomOrder() if (activity.config.alarmSort != SORT_BY_CUSTOM) { activity.config.alarmSort = SORT_BY_CUSTOM } } - private fun saveAlarmsCustomOrder(alarms: ArrayList) { - val alarmsCustomSortingIds = alarms.map { it.id } - - activity.config.alarmsCustomSorting = alarmsCustomSortingIds.joinToString { it.toString() } + private fun saveAlarmsCustomOrder() { + val ids = items.filterIsInstance().map { it.alarm.id } + activity.config.alarmsCustomSorting = ids.joinToString { it.toString() } } } diff --git a/app/src/main/kotlin/org/fossify/clock/adapters/TimerAdapter.kt b/app/src/main/kotlin/org/fossify/clock/adapters/TimerAdapter.kt index 052bb3aa..1719c5fa 100644 --- a/app/src/main/kotlin/org/fossify/clock/adapters/TimerAdapter.kt +++ b/app/src/main/kotlin/org/fossify/clock/adapters/TimerAdapter.kt @@ -98,7 +98,9 @@ class TimerAdapter( override fun getActionMenuId() = R.menu.cab_alarms - override fun prepareActionMode(menu: Menu) {} + override fun prepareActionMode(menu: Menu) { + menu.findItem(R.id.cab_add_to_group)?.isVisible = false + } override fun actionItemPressed(id: Int) { if (selectedKeys.isEmpty()) { diff --git a/app/src/main/kotlin/org/fossify/clock/dialogs/AddToGroupDialog.kt b/app/src/main/kotlin/org/fossify/clock/dialogs/AddToGroupDialog.kt new file mode 100644 index 00000000..d375d31a --- /dev/null +++ b/app/src/main/kotlin/org/fossify/clock/dialogs/AddToGroupDialog.kt @@ -0,0 +1,64 @@ +package org.fossify.clock.dialogs + +import android.widget.Toast +import org.fossify.clock.activities.SimpleActivity +import org.fossify.clock.databinding.DialogAddToGroupBinding +import org.fossify.clock.extensions.dbHelper +import org.fossify.clock.models.Group +import org.fossify.commons.extensions.getAlertDialogBuilder +import org.fossify.commons.extensions.setupDialogStuff +import org.fossify.commons.extensions.value +import org.fossify.commons.views.MyCompatRadioButton + +class AddToGroupDialog( + val selectedRefs: List, + val activity: SimpleActivity, + val callback: (groupId: Int) -> Unit, +) { + private val binding = DialogAddToGroupBinding.inflate(activity.layoutInflater) + private val groups = activity.dbHelper.getGroups() + private val radioIdToGroupId = HashMap() + + init { + val mutableGroups = groups.toMutableList() + + if (groups.any() && selectedRefs.any { it != 0 }){ + val noneText = activity.resources.getString(org.fossify.commons.R.string.none) + mutableGroups.add(0, Group(0, 0, noneText)) + } + + mutableGroups.forEach { group -> + val radioButton = MyCompatRadioButton(activity).apply { + id = android.view.View.generateViewId() + text = group.title + } + radioIdToGroupId[radioButton.id] = group.ref + binding.addToGroupRadioGroup.addView(radioButton) + } + + activity.getAlertDialogBuilder() + .setPositiveButton(org.fossify.commons.R.string.ok) { _, _ -> dialogConfirmed() } + .setNegativeButton(org.fossify.commons.R.string.cancel, null) + .apply { + activity.setupDialogStuff(binding.root, this, org.fossify.clock.R.string.add_to_group) + } + } + + private fun dialogConfirmed() { + val newGroupName = binding.addToGroupNewName.value.trim() + val groupNameExists = activity.dbHelper.getGroups().any { it.title == newGroupName } + + val groupId = if (newGroupName.isNotEmpty() && !groupNameExists) { + activity.dbHelper.insertGroup(newGroupName) + } else if (groupNameExists) { + val text = activity.resources.getString(org.fossify.clock.R.string.group_name_exists) + Toast.makeText(activity.baseContext, text, Toast.LENGTH_SHORT).show() + return + } else { + val checkedRadioId = binding.addToGroupRadioGroup.checkedRadioButtonId + radioIdToGroupId[checkedRadioId] ?: return + } + + callback(groupId) + } +} diff --git a/app/src/main/kotlin/org/fossify/clock/dialogs/EditAlarmDialog.kt b/app/src/main/kotlin/org/fossify/clock/dialogs/EditAlarmDialog.kt index 06a99263..b9d63fc3 100644 --- a/app/src/main/kotlin/org/fossify/clock/dialogs/EditAlarmDialog.kt +++ b/app/src/main/kotlin/org/fossify/clock/dialogs/EditAlarmDialog.kt @@ -122,7 +122,6 @@ class EditAlarmDialog( val dayLetters = activity.resources.getStringArray(org.fossify.commons.R.array.week_day_letters) - .toList() as ArrayList val dayIndexes = activity.rotateWeekdays(arrayListOf(0, 1, 2, 3, 4, 5, 6)) dayIndexes.forEach { diff --git a/app/src/main/kotlin/org/fossify/clock/dialogs/RenameGroupDialog.kt b/app/src/main/kotlin/org/fossify/clock/dialogs/RenameGroupDialog.kt new file mode 100644 index 00000000..8c236dbf --- /dev/null +++ b/app/src/main/kotlin/org/fossify/clock/dialogs/RenameGroupDialog.kt @@ -0,0 +1,45 @@ +package org.fossify.clock.dialogs + +import androidx.appcompat.app.AlertDialog +import org.fossify.clock.R +import org.fossify.clock.activities.SimpleActivity +import org.fossify.clock.databinding.DialogRenameGroupBinding +import org.fossify.clock.extensions.dbHelper +import org.fossify.clock.models.Group +import org.fossify.commons.extensions.getAlertDialogBuilder +import org.fossify.commons.extensions.setupDialogStuff +import org.fossify.commons.extensions.showKeyboard +import org.fossify.commons.extensions.toast +import org.fossify.commons.extensions.value + +class RenameGroupDialog( + val activity: SimpleActivity, + val group: Group, + val callback: () -> Unit, +) { + private val binding = DialogRenameGroupBinding.inflate(activity.layoutInflater).apply { + renameGroupTitle.setText(group.title) + } + + init { + activity.getAlertDialogBuilder() + .setPositiveButton(org.fossify.commons.R.string.ok, null) + .setNegativeButton(org.fossify.commons.R.string.cancel, null) + .apply { + activity.setupDialogStuff(binding.root, this, R.string.rename_group) { alertDialog -> + alertDialog.showKeyboard(binding.renameGroupTitle) + alertDialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener { + val newTitle = binding.renameGroupTitle.value.trim() + if (newTitle.isEmpty()) { + activity.toast(org.fossify.commons.R.string.empty_name) + return@setOnClickListener + } + + activity.dbHelper.updateGroup(Group(group.id, group.ref, newTitle)) + callback() + alertDialog.dismiss() + } + } + } + } +} diff --git a/app/src/main/kotlin/org/fossify/clock/extensions/Context.kt b/app/src/main/kotlin/org/fossify/clock/extensions/Context.kt index de931273..ae957cb1 100644 --- a/app/src/main/kotlin/org/fossify/clock/extensions/Context.kt +++ b/app/src/main/kotlin/org/fossify/clock/extensions/Context.kt @@ -29,6 +29,8 @@ import org.fossify.clock.helpers.AlarmController import org.fossify.clock.helpers.Config import org.fossify.clock.helpers.DBHelper import org.fossify.clock.helpers.EDITED_TIME_ZONE_SEPARATOR +import org.fossify.clock.helpers.ENTRY_ID +import org.fossify.clock.helpers.ENTRY_TYPE import org.fossify.clock.helpers.FORMAT_12H import org.fossify.clock.helpers.FORMAT_24H import org.fossify.clock.helpers.MyAnalogueTimeWidgetProvider @@ -37,6 +39,7 @@ import org.fossify.clock.helpers.NOTIFICATION_ID import org.fossify.clock.helpers.OPEN_ALARMS_TAB_INTENT_ID import org.fossify.clock.helpers.OPEN_STOPWATCH_TAB_INTENT_ID import org.fossify.clock.helpers.OPEN_TAB +import org.fossify.clock.helpers.PendingReenableManager import org.fossify.clock.helpers.TAB_ALARM import org.fossify.clock.helpers.TAB_STOPWATCH import org.fossify.clock.helpers.TAB_TIMER @@ -45,6 +48,7 @@ import org.fossify.clock.helpers.TODAY_BIT import org.fossify.clock.helpers.TOMORROW_BIT import org.fossify.clock.helpers.TimerHelper import org.fossify.clock.helpers.UPCOMING_ALARM_INTENT_ID +import org.fossify.clock.helpers.createPendingReenableRequestCode import org.fossify.clock.helpers.formatTime import org.fossify.clock.helpers.getAllTimeZones import org.fossify.clock.helpers.getDefaultTimeZoneTitle @@ -52,10 +56,12 @@ import org.fossify.clock.helpers.getTimeOfNextAlarm import org.fossify.clock.interfaces.TimerDao import org.fossify.clock.models.Alarm import org.fossify.clock.models.MyTimeZone +import org.fossify.clock.models.PendingReenable import org.fossify.clock.models.Timer import org.fossify.clock.models.TimerState import org.fossify.clock.receivers.AlarmReceiver import org.fossify.clock.receivers.HideTimerReceiver +import org.fossify.clock.receivers.ReenableAlarmReceiver import org.fossify.clock.receivers.SkipUpcomingAlarmReceiver import org.fossify.clock.receivers.StopAlarmReceiver import org.fossify.clock.receivers.UpcomingAlarmReceiver @@ -106,6 +112,9 @@ val Context.alarmManager: AlarmManager val Context.alarmController: AlarmController get() = AlarmController.getInstance(applicationContext) +val Context.pendingReenableManager: PendingReenableManager + get() = PendingReenableManager.getInstance(applicationContext) + fun Context.getFormattedDate(calendar: Calendar): String { val dayOfWeek = (calendar.get(Calendar.DAY_OF_WEEK) + 5) % 7 // make sure index 0 means monday val dayOfMonth = calendar.get(Calendar.DAY_OF_MONTH) @@ -276,6 +285,18 @@ fun Context.getAlarmIntent(alarm: Alarm): PendingIntent { ) } +fun Context.getReenablePendingIntent(job: PendingReenable): PendingIntent { + val intent = Intent(this, ReenableAlarmReceiver::class.java) + intent.putExtra(ENTRY_ID, job.entryId) + intent.putExtra(ENTRY_TYPE, job.entryType) + return PendingIntent.getBroadcast( + this, + createPendingReenableRequestCode(job.entryType, job.entryId), + intent, + PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE + ) +} + fun Context.cancelAlarmClock(alarm: Alarm) { val alarmManager = alarmManager alarmManager.cancel(getAlarmIntent(alarm)) diff --git a/app/src/main/kotlin/org/fossify/clock/fragments/AlarmFragment.kt b/app/src/main/kotlin/org/fossify/clock/fragments/AlarmFragment.kt index 7eb52fc9..20bca312 100644 --- a/app/src/main/kotlin/org/fossify/clock/fragments/AlarmFragment.kt +++ b/app/src/main/kotlin/org/fossify/clock/fragments/AlarmFragment.kt @@ -5,6 +5,7 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.fragment.app.Fragment +import org.fossify.clock.activities.GroupActivity import org.fossify.clock.activities.MainActivity import org.fossify.clock.activities.SimpleActivity import org.fossify.clock.adapters.AlarmsAdapter @@ -26,6 +27,8 @@ import org.fossify.clock.helpers.getTomorrowBit import org.fossify.clock.interfaces.ToggleAlarmInterface import org.fossify.clock.models.Alarm import org.fossify.clock.models.AlarmEvent +import org.fossify.clock.models.AlarmListItem +import org.fossify.clock.models.Group import org.fossify.commons.extensions.beVisibleIf import org.fossify.commons.extensions.getProperBackgroundColor import org.fossify.commons.extensions.getProperTextColor @@ -40,7 +43,7 @@ import org.greenrobot.eventbus.Subscribe import org.greenrobot.eventbus.ThreadMode class AlarmFragment : Fragment(), ToggleAlarmInterface { - private var alarms = ArrayList() + private var alarmItems = ArrayList() private var currentEditAlarmDialog: EditAlarmDialog? = null private lateinit var binding: FragmentAlarmBinding @@ -89,21 +92,23 @@ class AlarmFragment : Fragment(), ToggleAlarmInterface { setupAlarms() } - private fun getSortedAlarms(callback: (alarms: ArrayList) -> Unit) { + private fun getSortedItems(callback: (items: ArrayList) -> Unit) { val safeContext = context ?: return ensureBackgroundThread { - var newAlarms = context?.dbHelper?.getAlarms() - if (newAlarms == null) { - activity?.runOnUiThread { - callback(arrayListOf()) - } + val allAlarms = context?.dbHelper?.getAlarms() + if (allAlarms == null) { + activity?.runOnUiThread { callback(arrayListOf()) } return@ensureBackgroundThread } + val groups = safeContext.dbHelper.getGroups() + val groupedAlarms = allAlarms.filter { it.groupRef != 0 }.groupBy { it.groupRef } + var ungroupedAlarms = allAlarms.filter { it.groupRef == 0 } as ArrayList + when (safeContext.config.alarmSort) { - SORT_BY_ALARM_TIME -> newAlarms.sortBy { it.timeInMinutes } - SORT_BY_DATE_CREATED -> newAlarms.sortBy { it.id } - SORT_BY_DATE_AND_TIME -> newAlarms.sortWith(compareBy { + SORT_BY_ALARM_TIME -> ungroupedAlarms.sortBy { it.timeInMinutes } + SORT_BY_DATE_CREATED -> ungroupedAlarms.sortBy { it.id } + SORT_BY_DATE_AND_TIME -> ungroupedAlarms.sortWith(compareBy { safeContext.firstDayOrder(it.days) }.thenBy { it.timeInMinutes @@ -111,12 +116,12 @@ class AlarmFragment : Fragment(), ToggleAlarmInterface { SORT_BY_CUSTOM -> { val customAlarmsSortOrderString = activity?.config?.alarmsCustomSorting - if (customAlarmsSortOrderString == "") { - newAlarms.sortBy { it.id } + if (customAlarmsSortOrderString.isNullOrEmpty()) { + ungroupedAlarms.sortBy { it.id } } else { val customAlarmsSortOrder: List = - customAlarmsSortOrderString?.split(", ")?.map { it.toInt() }!! - val alarmsIdValueMap = newAlarms.associateBy { it.id } + customAlarmsSortOrderString.split(", ").map { it.toInt() } + val alarmsIdValueMap = ungroupedAlarms.associateBy { it.id } val sortedAlarms: ArrayList = ArrayList() customAlarmsSortOrder.map { id -> @@ -125,31 +130,47 @@ class AlarmFragment : Fragment(), ToggleAlarmInterface { } } - newAlarms = - (sortedAlarms + newAlarms.filter { it !in sortedAlarms }) as ArrayList + ungroupedAlarms = + (sortedAlarms + ungroupedAlarms.filter { it !in sortedAlarms }) as ArrayList } } } + val groupItems = groups + .sortedBy { it.title.lowercase() } + .map { group -> + AlarmListItem.GroupRow( + group = group, + alarms = groupedAlarms[group.ref].orEmpty().sortedBy { it.timeInMinutes } + ) + } + + val combined = ArrayList() + combined.addAll(groupItems) + combined.addAll(ungroupedAlarms.map { AlarmListItem.AlarmRow(it) }) + activity?.runOnUiThread { - callback(newAlarms) + callback(combined) } } } private fun setupAlarms() { - getSortedAlarms { sortedAlarms -> - alarms = sortedAlarms - val safeActivity = activity as? SimpleActivity ?: return@getSortedAlarms + getSortedItems { items -> + alarmItems = items + val safeActivity = activity as? SimpleActivity ?: return@getSortedItems var currAdapter = binding.alarmsList.adapter as? AlarmsAdapter if (currAdapter == null) { currAdapter = AlarmsAdapter( activity = safeActivity, - alarms = alarms, + items = items, toggleAlarmInterface = this, recyclerView = binding.alarmsList ) { - openEditAlarm(it as Alarm) + when (it) { + is Alarm -> openEditAlarm(it) + is Group -> GroupActivity.start(safeActivity, it.ref) + } }.apply { binding.alarmsList.adapter = this } @@ -158,10 +179,10 @@ class AlarmFragment : Fragment(), ToggleAlarmInterface { updatePrimaryColor() updateBackgroundColor(safeActivity.getProperBackgroundColor()) updateTextColor(safeActivity.getProperTextColor()) - updateItems(alarms) + updateItems(items) } } - binding.alarmsPlaceholder.beVisibleIf(alarms.isEmpty()) + binding.alarmsPlaceholder.beVisibleIf(items.isEmpty()) } } @@ -178,8 +199,7 @@ class AlarmFragment : Fragment(), ToggleAlarmInterface { (activity as SimpleActivity).handleFullScreenNotificationsPermission { granted -> if (granted) { if (requireContext().dbHelper.updateAlarmEnabledState(id, isEnabled)) { - val alarm = alarms.firstOrNull { it.id == id } - ?: return@handleFullScreenNotificationsPermission + val alarm = findAlarmById(id) ?: return@handleFullScreenNotificationsPermission alarm.isEnabled = isEnabled checkAlarmState(alarm) if (!alarm.isEnabled && alarm.oneShot) { @@ -196,10 +216,20 @@ class AlarmFragment : Fragment(), ToggleAlarmInterface { } } + private fun findAlarmById(id: Int): Alarm? { + alarmItems.forEach { item -> + when (item) { + is AlarmListItem.AlarmRow -> if (item.alarm.id == id) return item.alarm + is AlarmListItem.GroupRow -> item.alarms.firstOrNull { it.id == id }?.let { return it } + } + } + return null + } + private fun checkAlarmState(alarm: Alarm) { val activity = activity as? MainActivity ?: return if (alarm.isEnabled) { - activity.alarmController.scheduleNextOccurrence(alarm = alarm, showToasts = true) + activity.alarmController.scheduleNextOccurrence(alarm = alarm) } else { activity.cancelAlarmClock(alarm) } diff --git a/app/src/main/kotlin/org/fossify/clock/helpers/Config.kt b/app/src/main/kotlin/org/fossify/clock/helpers/Config.kt index 1839bfcc..119e062a 100644 --- a/app/src/main/kotlin/org/fossify/clock/helpers/Config.kt +++ b/app/src/main/kotlin/org/fossify/clock/helpers/Config.kt @@ -7,6 +7,7 @@ import org.fossify.clock.extensions.gson.gson import org.fossify.clock.models.Alarm import org.fossify.clock.models.ObfuscatedAlarm import org.fossify.clock.models.ObfuscatedTimer +import org.fossify.clock.models.PendingReenable import org.fossify.clock.models.Timer import org.fossify.commons.extensions.getDefaultAlarmSound import org.fossify.commons.extensions.getDefaultAlarmTitle @@ -150,4 +151,18 @@ class Config(context: Context) : BaseConfig(context) { set(migrateFirstDayOfWeek) = prefs.edit { putBoolean(MIGRATE_FIRST_DAY_OF_WEEK, migrateFirstDayOfWeek) } + + var promptEntryType: Int + get() = prefs.getInt(PROMPT_ENTRY_TYPE, ENTRY_TYPE_NONE) + set(promptEntryType) = prefs.edit { putInt(PROMPT_ENTRY_TYPE, promptEntryType) } + + var promptEntryId: Int + get() = prefs.getInt(PROMPT_ENTRY_ID, -1) + set(promptEntryId) = prefs.edit { putInt(PROMPT_ENTRY_ID, promptEntryId) } + + var pendingReenables: List + get() = prefs.getString(PENDING_REENABLES, null)?.let { + gson.fromJson(it, Array::class.java).toList() + } ?: emptyList() + set(value) = prefs.edit { putString(PENDING_REENABLES, gson.toJson(value)) } } diff --git a/app/src/main/kotlin/org/fossify/clock/helpers/Constants.kt b/app/src/main/kotlin/org/fossify/clock/helpers/Constants.kt index 2d812c03..61892bca 100644 --- a/app/src/main/kotlin/org/fossify/clock/helpers/Constants.kt +++ b/app/src/main/kotlin/org/fossify/clock/helpers/Constants.kt @@ -104,6 +104,16 @@ const val FORMAT_24H = "HH:mm" const val FORMAT_12H_WITH_SECONDS = "h:mm:ss a" const val FORMAT_24H_WITH_SECONDS = "HH:mm:ss" +// alarm reenable +const val PROMPT_ENTRY_TYPE = "prompt_entry_type" +const val PROMPT_ENTRY_ID = "prompt_entry_id" +const val PENDING_REENABLES = "pending_reenables" +const val ENTRY_TYPE = "entry_type" +const val ENTRY_ID = "entry_id" +const val ENTRY_TYPE_NONE = 0 +const val ENTRY_TYPE_ALARM = 1 +const val ENTRY_TYPE_GROUP = 2 + private val DAY_BIT_MAP = mapOf( Calendar.SUNDAY to SUNDAY_BIT, Calendar.MONDAY to MONDAY_BIT, @@ -311,3 +321,29 @@ fun updateNonRecurringAlarmDay(alarm: Alarm) { TOMORROW_BIT } } + +fun getLastRelevantOccurrence(alarm: Alarm): Calendar { + val referenceTime = Calendar.getInstance().apply { + set(Calendar.HOUR_OF_DAY, alarm.timeInMinutes / 60) + set(Calendar.MINUTE, alarm.timeInMinutes % 60) + set(Calendar.SECOND, 0) + set(Calendar.MILLISECOND, 0) + } + + return when (alarm.days) { + TODAY_BIT -> referenceTime + TOMORROW_BIT -> referenceTime.apply { add(Calendar.DAY_OF_MONTH, 1) } + else -> { + repeat(7) { + val currentDay = getDayNumber(referenceTime.get(Calendar.DAY_OF_WEEK)) + if (alarm.days.isBitSet(currentDay)) { + return referenceTime + } + referenceTime.add(Calendar.DAY_OF_MONTH, 1) + } + referenceTime + } + } +} + +fun createPendingReenableRequestCode(entryType: Int, entryId: Int) = entryType * 100_000 + entryId diff --git a/app/src/main/kotlin/org/fossify/clock/helpers/DBHelper.kt b/app/src/main/kotlin/org/fossify/clock/helpers/DBHelper.kt index b4097599..bab3cd68 100644 --- a/app/src/main/kotlin/org/fossify/clock/helpers/DBHelper.kt +++ b/app/src/main/kotlin/org/fossify/clock/helpers/DBHelper.kt @@ -10,6 +10,7 @@ import android.text.TextUtils import org.fossify.clock.extensions.cancelAlarmClock import org.fossify.clock.extensions.createNewAlarm import org.fossify.clock.models.Alarm +import org.fossify.clock.models.Group import org.fossify.commons.extensions.getIntValue import org.fossify.commons.extensions.getStringValue import org.fossify.commons.helpers.FRIDAY_BIT @@ -34,11 +35,16 @@ class DBHelper private constructor( private val COL_SOUND_URI = "sound_uri" private val COL_LABEL = "label" private val COL_ONE_SHOT = "one_shot" + private val GROUPS_TABLE_NAME = "alarm_groups" + private val COL_GROUP_ID = "group_id" + private val COL_GROUP_REF = "group_ref" + private val COL_GROUP_TITLE = "title" + private val mDb = writableDatabase companion object { - private const val DB_VERSION = 2 + private const val DB_VERSION = 3 const val DB_NAME = "alarms.db" @SuppressLint("StaticFieldLeak") @@ -55,16 +61,30 @@ class DBHelper private constructor( override fun onCreate(db: SQLiteDatabase) { db.execSQL( - "CREATE TABLE IF NOT EXISTS $ALARMS_TABLE_NAME ($COL_ID INTEGER PRIMARY KEY AUTOINCREMENT, $COL_TIME_IN_MINUTES INTEGER, $COL_DAYS INTEGER, " + - "$COL_IS_ENABLED INTEGER, $COL_VIBRATE INTEGER, $COL_SOUND_TITLE TEXT, $COL_SOUND_URI TEXT, $COL_LABEL TEXT, $COL_ONE_SHOT INTEGER)" + "CREATE TABLE IF NOT EXISTS $ALARMS_TABLE_NAME (" + + "$COL_ID INTEGER PRIMARY KEY AUTOINCREMENT, $COL_TIME_IN_MINUTES INTEGER, $COL_DAYS INTEGER, " + + "$COL_IS_ENABLED INTEGER, $COL_VIBRATE INTEGER, $COL_SOUND_TITLE TEXT, $COL_SOUND_URI TEXT, " + + "$COL_LABEL TEXT, $COL_ONE_SHOT INTEGER, $COL_GROUP_ID INTEGER)" ) + createGroupsTable(db) insertInitialAlarms(db) } + private fun createGroupsTable(db: SQLiteDatabase) { + db.execSQL( + "CREATE TABLE IF NOT EXISTS $GROUPS_TABLE_NAME ($COL_ID INTEGER PRIMARY KEY AUTOINCREMENT, " + + "$COL_GROUP_REF INTEGER, $COL_GROUP_TITLE TEXT)" + ) + } + override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) { if (oldVersion == 1 && newVersion > oldVersion) { db.execSQL("ALTER TABLE $ALARMS_TABLE_NAME ADD COLUMN $COL_ONE_SHOT INTEGER NOT NULL DEFAULT 0") } + if (oldVersion < 3) { + db.execSQL("ALTER TABLE $GROUPS_TABLE_NAME ADD COLUMN $COL_GROUP_REF INTEGER DEFAULT 0") + createGroupsTable(db) + } } private fun insertInitialAlarms(db: SQLiteDatabase) { @@ -107,6 +127,71 @@ class DBHelper private constructor( mDb.delete(ALARMS_TABLE_NAME, selection, null) } + fun insertGroup(title: String, ref: Int = 0): Int { + val values = ContentValues().apply { + put(COL_GROUP_TITLE, title) + } + val res = mDb.insert(GROUPS_TABLE_NAME, null, values).toInt() + if (res == -1) { + return -1 + } + + val finalRef = if (ref == 0) res else ref + if (ref == 0) { + updateGroup(Group(res, res, title)) + } else { + updateGroup(Group(res, ref, title)) + } + + return finalRef + } + + fun updateGroup(group: Group) { + val values = ContentValues().apply { + put(COL_GROUP_TITLE, group.title) + put(COL_GROUP_REF, group.ref) } + mDb.update(GROUPS_TABLE_NAME, values, "$COL_ID = ?", arrayOf(group.id.toString())) + } + + fun deleteGroup(groupRef: Int) { + val values = ContentValues().apply { putNull(COL_GROUP_ID) } + mDb.update(ALARMS_TABLE_NAME, values, "$COL_GROUP_ID = ?", arrayOf(groupRef.toString())) + mDb.delete(GROUPS_TABLE_NAME, "$COL_GROUP_REF = ?", arrayOf(groupRef.toString())) + } + fun getGroups(): ArrayList { + val groups = ArrayList() + val cursor = mDb.query(GROUPS_TABLE_NAME, arrayOf(COL_ID, COL_GROUP_REF, COL_GROUP_TITLE), + null, null, null, null, null) + cursor.use { + if (it.moveToFirst()){ + do { + groups.add(Group(id = it.getIntValue(COL_ID), ref = it.getIntValue(COL_GROUP_REF), + title = it.getStringValue(COL_GROUP_TITLE))) + } while (it.moveToNext()) + } + } + + return groups + } + + fun assignAlarmsToGroup(alarmIds: List, groupRef: Int) { + val values = ContentValues().apply { + if (groupRef == 0) { + putNull(COL_GROUP_ID) + } else { + put(COL_GROUP_ID, groupRef) + } + } + val args = TextUtils.join(", ", alarmIds) + mDb.update(ALARMS_TABLE_NAME, values, "$COL_ID IN ($args)", null) + } + + fun updateGroupEnabledState(groupRef: Int, isEnabled: Boolean): List { + val values = ContentValues().apply { put(COL_IS_ENABLED, isEnabled) } + mDb.update(ALARMS_TABLE_NAME, values, "$COL_GROUP_ID = ?", arrayOf(groupRef.toString())) + return getAlarms().filter { it.groupRef == groupRef } + } + fun getAlarmWithId(id: Int) = getAlarms().firstOrNull { it.id == id } fun getAlarmsWithUri(uri: String) = getAlarms().filter { it.soundUri == uri } @@ -121,6 +206,7 @@ class DBHelper private constructor( put(COL_SOUND_URI, alarm.soundUri) put(COL_LABEL, alarm.label) put(COL_ONE_SHOT, alarm.oneShot) + put(COL_GROUP_ID, alarm.groupRef) } } @@ -137,7 +223,8 @@ class DBHelper private constructor( COL_SOUND_TITLE, COL_SOUND_URI, COL_LABEL, - COL_ONE_SHOT + COL_ONE_SHOT, + COL_GROUP_ID ) var cursor: Cursor? = null try { @@ -154,6 +241,7 @@ class DBHelper private constructor( val soundUri = cursor.getStringValue(COL_SOUND_URI) val label = cursor.getStringValue(COL_LABEL) val oneShot = cursor.getIntValue(COL_ONE_SHOT) == 1 + val groupId = cursor.getIntValue(COL_GROUP_ID) val alarm = Alarm( id = id, @@ -164,7 +252,8 @@ class DBHelper private constructor( soundTitle = soundTitle, soundUri = soundUri, label = label, - oneShot = oneShot + oneShot = oneShot, + groupRef = groupId ) alarms.add(alarm) } catch (e: Exception) { diff --git a/app/src/main/kotlin/org/fossify/clock/helpers/ImportHelper.kt b/app/src/main/kotlin/org/fossify/clock/helpers/ImportHelper.kt index bcb524d7..e8bd2919 100644 --- a/app/src/main/kotlin/org/fossify/clock/helpers/ImportHelper.kt +++ b/app/src/main/kotlin/org/fossify/clock/helpers/ImportHelper.kt @@ -6,6 +6,7 @@ import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.json.Json import kotlinx.serialization.json.decodeFromStream import org.fossify.clock.models.Alarm +import org.fossify.clock.models.Group import org.fossify.clock.models.AlarmTimerBackup import org.fossify.clock.models.Timer import org.fossify.commons.extensions.showErrorToast @@ -27,11 +28,13 @@ class ImportHelper( return try { context.contentResolver.openInputStream(uri)?.use { inputStream -> val backup = Json.decodeFromStream(inputStream) - val importedAlarms = insertAlarms(backup.alarms) + val importedGroups = insertGroups(backup.groups) + val updatedAlarms = updateAlarmGroupIds(backup.alarms, backup.groups) + val importedAlarms = insertAlarms(updatedAlarms) val importedTimers = insertTimers(backup.timers) when { - importedAlarms > 0 || importedTimers > 0 -> ImportResult.IMPORT_OK - importedAlarms == 0 && importedTimers == 0 -> ImportResult.IMPORT_INCOMPLETE + importedGroups > 0 || importedAlarms > 0 || importedTimers > 0 -> ImportResult.IMPORT_OK + importedGroups == 0 && importedAlarms == 0 && importedTimers == 0 -> ImportResult.IMPORT_INCOMPLETE else -> ImportResult.IMPORT_FAIL } } ?: ImportResult.IMPORT_FAIL @@ -45,7 +48,9 @@ class ImportHelper( val existingAlarms = dbHelper.getAlarms() var insertedCount = 0 alarms.forEach { alarm -> - if (!isAlarmAlreadyInserted(alarm, existingAlarms)) { + if (isAlarmAlreadyInserted(alarm, existingAlarms)) { + dbHelper.updateAlarm(alarm) + } else { if (dbHelper.insertAlarm(alarm) != -1) { insertedCount++ } @@ -54,6 +59,37 @@ class ImportHelper( return insertedCount } + private fun updateAlarmGroupIds(alarms: List, backupGroups: List): List { + val localGroups = dbHelper.getGroups() + alarms.forEach { alarm -> + if (alarm.groupRef != 0) { + val backupGroup = backupGroups.find { it.ref == alarm.groupRef } + if (backupGroup != null) { + val localGroup = localGroups.find { it.title == backupGroup.title } + if (localGroup != null) { + alarm.groupRef = localGroup.ref + } + } + } + } + + return alarms + } + + private fun insertGroups(groups: List): Int { + val existingGroups = dbHelper.getGroups() + var insertedCount = 0 + groups.forEach { group -> + if (!isGroupInserted(group, existingGroups)){ + val newId = dbHelper.insertGroup(group.title, group.ref) + if (newId != -1){ + insertedCount++ + } + } + } + return insertedCount + } + private fun insertTimers(timers: List): Int { var insertedCount = 0 timers.forEach { timer -> @@ -85,6 +121,16 @@ class ImportHelper( alarm.label == existingAlarm.label && alarm.oneShot == existingAlarm.oneShot ) { + alarm.id = existingAlarm.id + return true + } + } + return false + } + + private fun isGroupInserted(group: Group, existingGroups: List): Boolean{ + for (existingGroup in existingGroups) { + if (group.title == existingGroup.title) { return true } } diff --git a/app/src/main/kotlin/org/fossify/clock/helpers/PendingReenableManager.kt b/app/src/main/kotlin/org/fossify/clock/helpers/PendingReenableManager.kt new file mode 100644 index 00000000..a50280c5 --- /dev/null +++ b/app/src/main/kotlin/org/fossify/clock/helpers/PendingReenableManager.kt @@ -0,0 +1,119 @@ +package org.fossify.clock.helpers + +import android.app.AlarmManager +import android.app.Application +import android.content.Context +import androidx.core.app.AlarmManagerCompat +import org.fossify.clock.extensions.alarmController +import org.fossify.clock.extensions.alarmManager +import org.fossify.clock.extensions.config +import org.fossify.clock.extensions.dbHelper +import org.fossify.clock.extensions.getReenablePendingIntent +import org.fossify.clock.models.Alarm +import org.fossify.clock.models.PendingReenable +import kotlin.time.Clock + +class PendingReenableManager( + private val context: Application, + private val db: DBHelper, + private val config: Config, +) { + fun onManuallyDisabled(entryType: Int, entryId: Int) { + config.promptEntryType = entryType + config.promptEntryId = entryId + } + + fun onManuallyEnabled(entryType: Int, entryId: Int) { + clearPromptIfMatching(entryType, entryId) + cancelPendingReenable(entryType, entryId) + } + + fun isPromptVisible(entryType: Int, entryId: Int): Boolean { + return config.promptEntryType == entryType && config.promptEntryId == entryId + } + + fun confirmReenable(entryType: Int, entryId: Int, affectedAlarms: List) { + val triggerAtMillis = affectedAlarms.maxOfOrNull { getLastRelevantOccurrence(it).timeInMillis } ?: return + if (triggerAtMillis == 0L) return + + val pendingReenable = PendingReenable( + entryType = entryType, + entryId = entryId, + alarmIds = affectedAlarms.map { it.id }, + triggerAtMillis = triggerAtMillis + ) + + config.pendingReenables = config.pendingReenables.filterNot { it.entryType == entryType && it.entryId == entryId } + pendingReenable + + AlarmManagerCompat.setExactAndAllowWhileIdle( + context.alarmManager, + AlarmManager.RTC_WAKEUP, + triggerAtMillis, + context.getReenablePendingIntent(pendingReenable) + ) + + clearPromptIfMatching(entryType, entryId) + } + + fun reprocessPendingReenables() { + val now = System.currentTimeMillis() + val (due, notYetDue) = config.pendingReenables.partition { it.triggerAtMillis <= now } + + due.forEach { pending -> enableAlarmIds(context, pending.alarmIds) } + + notYetDue.forEach { pending -> + AlarmManagerCompat.setExactAndAllowWhileIdle( + context.alarmManager, + AlarmManager.RTC_WAKEUP, + pending.triggerAtMillis, + context.getReenablePendingIntent(pending) + ) + } + + config.pendingReenables = notYetDue + } + + private fun clearPromptIfMatching(entryType: Int, entryId: Int) { + if (config.promptEntryType == entryType && config.promptEntryId == entryId) { + config.promptEntryType = ENTRY_TYPE_NONE + config.promptEntryId = -1 + } + } + + private fun cancelPendingReenable(entryType: Int, entryId: Int) { + val pending = config.pendingReenables + .find { it.entryType == entryType && it.entryId == entryId } ?: return + + context.alarmManager.cancel(context.getReenablePendingIntent(pending)) + config.pendingReenables = config.pendingReenables.filterNot { it == pending } + } + + companion object { + @Volatile + private var instance: PendingReenableManager? = null + + fun getInstance(context: Context): PendingReenableManager { + val appContext = context.applicationContext as Application + return instance ?: synchronized(this) { + instance ?: PendingReenableManager( + context = appContext, + db = appContext.dbHelper, + config = appContext.config + ).also { instance = it } + } + } + + fun enableAlarmIds(context: Context, alarmIds: List) { + alarmIds.forEach { alarmId -> + context.dbHelper.getAlarmWithId(alarmId)?.let { alarm -> + alarm.isEnabled = true + if (!alarm.isRecurring()) { + updateNonRecurringAlarmDay(alarm) + } + context.dbHelper.updateAlarm(alarm) + context.alarmController.scheduleNextOccurrence(alarm) + } + } + } + } +} diff --git a/app/src/main/kotlin/org/fossify/clock/models/Alarm.kt b/app/src/main/kotlin/org/fossify/clock/models/Alarm.kt index 263ca1fa..b2d658a2 100644 --- a/app/src/main/kotlin/org/fossify/clock/models/Alarm.kt +++ b/app/src/main/kotlin/org/fossify/clock/models/Alarm.kt @@ -16,6 +16,7 @@ data class Alarm( var soundUri: String, var label: String, var oneShot: Boolean = false, + var groupRef: Int = 0, ) { fun isRecurring() = days > 0 diff --git a/app/src/main/kotlin/org/fossify/clock/models/AlarmListItem.kt b/app/src/main/kotlin/org/fossify/clock/models/AlarmListItem.kt new file mode 100644 index 00000000..7b64ce22 --- /dev/null +++ b/app/src/main/kotlin/org/fossify/clock/models/AlarmListItem.kt @@ -0,0 +1,6 @@ +package org.fossify.clock.models + +sealed class AlarmListItem { + data class AlarmRow(val alarm: Alarm) : AlarmListItem() + data class GroupRow(val group: Group, val alarms: List) : AlarmListItem() +} diff --git a/app/src/main/kotlin/org/fossify/clock/models/AlarmTimerBackup.kt b/app/src/main/kotlin/org/fossify/clock/models/AlarmTimerBackup.kt index c7deac16..0a0486f2 100644 --- a/app/src/main/kotlin/org/fossify/clock/models/AlarmTimerBackup.kt +++ b/app/src/main/kotlin/org/fossify/clock/models/AlarmTimerBackup.kt @@ -6,5 +6,6 @@ import androidx.annotation.Keep @kotlinx.serialization.Serializable data class AlarmTimerBackup( val alarms: List, + val groups: List, val timers: List, ) diff --git a/app/src/main/kotlin/org/fossify/clock/models/Group.kt b/app/src/main/kotlin/org/fossify/clock/models/Group.kt new file mode 100644 index 00000000..f68e4038 --- /dev/null +++ b/app/src/main/kotlin/org/fossify/clock/models/Group.kt @@ -0,0 +1,13 @@ +package org.fossify.clock.models + +import androidx.annotation.Keep +import kotlinx.serialization.KSerializer + +@Keep +@kotlinx.serialization.Serializable +data class Group ( + var id: Int, + var ref: Int, + var title: String, +) { +} diff --git a/app/src/main/kotlin/org/fossify/clock/models/PendingReenable.kt b/app/src/main/kotlin/org/fossify/clock/models/PendingReenable.kt new file mode 100644 index 00000000..c97d86eb --- /dev/null +++ b/app/src/main/kotlin/org/fossify/clock/models/PendingReenable.kt @@ -0,0 +1,12 @@ +package org.fossify.clock.models + +import androidx.annotation.Keep + +@Keep +@kotlinx.serialization.Serializable +data class PendingReenable( + var entryType: Int, + var entryId: Int, + var alarmIds: List, + var triggerAtMillis: Long, +) diff --git a/app/src/main/kotlin/org/fossify/clock/receivers/ReenableAlarmReceiver.kt b/app/src/main/kotlin/org/fossify/clock/receivers/ReenableAlarmReceiver.kt new file mode 100644 index 00000000..f1de56fe --- /dev/null +++ b/app/src/main/kotlin/org/fossify/clock/receivers/ReenableAlarmReceiver.kt @@ -0,0 +1,40 @@ +package org.fossify.clock.receivers + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import org.fossify.clock.extensions.alarmController +import org.fossify.clock.extensions.config +import org.fossify.clock.extensions.dbHelper +import org.fossify.clock.extensions.goAsync +import org.fossify.clock.extensions.updateWidgets +import org.fossify.clock.helpers.ENTRY_ID +import org.fossify.clock.helpers.ENTRY_TYPE +import org.fossify.clock.helpers.PendingReenableManager.Companion.enableAlarmIds +import org.fossify.clock.helpers.updateNonRecurringAlarmDay +import org.fossify.clock.models.AlarmEvent +import org.greenrobot.eventbus.EventBus + +/* +* Receiver responsible for *ENABLING* alarms after being turned off for one day +*/ +class ReenableAlarmReceiver : BroadcastReceiver() { + override fun onReceive(context: Context, intent: Intent) { + val id = intent.getIntExtra(ENTRY_ID, -1) + val type = intent.getIntExtra(ENTRY_TYPE, -1) + if (id != -1 && type != -1) { + goAsync { + val job = context.config.pendingReenables + .find { it.entryId == id && it.entryType == type } ?: return@goAsync + + enableAlarmIds(context, job.alarmIds) + + context.config.pendingReenables = context.config.pendingReenables + .filterNot { it.entryId == id && it.entryType == type } + + EventBus.getDefault().post(AlarmEvent.Refresh) + context.updateWidgets() + } + } + } +} diff --git a/app/src/main/kotlin/org/fossify/clock/receivers/RescheduleAlarmsReceiver.kt b/app/src/main/kotlin/org/fossify/clock/receivers/RescheduleAlarmsReceiver.kt index ac207d11..2b6b3eaa 100644 --- a/app/src/main/kotlin/org/fossify/clock/receivers/RescheduleAlarmsReceiver.kt +++ b/app/src/main/kotlin/org/fossify/clock/receivers/RescheduleAlarmsReceiver.kt @@ -6,6 +6,7 @@ import android.content.Context import android.content.Intent import org.fossify.clock.extensions.alarmController import org.fossify.clock.extensions.goAsync +import org.fossify.clock.extensions.pendingReenableManager /** * Receiver responsible for rescheduling alarms in background. @@ -16,6 +17,7 @@ class RescheduleAlarmsReceiver : BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) { goAsync { context.alarmController.rescheduleEnabledAlarms() + context.pendingReenableManager.reprocessPendingReenables() } } } diff --git a/app/src/main/res/layout/activity_group.xml b/app/src/main/res/layout/activity_group.xml new file mode 100644 index 00000000..851b44a5 --- /dev/null +++ b/app/src/main/res/layout/activity_group.xml @@ -0,0 +1,76 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/dialog_add_to_group.xml b/app/src/main/res/layout/dialog_add_to_group.xml new file mode 100644 index 00000000..6a39a996 --- /dev/null +++ b/app/src/main/res/layout/dialog_add_to_group.xml @@ -0,0 +1,25 @@ + + + + + + + + + + diff --git a/app/src/main/res/layout/dialog_rename_group.xml b/app/src/main/res/layout/dialog_rename_group.xml new file mode 100644 index 00000000..5c6a899d --- /dev/null +++ b/app/src/main/res/layout/dialog_rename_group.xml @@ -0,0 +1,25 @@ + + + + + + + + + diff --git a/app/src/main/res/layout/item_alarm.xml b/app/src/main/res/layout/item_alarm.xml index 3a0db327..ac4bf23c 100644 --- a/app/src/main/res/layout/item_alarm.xml +++ b/app/src/main/res/layout/item_alarm.xml @@ -54,6 +54,27 @@ app:layout_constraintTop_toBottomOf="@id/alarm_days" tools:text="Good morning!" /> + + + + + + + + + + + + + + + diff --git a/app/src/main/res/menu/cab_alarms.xml b/app/src/main/res/menu/cab_alarms.xml index 9e2f2147..0e159764 100644 --- a/app/src/main/res/menu/cab_alarms.xml +++ b/app/src/main/res/menu/cab_alarms.xml @@ -1,6 +1,11 @@ + + + + diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml index ad6258aa..fe50fb30 100644 --- a/app/src/main/res/values-ar/strings.xml +++ b/app/src/main/res/values-ar/strings.xml @@ -54,7 +54,19 @@ تصدير المنبهات والمؤقتات استيراد المنبهات والمؤقتات غير مجدول + هل نعيد تشغيله غدًا؟ تنبيه فائت تم استبداله بمنبه آخر مدة المؤقِّت + أضف إلى المجموعة + مجموعة جديدة + اسم المجموعة + حذف المجموعة + إعادة تسمية المجموعة + هل تريد حذف هذه المجموعة؟ سيتم إعادة تنبيهاتها إلى القائمة الرئيسية. + + منبه %d + %d منبهاتs + + هناك مجموعة تحمل هذا الاسم بالفعل diff --git a/app/src/main/res/values-az/strings.xml b/app/src/main/res/values-az/strings.xml index decd87f9..16bf40e8 100644 --- a/app/src/main/res/values-az/strings.xml +++ b/app/src/main/res/values-az/strings.xml @@ -19,4 +19,16 @@ Sayğaç bölməsi Saniyələri göstər Səsi aramla yüksəlt + Qrupa əlavə et + Yeni qrup + Qrup adı + Qrupu sil + Qrupun adını dəyişdir + Bu qrupu silin? Onun siqnalları əsas siyahıya qaytarılacaq. + + %d alarm + %d alarms + + Bu adla artıq bir qrup mövcuddur + Sabah yenidən yandırım? diff --git a/app/src/main/res/values-be/strings.xml b/app/src/main/res/values-be/strings.xml index ea5d663d..2d55442d 100644 --- a/app/src/main/res/values-be/strings.xml +++ b/app/src/main/res/values-be/strings.xml @@ -47,6 +47,7 @@ Выберыце таймер для адключэння Выберыце будзільнік для адключэння Будзільнік створаны + Заўтра зноў уключыць? Прапушчаны будзільнік Дадаць будзільнік Дадаць таймер @@ -55,4 +56,15 @@ Час дзеяння будзільніка сышоў Імпартаваць будзільнікі і таймеры Экспартаваць будзільнікі і таймеры + Дадаць у групу + Новая група + Назва групы + Выдаліць групу + Перайменаваць групу + Выдаліць гэту групу? Яе абвесткі будуць вернуты ў асноўны спіс. + + %d ы трывожны сігнал + %d трывог + + Ужо існуе група з такой назвай. diff --git a/app/src/main/res/values-bg/strings.xml b/app/src/main/res/values-bg/strings.xml index 2557dc8e..b304c46a 100644 --- a/app/src/main/res/values-bg/strings.xml +++ b/app/src/main/res/values-bg/strings.xml @@ -51,6 +51,18 @@ Продължителност на отброяването Не планирано Времето за звънене на будилника е изтекло + Да го включим отново утре? Пропуснат будилник Заменен от друг будилник + Добави към групата + Нова група + Име на групата + Изтриване на група + Преименуване на група + Да изтриете тази група ли? Нейните аларми ще бъдат преместени обратно в основния списък. + + %d аларма + %d аларми + + Вече съществува група с това име diff --git a/app/src/main/res/values-br/strings.xml b/app/src/main/res/values-br/strings.xml index e533ae13..fdb7e629 100644 --- a/app/src/main/res/values-br/strings.xml +++ b/app/src/main/res/values-br/strings.xml @@ -52,4 +52,16 @@ Penaos e c\'hallan cheñch urzh rummañ an troioù en ivinell padventer? Klikit war unan eus ar bannoù evit rummañ an troioù hervez ar bann-mañ. Gant klikoù ouzhpenn e c\'hallit trec\'haoliñ etre urzh rummañ war-gresk ha war-zigresk. Padelezh ar munuter + Ouzhpennañ d\'ar strollad + Strollad nevez + Anv ar strollad + Dilemel ar strollad + Adanvañ ar strollad + Dilemel ar strollad-mañ ? Adkaset e vo e evezhiadennoù d\'al listenn bennañ. + + %d diwall + %d diwalls + + Ur strollad gant an anv-se a zo dija. + Adlañsañ warc\'hoazh? diff --git a/app/src/main/res/values-ca/strings.xml b/app/src/main/res/values-ca/strings.xml index 85b04969..f70af234 100644 --- a/app/src/main/res/values-ca/strings.xml +++ b/app/src/main/res/values-ca/strings.xml @@ -51,7 +51,19 @@ Importa alarmes i temporitzadors Durada del temporitzador No planificada + Torna-ho a engegar demà? Alarma desatesa Substituïda per una altra alarma L\'alarma ha excedit el temps d\'espera + Afegeix al grup + Grup nou + Nom del grup + Suprimeix grup + Canvia el nom del grup + Eliminar aquest grup? Les seves alarmes es tornaran a traslladar a la llista principal. + + Alerta %d + %d alarmes + + Ja existeix un grup amb aquest nom. diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index 46020710..49cdf99a 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -51,7 +51,19 @@ Exportovat budíky a časovače Trvání časovače Nenaplánováno + Zítra to zase zapnout? Zmeškaný budík Nahrazen jiným budíkem Budík vypršel + Přidat do skupiny + Nová skupina + Název skupiny + Odstranit skupinu + Přejmenovat skupinu + Chcete tuto skupinu smazat? Její upozornění budou přesunuta zpět do hlavního seznamu. + + %d budík + %d budíků + + Skupina s tímto názvem již existuje diff --git a/app/src/main/res/values-cy/strings.xml b/app/src/main/res/values-cy/strings.xml index 7fb552ca..c0c05954 100644 --- a/app/src/main/res/values-cy/strings.xml +++ b/app/src/main/res/values-cy/strings.xml @@ -28,4 +28,15 @@ Cynyddu uchder sain yn raddol Sut gallaf newid trefn lapiau yn y tab stopwats? Clifia ar unrhyw un o\'r colofnau. Mi fydd hynny\'n ail-drefnu\'r lapiau yn ôl y golofn honno. Gyda chliciau ychwanegol, bydd y drefn yn toglo rhwng esgynnol a disgynnol. + Grŵp newydd + Enw\'r grŵp + Dileu grŵp + Ail-enwi grŵp + Dileu\'r grŵp hwn? Bydd ei larwmau\'n cael eu symud yn ôl i\'r prif restr. + + %d alarm + %d larwm + + Mae grŵp o\'r enw hwnnw eisoes yn bodoli. + Tynnau eto yfory diff --git a/app/src/main/res/values-da/strings.xml b/app/src/main/res/values-da/strings.xml index d3c4bf63..0ce7045e 100644 --- a/app/src/main/res/values-da/strings.xml +++ b/app/src/main/res/values-da/strings.xml @@ -43,6 +43,7 @@ Tilføj timer Kommende alarm Ikke planlagt + Tænd den igen i morgen? Misset alarm Erstattet af en anden alarm Alarmen udløb @@ -53,4 +54,15 @@ %d timer kører %d timere kører + Tilføj til gruppe + Ny gruppe + Gruppe navn + Slet gruppe + Omdøb gruppe + Skal denne gruppe slettes? Dens alarmer flyttes tilbage til hovedlisten. + + %d alarm + %d alarmer + + Der findes allerede en gruppe med det navn diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 95a68637..e552e432 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -50,7 +50,19 @@ Alarme und Timer exportieren Timer-Zeitdauer Nicht geplant + Morgen wieder einschalten? Verpasster Alarm Ersetzt durch einen anderen Alarm Alarm ist abgelaufen + Zur Gruppe hinzufügen + Neue Gruppe + Gruppenname + Gruppe löschen + Gruppe umbenennen + Diese Gruppe löschen? Die darin enthaltenen Alarme werden zurück in die Hauptliste verschoben. + + %d Alarm + %d Alarme + + Eine Gruppe mit diesem Namen existiert bereits diff --git a/app/src/main/res/values-el/strings.xml b/app/src/main/res/values-el/strings.xml index 6ece999d..61c3462b 100644 --- a/app/src/main/res/values-el/strings.xml +++ b/app/src/main/res/values-el/strings.xml @@ -51,5 +51,17 @@ Εισαγωγή ξυπνητηριών και χρονομέτρων Εξαγωγή ξυπνητηριών και χρονομέτρων Διάρκεια χρονομέτρου + Να το ξαναβάλω αύριο; Η αφύπνιση παραλήφθηκε + Προσθήκη στην ομάδα + Νέα ομάδα + Όνομα ομάδας + Διαγραφή ομάδας + Μετονομασία ομάδας + Να διαγραφεί αυτή η ομάδα; Οι ειδοποιήσεις της θα μεταφερθούν ξανά στην κύρια λίστα. + + %d ξυπνητήρι + %d ειδοποιήσεις + + Υπάρχει ήδη μια ομάδα με αυτό το όνομα diff --git a/app/src/main/res/values-en-rGB/strings.xml b/app/src/main/res/values-en-rGB/strings.xml index abfdf865..374010e8 100644 --- a/app/src/main/res/values-en-rGB/strings.xml +++ b/app/src/main/res/values-en-rGB/strings.xml @@ -37,6 +37,7 @@ Add timer Upcoming alarm Not scheduled + Turn on again tomorrow? Missed alarm Replaced by another alarm Alarm timed out @@ -53,4 +54,15 @@ Export alarms and timers How can I change lap sorting at the stopwatch tab? Just click on any of the columns, that will make the laps be sorted by the given column. With additional clicks you can toggle between ascending and descending sorting. + Add to group + New group + Group name + Delete group + Rename group + Delete this group? Its alarms will be moved back to the main list. + + %d alarm + %d alarms + + A group with that name already exists diff --git a/app/src/main/res/values-eo/strings.xml b/app/src/main/res/values-eo/strings.xml index cbccb4cc..a9662d1f 100644 --- a/app/src/main/res/values-eo/strings.xml +++ b/app/src/main/res/values-eo/strings.xml @@ -49,8 +49,20 @@ Importi alarmojn kaj tempomezurilojn Eksporti alarmojn kaj tempomezurilojn Daŭro de tempomezurilo + Ĉu vi ŝaltos ĝin denove morgaŭ? Perdita alarmo Alarmo elĉerpiĝis Ne planita Anstataŭigita de alia alarmo + Aldoni al grupo + Nova grupo + Grupnomo + Forigi grupon + Renomigi grupon + Forigi ĉi tiun grupon? Ĝiaj alarmoj estos movitaj reen al la ĉefa listo. + + %d alarmo + %d alarmoj + + Jam ekzistas grupo kun tiu nomo. diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index d7e47d09..f76069b6 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -50,8 +50,20 @@ Importar alarmas y temporizadores Exportar alarmas y temporizadores Duración de temporizador + Vuelve a encenderlo mañana Alarma perdida Sustituida por otra alarma No programado Alarma caducada + Añadir al grupo + Nuevo grupo + Nombre del grupo + Eliminar grupo + Cambiar el nombre del grupo + ¿Quieres eliminar este grupo? Sus alarmas se volverán a mover a la lista principal. + + Alarma %d + %d alarmas + + Ya existe un grupo con ese nombre diff --git a/app/src/main/res/values-et/strings.xml b/app/src/main/res/values-et/strings.xml index 7f3dec1d..60d01c9d 100644 --- a/app/src/main/res/values-et/strings.xml +++ b/app/src/main/res/values-et/strings.xml @@ -51,6 +51,18 @@ Taimeri kestus Äratus aegus Pole ajastatud + Kas homme jälle sisse lülitada? Märkamata äratus Seda asendas teine äratus + Lisa rühma + Uus rühm + Rühma nimi + Rühma kustutamine + Rühma ümbernimetamine + Kas soovid selle grupi kustutada? Selle äratused viiakse tagasi põhinimekirja. + + %d äratus + %d äratust + + Sellise nimega rühm on juba olemas diff --git a/app/src/main/res/values-eu/strings.xml b/app/src/main/res/values-eu/strings.xml index fddf3fd8..707b888d 100644 --- a/app/src/main/res/values-eu/strings.xml +++ b/app/src/main/res/values-eu/strings.xml @@ -50,7 +50,19 @@ Esportatu alarma eta denboragailuak Tenporizadorearen iraupena Programatu gabe + Bihar berriro piztu? Galdutako alarma Beste alarma batek ordeztu du Alarma iraungita + Taldeari gehitu + Talde berria + Taldearen izena + Taldea ezabatu + Taldearen izena aldatu + Talde hau ezabatu? Bere alerta guztiak berriro zerrenda nagusira eramango dira. + + %d alarma + %d alarma + + Izena horrekin talde bat dagoeneko badago. diff --git a/app/src/main/res/values-fa/strings.xml b/app/src/main/res/values-fa/strings.xml index 18de5af0..25934850 100644 --- a/app/src/main/res/values-fa/strings.xml +++ b/app/src/main/res/values-fa/strings.xml @@ -1,4 +1,16 @@ ساعت زنگی + افزودن به گروه + گروه جدید + نام گروه + حذف گروه + نام گروه را تغییر دهید + حذف این گروه؟ هشدارهای آن به فهرست اصلی بازگردانده خواهند شد. + + هشدار %d + %d هشدار + + گروهی با آن نام از قبل وجود دارد + فردا صبح دوباره روشن کن diff --git a/app/src/main/res/values-fi/strings.xml b/app/src/main/res/values-fi/strings.xml index 7a9d170e..5e7c37a2 100644 --- a/app/src/main/res/values-fi/strings.xml +++ b/app/src/main/res/values-fi/strings.xml @@ -52,5 +52,17 @@ Hälytys päättyi Ei aikataulutettu Toinen hälytys korvannut + Käynnistetäänkö se huomenna uudelleen? Hukattu hälytys + Lisää ryhmään + Uusi ryhmä + Ryhmän nimi + Poista ryhmä + Nimeä ryhmä uudelleen + Haluatko poistaa tämän ryhmän? Sen hälytykset siirretään takaisin pääluetteloon. + + %d hälytys + %d hälytystä + + Tällainen ryhmä on jo olemassa diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index b7bf54b1..d5af7177 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -52,6 +52,18 @@ Importer des alarmes et des timers Non programmé Remplacée par une autre alarme + On le rallume demain ? Alarme manquée Alarme expirée + Ajouter au groupe + Nouveau groupe + Nom du groupe + Supprimer le groupe + Renommer un groupe + Supprimer ce groupe ? Ses alarmes seront replacées dans la liste principale. + + Alarme %d + %d alarmes + + Un groupe portant ce nom existe déjà diff --git a/app/src/main/res/values-ga/strings.xml b/app/src/main/res/values-ga/strings.xml index 4aeb17f7..63e87de3 100644 --- a/app/src/main/res/values-ga/strings.xml +++ b/app/src/main/res/values-ga/strings.xml @@ -38,7 +38,7 @@ Clog analógach Clog digiteach Aláram atá le teacht - Amadóirí ag rith + Amadóirí ag rithr Tá an t- amadóir do %s ag rith Amadóir Nua Cluaisín clog @@ -54,6 +54,18 @@ Fad an lasc ama Chuaigh an t-am ar an aláram amach Gan sceidealú + Besok dinyalakan lagi? Aláram caillte Cuireadh aláram eile ina áit + Cuir leis an ngrúpa + Grúpa nua + Ainm an ghrúpa + Scrios an grúpa + Athainm an ghrúpa + An grúpa seo a scriosadh? Aistrófar a chuid aláraim ar ais go dtí an príomhliosta. + + Alaram %d + %d aláraim + + Tá grúpa leis an ainm sin ann cheana féin. diff --git a/app/src/main/res/values-gl/strings.xml b/app/src/main/res/values-gl/strings.xml index eb1772ec..82ceabbe 100644 --- a/app/src/main/res/values-gl/strings.xml +++ b/app/src/main/res/values-gl/strings.xml @@ -48,9 +48,21 @@ So faga clic en calquera das columnas, iso fará que se ordenen pola columna dada. Con clics adicionais, pode alternar entre clasificación ascendente e descendente. Duración do temporizador Non programado + ¿Volver a acender mañá? Alarma perdida Substituída por outra alarma Alarma caduca Importar alarmas e temporizadores Exportar alarmas e temporizadores + Engadir ao grupo + Novo grupo + Nome do grupo + Eliminar grupo + Renombrar grupo + Eliminar este grupo? As súas alarmas volverán á lista principal. + + Alarma %d + %d alarmas + + Xa existe un grupo con ese nome. diff --git a/app/src/main/res/values-hi/strings.xml b/app/src/main/res/values-hi/strings.xml index 19037ded..2c2f2451 100644 --- a/app/src/main/res/values-hi/strings.xml +++ b/app/src/main/res/values-hi/strings.xml @@ -51,6 +51,18 @@ अलार्म और टाइमर निर्यात करें अलार्म का समय समाप्त अनुसूचित नहीं + क्या कल फिर से चालू करें? अलार्म छूट गया दूसरे अलार्म द्वारा प्रतिस्थापित + समूह में जोड़ें + नया समूह + समूह का नाम + समूह हटाएँ + समूह का नाम बदलें + इस समूह को हटाएँ? इसके अलार्म मुख्य सूची में वापस स्थानांतरित कर दिए जाएँगे। + + %d अलार्म + %d अलार्म + + उस नाम का एक समूह पहले से ही मौजूद है। diff --git a/app/src/main/res/values-hr/strings.xml b/app/src/main/res/values-hr/strings.xml index 59a76de8..47bad2ff 100644 --- a/app/src/main/res/values-hr/strings.xml +++ b/app/src/main/res/values-hr/strings.xml @@ -49,9 +49,21 @@ Timer Trajanje timera Nije planirano + Upaliti ponovno sutra? Propušteni alarm Zamijenjen drugim alarmom Vrijeme alarma je isteklo Uvezi alarme i timere Izvezi alarme i timere + Dodaj u grupu + Nova grupa + Naziv grupe + Izbriši grupu + Preimenuj grupu + Izbrisati ovu grupu? Njezine će se alarme vratiti na glavnu listu. + + %d alarm + %d alarma + + Grupa s tim imenom već postoji diff --git a/app/src/main/res/values-hu/strings.xml b/app/src/main/res/values-hu/strings.xml index 67f78b6c..29e1ece9 100644 --- a/app/src/main/res/values-hu/strings.xml +++ b/app/src/main/res/values-hu/strings.xml @@ -51,6 +51,18 @@ Időzítés időtartama Ébresztő időkorlátja lejárt Nincs ütemezve + Holnap újra bekapcsolod? Kihagyott ébresztés Másik ébresztés helyettesíti + Csoporthoz hozzáadása + Új csoport + Csoport neve + Csoport törlése + Csoport átnevezése + Törölje ezt a csoportot? A hozzá tartozó riasztások visszakerülnek a fő listára. + + %d riasztás + %d riasztás + + Már létezik egy ilyen nevű csoport diff --git a/app/src/main/res/values-ia/strings.xml b/app/src/main/res/values-ia/strings.xml index 8971d392..2b835e72 100644 --- a/app/src/main/res/values-ia/strings.xml +++ b/app/src/main/res/values-ia/strings.xml @@ -26,4 +26,4 @@ Initiar chronometro Horologio analogic Le temporisator esseva stoppate - \ 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 c5c06bc3..9ce63ab2 100644 --- a/app/src/main/res/values-in/strings.xml +++ b/app/src/main/res/values-in/strings.xml @@ -50,6 +50,18 @@ Impor alarm dan pewaktu Ekspor alarm dan pewaktu Belum dijadwalkan + Besok dinyalakan lagi? Alarm terlewat Digantikan oleh alarm lain + Tambahkan ke grup + Grup baru + Nama grup + Hapus grup + Endurnefna hóp + Hapus grup ini? Notifikasi dari grup ini akan dipindahkan kembali ke daftar utama. + + Alarm %d + %d alarm + + Sudah ada grup dengan nama itu diff --git a/app/src/main/res/values-is/strings.xml b/app/src/main/res/values-is/strings.xml index 9b9e4f50..b6d47e0b 100644 --- a/app/src/main/res/values-is/strings.xml +++ b/app/src/main/res/values-is/strings.xml @@ -21,6 +21,7 @@ Engir vekjarar fundust Bæta við vekjara Vekjarar á döfinni + Kveikja aftur á morgun? Viðvaranir sem misst var af Skipt út fyrir annan vekjara Vekjari rann út @@ -31,4 +32,15 @@ Hækka hljóðstyrk jafnt og þétt Hvernig get ég breytt röðun hrigja í skeiðklukkuflipanum? Smellið á hvaða dálk sem er, það mun raða hringjunum eftir þeim dálki. Með því að smella oftar er hægt að breyta milli þess að hafa minnsta eða hæsta gildi eftst. + Bæta við hóp + Ný hópur + Nafn hópsins + Eyða hópi + Csoport átnevezése + Eyða þessum hópi? Viðvaranir hans verða færðar aftur í aðallista. + + %d viðvörun + %d viðvaranir + + Hópur með það nafn er þegar til. diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index 16b85e4f..8cfab4ca 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -51,7 +51,19 @@ Esporta sveglie e timer Durata del timer Non in programma + Lo riaccendiamo domani? Sveglia mancata Sostituita da un\'altra sveglia Sveglia scaduta + Aggiungi al gruppo + Nuovo gruppo + Nome del gruppo + Elimina gruppo + Rinomina gruppo + Elimina questo gruppo? I suoi allarmi verranno riportati nell\'elenco principale. + + %d allarme + %d allarmi + + Esiste già un gruppo con quel nome diff --git a/app/src/main/res/values-iw/strings.xml b/app/src/main/res/values-iw/strings.xml index 8bcabae9..2dd311a3 100644 --- a/app/src/main/res/values-iw/strings.xml +++ b/app/src/main/res/values-iw/strings.xml @@ -52,6 +52,18 @@ התראה קרובה זמן הפעולה של ההתראה הסתיים לא מתוכנן + להדליק שוב מחר? התראה שלא נענתה הוחלף על ידי התראה אחרת + הוסף לקבוצה + קבוצה חדשה + שם הקבוצה + מחק קבוצה + שינוי שם הקבוצה + האם למחוק את הקבוצה הזו? ההתראות שלה יועברו בחזרה לרשימה הראשית. + + %d אזעקה + %d אזעקות + + כבר קיימת קבוצה בשם זה diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index db309b2a..dc1dfb57 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -47,9 +47,21 @@ 消去するタイマーを選択してください ­タイマー時間 スケジュールされていません + 明日また電源を入れる? 見逃したアラーム 他のアラームによって置き換えられました タイムアウトしたアラーム アラームとタイマーをインポートする アラームとタイマーをエクスポートする + グループに追加 + 新しいグループ + グループ名 + グループを削除 + グループの名前を変更する + このグループを削除しますか?このグループのアラームはメインリストに戻されます。 + + %d アラーム + %d 件のアラーム + + その名前のグループはすでに存在します diff --git a/app/src/main/res/values-ko/strings.xml b/app/src/main/res/values-ko/strings.xml index 2add7b78..1b7be9af 100644 --- a/app/src/main/res/values-ko/strings.xml +++ b/app/src/main/res/values-ko/strings.xml @@ -47,5 +47,17 @@ 아무 열이나 누르면, 해당 열에 대해 정렬됩니다. 다시 누르면 오름차순과 내림차순 정렬을 바꿀 수 있습니다. 타이머 길이 예약되지 않음 + 내일 다시 켜볼까? 놓친 알람 + 그룹에 추가 + 새로운 그룹 + 그룹 이름 + 그룹 삭제 + 그룹 이름 변경 + 이 그룹을 삭제하시겠습니까? 이 그룹의 알림은 메인 목록으로 다시 이동됩니다. + + %d 알람 + %d 개의 알람 + + 그런 이름의 그룹이 이미 존재합니다 diff --git a/app/src/main/res/values-lt/strings.xml b/app/src/main/res/values-lt/strings.xml index 208c134c..c5bea466 100644 --- a/app/src/main/res/values-lt/strings.xml +++ b/app/src/main/res/values-lt/strings.xml @@ -24,4 +24,16 @@ Rodyti sekundes Kaip aš galiu pakeisti etapų rikiavimą chronometro auselėje? Spustelėkite ant bet kurio stulpelio, tai surikiuos etapus pagal tą stulpelį. Su papildomais paspaudimais Jūs galite pasirinkti tarp rikiavimo mažėjančia arba didėjančia tvarka. + Pridėti prie grupės + Nauja grupė + Grupės pavadinimas + Ištrinti grupę + Pervardyti grupę + Ar norite ištrinti šią grupę? Jos žadintuvai bus perkelti atgal į pagrindinį sąrašą. + + %d budilka + %d budilkas + + Grupė su tokiu pavadinimu jau egzistuoja + Rytoj vėl įjungti? diff --git a/app/src/main/res/values-lv/strings.xml b/app/src/main/res/values-lv/strings.xml index 5e1b07dd..66acbd73 100644 --- a/app/src/main/res/values-lv/strings.xml +++ b/app/src/main/res/values-lv/strings.xml @@ -3,4 +3,16 @@ Pulkstenis Laika josla Trīcēt + Pievienot grupai + Jauna grupa + Grupas nosaukums + Dzēst grupu + Pārdēvēt grupu + Hunc gregem delere? Monitus eius ad indicem principalem redigentur. + + %d modinātājs + %d modinātāji + + Grupa ar šādu nosaukumu jau pastāv + Rīt atkal ieslēgt? diff --git a/app/src/main/res/values-mk/strings.xml b/app/src/main/res/values-mk/strings.xml index b1ef6a5c..09b61686 100644 --- a/app/src/main/res/values-mk/strings.xml +++ b/app/src/main/res/values-mk/strings.xml @@ -1,4 +1,16 @@ Часовник + Додај во група + Нова група + Име на групата + Избриши група + Преименувај група + Да се избрише оваа група? Нејзините аларми ќе бидат вратени во главната листа. + + %d аларм + %d аларми + + Веќе постои група со тоа име. + Да го вклучиме повторно утре? diff --git a/app/src/main/res/values-ml/strings.xml b/app/src/main/res/values-ml/strings.xml index 5e3d193b..52ea9901 100644 --- a/app/src/main/res/values-ml/strings.xml +++ b/app/src/main/res/values-ml/strings.xml @@ -41,4 +41,16 @@ വോളിയം ക്രമേണ വർദ്ധിപ്പിക്കുക സ്റ്റോപ്പ് വാച്ച് ടാബിൽ ലാപ് സോർട്ടിംഗ് എങ്ങനെ മാറ്റാം? ഏതെങ്കിലും കോളങ്ങളിൽ ക്ലിക്കുചെയ്യുക, അത് നൽകിയ കോളം ഉപയോഗിച്ച് ലാപ്പുകൾ ക്രമീകരിക്കാൻ ഇടയാക്കും. അധിക ക്ലിക്കുകൾ ഉപയോഗിച്ച് നിങ്ങൾക്ക് അസെന്റിങ് ആയും ഡിസെൻഡിങ് ആയും ടോഗിൾ ചെയ്യാൻ സഹായിക്കും. + ഗ്രൂപ്പിൽ ചേർക്കുക + പുതിയ ഗ്രൂപ്പ് + സംഘത്തിൻ്റെ പേര് + ഗ്രൂപ്പ് ഇല്ലാതാക്കുക + ഗ്രൂപ്പ് പുനർനാമകരണം ചെയ്യുക + ഈ ഗ്രൂപ്പ് ഇല്ലാതാക്കണോ? ഇതിന്റെ അലാറങ്ങൾ പ്രധാന ലിസ്റ്റിലേക്ക് തിരികെ മാറ്റും. + + %d അലാറം + %d അലാറങ്ങൾ + + അങ്ങനെയൊരു ഗ്രൂപ്പ് ഇതിനകം നിലവിലുണ്ട് + നാളെ വീണ്ടും ഓൺ ചെയ്യണോ? diff --git a/app/src/main/res/values-my/strings.xml b/app/src/main/res/values-my/strings.xml index 8de6f424..e8c2212a 100644 --- a/app/src/main/res/values-my/strings.xml +++ b/app/src/main/res/values-my/strings.xml @@ -45,4 +45,12 @@ မြည်သံအားတစ်ဆင့်ချင်းမြှင့်ပါ ငါဘယ်လိုစံချိန်ယူနာရီမှာပြန်​ကျော့ထားတယ့်စာရင်းအစီအစဥ်ကိုချိန်းနိုင်မလဲ? ကော်လံတစ်ခု​ပေါ်မှာနှိပ်ပါ သင်နှိပ်လိုက်သည့်​ကော်လံကိုတစ်ခါထပ်နှိပ်ခြင်းဖြင့် ကြီးစဥ်ငယ်လိုက် နဲ့ငယ်စဥ်ကြီးလိုက်စီနိုင်ပါတယ်။ + အုပ်စုထဲထည့်ပါ + အသစ်အဖွဲ့ + အဖွဲ့အမည် + အုပ်စု ဖျက်ရန် + အဖွဲ့အမည်ပြောင်းပါ + Да изтриете тази група ли? Нейните аларми ще бъдат преместени обратно в основния списък. + အဲဒီနာမည်နဲ့အဖွဲ့တစ်ဖွဲ့က ရှိပြီးသားပါ။ + မနက်ဖြန် ထပ်ဖွင့်မလား? diff --git a/app/src/main/res/values-nb-rNO/strings.xml b/app/src/main/res/values-nb-rNO/strings.xml index e59e9166..67a15d1c 100644 --- a/app/src/main/res/values-nb-rNO/strings.xml +++ b/app/src/main/res/values-nb-rNO/strings.xml @@ -46,4 +46,16 @@ Eksporter alarmer og timere Ikke planlagt Alarmen gikk ut + Legg til i gruppe + Ny gruppe + Gruppenavn + Slett gruppe + Gi gruppen nytt navn + Skal denne gruppen slettes? Alarmene i den vil da bli flyttet tilbake til hovedlisten. + + %d alarm + %d alarmer + + Det finnes allerede en gruppe med det navnet + Slå den på igjen i morgen? diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml index 14e07b33..d23d5065 100644 --- a/app/src/main/res/values-nl/strings.xml +++ b/app/src/main/res/values-nl/strings.xml @@ -50,7 +50,19 @@ Wekkers en timers exporteren Tijd Niet ingepland + Morgen weer aanzetten? Wekker gemist Vervangen door andere wekker Wekker afgelopen + Aan groep toevoegen + Nieuwe groep + Groepsnaam + Groep verwijderen + Groep hernoemen + Deze groep verwijderen? De meldingen ervan worden dan teruggezet naar de hoofdlijst. + + %d alarm + %d alarmen + + Er bestaat al een groep met die naam diff --git a/app/src/main/res/values-oc/strings.xml b/app/src/main/res/values-oc/strings.xml index 31b44cd8..c7e95eb7 100644 --- a/app/src/main/res/values-oc/strings.xml +++ b/app/src/main/res/values-oc/strings.xml @@ -30,6 +30,7 @@ Cap de minutador pas trobat Apondre una minutador Alarma per arribar + Reencendre deman matin? Alarma mancada Remplaçada per una autra alarma Alarma expirada @@ -53,4 +54,14 @@ Exportar las alarmas e los minutadors Cossí cambi los torns dins l’onglet cronomètre ? Clicatz sus una de las colomnas, aquò farà que los torns seràn ordenats per la colomna donada. Amb de clics suplementaris podètz alternar entre l\'òrdre creissent e descreissent. + Nòu grop + Nom del grop + Suprimir lo grop + Renomenar lo grop + Suprimir aqueste grop? Sas alarmas seràn tornadas a la lista principala. + + Alarma %d + %d alarmas + + Un grop amb aquel nom ja existís. diff --git a/app/src/main/res/values-pa-rPK/strings.xml b/app/src/main/res/values-pa-rPK/strings.xml index 0601ed83..dd207633 100644 --- a/app/src/main/res/values-pa-rPK/strings.xml +++ b/app/src/main/res/values-pa-rPK/strings.xml @@ -30,4 +30,15 @@ سماں والے دی ٹیب سکنٹ ویکھو ہولی ہولی آواز ودھاؤ + ਨਵਾਂ ਸਮੂਹ + ਸਮੂਹ ਦਾ ਨਾਮ + ਸਮੂਹ ਮਿਟਾਓ + ਸਮੂਹ ਦਾ ਨਾਮ ਬਦਲੋ + ਇਸ ਗਰੁੱਪ ਨੂੰ ਮਿਟਾਓ? ਇਸ ਦੀਆਂ ਅਲਾਰਮਾਂ ਮੁੱਖ ਸੂਚੀ ਵਿੱਚ ਵਾਪਸ ਭੇਜ ਦਿੱਤੀਆਂ ਜਾਣਗੀਆਂ। + + %d ਚੇਤਾਵਨੀ + %d ਚੇਤਾਵਨੀਆਂ + + ਉਸੇ ਨਾਮ ਦਾ ਇੱਕ ਗਰੁੱਪ ਪਹਿਲਾਂ ਹੀ ਮੌਜੂਦ ਹੈ। + ਕੱਲ੍ਹ ਸਵੇਰੇ ਫਿਰ ਜਗਾਓ diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index 884eaf7b..aa5fc868 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -52,7 +52,19 @@ Importuj alarmy i minutniki Czas trwania minutnika Niezaplanowany + Włącz to jutro ponownie Pominięty alarm Zastąpiony innym alarmem Alarm wygasł + Dodaj do grupy + Nowa grupa + Nazwa grupy + Usuń grupę + Zmień nazwę grupy + Czy chcesz usunąć tę grupę? Jej alarmy zostaną przeniesione z powrotem na listę główną. + + %d alarm + %d alarmów + + Grupa o tej nazwie już istnieje diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index 4462089d..5774a8c3 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -52,6 +52,18 @@ Importar alarmes e temporizadores O tempo limite do alarme passou Não agendado + Ligue-o novamente amanhã Alarme perdido Substituído por outro alarme + Adicionar ao grupo + Novo grupo + Nome do grupo + Excluir grupo + Renomear grupo + ਇਸ ਗਰੁੱਪ ਨੂੰ ਮਿਟਾਓ? ਇਸ ਦੀਆਂ ਅਲਾਰਮਾਂ ਮੁੱਖ ਸੂਚੀ ਵਿੱਚ ਵਾਪਸ ਭੇਜ ਦਿੱਤੀਆਂ ਜਾਣਗੀਆਂ। + + Alarme %d + %d alarmes + + Já existe um grupo com esse nome diff --git a/app/src/main/res/values-pt-rPT/strings.xml b/app/src/main/res/values-pt-rPT/strings.xml index 02104103..b9192c2c 100644 --- a/app/src/main/res/values-pt-rPT/strings.xml +++ b/app/src/main/res/values-pt-rPT/strings.xml @@ -51,7 +51,19 @@ Exportar alarmes e temporizadores Basta clicar em qualquer uma das colunas e fará com que as voltas sejam classificadas pela coluna especificada. Com cliques adicionais, pode alternar a ordem de classificação entre crescente e decrescente. Não agendado + Ligue-o novamente amanhã Alarme perdido Substituído por outro alarme Alarme expirou + Adicionar ao grupo + Novo grupo + Nome do grupo + Eliminar grupo + Renomear grupo + Deseja eliminar este grupo? Os seus alarmes serão transferidos de volta para a lista principal. + + Alarme %d + %d alarmes + + Já existe um grupo com esse nome diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml index bd769232..5c9177d6 100644 --- a/app/src/main/res/values-pt/strings.xml +++ b/app/src/main/res/values-pt/strings.xml @@ -36,7 +36,7 @@ Novo temporizador %d temporizador em curso - %d temporizadores em curso + %d temporizadores em cursoala %d temporizadores em curso Relógio @@ -52,6 +52,18 @@ Exportar alarmes e temporizadores Alarme caducado Não agendado + Ligue-o novamente amanhã Alarme perdido Substituído por outro alarme + Adicionar ao grupo + Novo grupo + Nome do grupo + Eliminar grupo + Renomear grupo + Deseja eliminar este grupo? Os seus alarmes serão transferidos de volta para a lista principal. + + Alarme %d + %d alarmes + + Já existe um grupo com esse nome diff --git a/app/src/main/res/values-ro/strings.xml b/app/src/main/res/values-ro/strings.xml index de793e7a..c873f19c 100644 --- a/app/src/main/res/values-ro/strings.xml +++ b/app/src/main/res/values-ro/strings.xml @@ -52,6 +52,18 @@ Importă alarme și cronometre Exportă alarme și cronometre Nu este programat + Pornește-l din nou mâine Alarmă ratată Înlocuită cu o altă alarmă + Adaugă la grup + Grup nou + Numele grupului + Șterge grupul + Redenumire grup + Vrei să ștergi acest grup? Alarmele sale vor fi mutate înapoi în lista principală. + + Alarmă %d + %d alarme + + Există deja un grup cu acest nume diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index aa2ee140..0d07f9d1 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -52,7 +52,19 @@ Импорт таймеров и будильников Длительность таймера Не запланировано + Зажгите завтра снова Пропущенный будильник Заменён другим будильником Время действия будильника истекло + Добавить в группу + Новая группа + Название группы + Удалить группу + Переименовать группу + Удалить эту группу? Ее оповещения будут перенесены обратно в основной список. + + %d будильник + %d сигналов тревоги + + Группа с таким названием уже существует diff --git a/app/src/main/res/values-sk/strings.xml b/app/src/main/res/values-sk/strings.xml index 4c69052d..491820f3 100644 --- a/app/src/main/res/values-sk/strings.xml +++ b/app/src/main/res/values-sk/strings.xml @@ -48,10 +48,22 @@ Ako viem zmeniť poradie kôl na okne so stopkami? Stačí, ak kliknete na niektorý stĺpec, to aktivuje triedenie podľa daného stĺpca. Ďalším kliknutím na daný stĺpec viete prepínať zostupné a vzostupné triedenie. Nenaplánované + Zajtra to skús znova Zmeškaný budík Nahradené iným budíkom Budík vypršal Importovať budíky a časovače Exportovať budíky a časovače Dĺžky trvania časovača + Pridať do skupiny + Nová skupina + Názov skupiny + Odstrániť skupinu + Pomenovať skupinu + Chcete túto skupinu odstrániť? Jej upozornenia sa presunú späť do hlavného zoznamu. + + %d budík + %d budíkov + + Skupina s týmto názvom už existuje diff --git a/app/src/main/res/values-sl/strings.xml b/app/src/main/res/values-sl/strings.xml index 3cddd978..030f17bc 100644 --- a/app/src/main/res/values-sl/strings.xml +++ b/app/src/main/res/values-sl/strings.xml @@ -43,4 +43,16 @@ Povečajte postopno glasnost Kako spremenim razvrščanje krogov v zavihku štoparica\? Kliknite na izbrani stolpec, da se krogi razvrstijo stolpcu. Z dodatnimi kliki preklapljate med naraščajočim in padajočim razvrščanjem. + Dodaj v skupino + Nova skupina + Ime skupine + Izbriši skupino + Preimenuj skupino + Ali želite izbrisati to skupino? Njeni alarmi bodo vrnjeni na glavni seznam. + + %d alarm + %d alarmov + + Skupina s tem imenom že obstaja + Jutri spet prižgi diff --git a/app/src/main/res/values-sr/strings.xml b/app/src/main/res/values-sr/strings.xml index cbe9c9f2..cb1e2379 100644 --- a/app/src/main/res/values-sr/strings.xml +++ b/app/src/main/res/values-sr/strings.xml @@ -43,4 +43,16 @@ Како могу да променим сортирање кругова на картици штоперице\? Само кликните на било коју колону, тако да ће кругови бити сортирани према датој колони. Додатним кликовима можете се пребацивати између растућег и опадајућег сортирања. Покрени штоперицу + Додај у групу + Нова група + Назив групе + Избриши групу + Преименујте групу + Избришите ову групу? Њене аларме ће бити враћене у главну листу. + + %d аларм + %d аларма + + Већ постоји група са тим именом. + Укључи поново сутра ујутру diff --git a/app/src/main/res/values-sv/strings.xml b/app/src/main/res/values-sv/strings.xml index bdfb7043..71feb514 100644 --- a/app/src/main/res/values-sv/strings.xml +++ b/app/src/main/res/values-sv/strings.xml @@ -50,7 +50,19 @@ Exportera alarm och timers Timerlängd Inte schemalagd + Tänd den igen i morgon Missat alarm Ersatt av ett annat alarm Alarm gick ut + Lägg till i gruppen + Ny grupp + Gruppnamn + Ta bort grupp + Byt namn på gruppen + ¿Quieres eliminar este grupo? Sus alarmas se volverán a mover a la lista principal. + + %d larm + %d larm + + Det finns redan en grupp med det namnet diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index c4c26c8c..ef69264d 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -51,6 +51,18 @@ Zamanlayıcı süresi Alarm zaman aşımına uğradı Planlanmadı + Yarın tekrar açın Kaçırılmış alarm Yerine başka bir alarm konuldu + Gruba ekle + Yeni grup + Grup adı + Grubu sil + Grubun adını değiştir + ¿Quieres eliminar este grupo? Sus alarmas se volverán a mover a la lista principal. + + %d alarmı + %d alarm + + Bu isimde bir grup zaten mevcut diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index 6a72bb4a..7f1290c1 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -53,6 +53,18 @@ Тривалість таймера Час спрацювання будильника вичерпано Не заплановано + Запали знову завтра Пропущений будильник Замінено на інший будильник + Додати до групи + Нова група + Назва групи + Видалити групу + Перейменувати групу + ¿Quieres eliminar este grupo? Sus alarmas se volverán a mover a la lista principal. + + %d будильник + %d сигналів тривоги + + Група з такою назвою вже існує diff --git a/app/src/main/res/values-vi/strings.xml b/app/src/main/res/values-vi/strings.xml index 50fa60dd..221069cf 100644 --- a/app/src/main/res/values-vi/strings.xml +++ b/app/src/main/res/values-vi/strings.xml @@ -31,7 +31,7 @@ Thêm báo thức Không tìm thấy bộ hẹn giờ nào Thêm bộ hẹn giờ - Báo thức sắp tới + Báo thức sắp tớir Đồng hồ hẹn giờ đang chạy Bộ hẹn giờ cho %s đang chạy Hẹn giờ mới @@ -45,4 +45,16 @@ Hiển thị giây Tăng âm lượng dần dần Làm cách nào tôi có thể thay đổi cách sắp xếp vòng ở danh mục đồng hồ bấm giờ? - \ No newline at end of file + Thêm vào nhóm + Nhóm mới + Tên nhóm + Xóa nhóm + Đổi tên nhóm + Xóa nhóm này? Các cảnh báo của nhóm này sẽ được chuyển trở lại danh sách chính. + + Báo thức %d + %d báo thức + + Đã có một nhóm mang tên đó rồi + Hãy bật lại vào ngày mai + diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index f3e1ddc5..3b468959 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -49,6 +49,7 @@ 导入闹钟和定时器 计时器持续时长 未设置 + 明天再打開嗎? 错过的闹钟 已被另一个闹钟替代 闹钟已超时 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index b129f5c3..d21bf1e4 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -33,6 +33,7 @@ Add timer Upcoming alarm Not scheduled + Turn on again tomorrow? Missed alarm Replaced by another alarm Alarm timed out @@ -60,6 +61,17 @@ How can I change lap sorting at the stopwatch tab? Just click on any of the columns, that will make the laps be sorted by the given column. With additional clicks you can toggle between ascending and descending sorting. + Add to group + New group + Group name + Delete group + + %d alarm + %d alarms + + Rename group + Delete this group? Its alarms will be moved back to the main list. + A group with that name already exists