├── .gitattributes ├── .gitignore ├── LICENSE ├── PRIVACY.md ├── README.md ├── TODO ├── app ├── build.gradle.kts ├── proguard-rules.pro ├── schemas │ └── com.nfcalarmclock.db.NacAlarmDatabase │ │ ├── 1.json │ │ ├── 10.json │ │ ├── 11.json │ │ ├── 12.json │ │ ├── 13.json │ │ ├── 14.json │ │ ├── 15.json │ │ ├── 16.json │ │ ├── 17.json │ │ ├── 18.json │ │ ├── 19.json │ │ ├── 2.json │ │ ├── 20.json │ │ ├── 21.json │ │ ├── 22.json │ │ ├── 23.json │ │ ├── 24.json │ │ ├── 25.json │ │ ├── 26.json │ │ ├── 27.json │ │ ├── 28.json │ │ ├── 29.json │ │ ├── 3.json │ │ ├── 30.json │ │ ├── 31.json │ │ ├── 32.json │ │ ├── 33.json │ │ ├── 34.json │ │ ├── 35.json │ │ ├── 4.json │ │ ├── 5.json │ │ ├── 6.json │ │ ├── 7.json │ │ ├── 8.json │ │ └── 9.json └── src │ ├── foss │ └── kotlin │ │ └── com │ │ └── nfcalarmclock │ │ ├── ratemyapp │ │ └── NacRateMyApp.kt │ │ └── support │ │ └── NacSupportSetting.kt │ ├── googleplay │ └── kotlin │ │ └── com │ │ └── nfcalarmclock │ │ ├── ratemyapp │ │ └── NacRateMyApp.kt │ │ └── support │ │ └── NacSupportSetting.kt │ └── main │ ├── AndroidManifest.xml │ ├── ic_launcher-playstore.png │ ├── kotlin │ └── com │ │ └── nfcalarmclock │ │ ├── NacNfcAlarmClockApplication.kt │ │ ├── alarm │ │ ├── NacAlarmRepository.kt │ │ ├── NacAlarmViewModel.kt │ │ ├── activealarm │ │ │ ├── NacActiveAlarmActivity.kt │ │ │ ├── NacActiveAlarmBroadcastReceiver.kt │ │ │ ├── NacActiveAlarmLayoutHandler.kt │ │ │ ├── NacActiveAlarmNotification.kt │ │ │ ├── NacActiveAlarmService.kt │ │ │ ├── NacOriginalLayoutHandler.kt │ │ │ ├── NacSkipAlarmNotification.kt │ │ │ ├── NacSwipeAnimationHandler.kt │ │ │ ├── NacSwipeLayoutHandler.kt │ │ │ └── NacWakeupProcess.kt │ │ ├── db │ │ │ ├── NacAlarm.kt │ │ │ ├── NacAlarmDao.kt │ │ │ └── NacAlarmTypeConverters.kt │ │ └── options │ │ │ ├── NacAlarmOptionsDialog.kt │ │ │ ├── NacGenericAlarmOptionsDialog.kt │ │ │ ├── audiosource │ │ │ └── NacAudioSourceDialog.kt │ │ │ ├── dismissoptions │ │ │ └── NacDismissOptionsDialog.kt │ │ │ ├── flashlight │ │ │ ├── NacFlashlight.kt │ │ │ └── NacFlashlightOptionsDialog.kt │ │ │ ├── mediapicker │ │ │ ├── NacMediaPickerActivity.kt │ │ │ ├── NacMediaPickerFragment.kt │ │ │ ├── music │ │ │ │ ├── NacDirectorySelectedWarningDialog.kt │ │ │ │ └── NacMusicPickerFragment.kt │ │ │ └── ringtone │ │ │ │ └── NacRingtonePickerFragment.kt │ │ │ ├── missedalarm │ │ │ └── NacMissedAlarmNotification.kt │ │ │ ├── name │ │ │ └── NacNameDialog.kt │ │ │ ├── nextalarmformat │ │ │ ├── NacNextAlarmFormatDialog.kt │ │ │ └── NacNextAlarmFormatPreference.kt │ │ │ ├── nfc │ │ │ ├── NacDeleteNfcTagDialog.kt │ │ │ ├── NacNfc.kt │ │ │ ├── NacNfcTagAdapter.kt │ │ │ ├── NacNfcTagRepository.kt │ │ │ ├── NacNfcTagSettingFragment.kt │ │ │ ├── NacNfcTagViewHolder.kt │ │ │ ├── NacNfcTagViewModel.kt │ │ │ ├── NacRenameNfcTagDialog.kt │ │ │ ├── NacSaveNfcTagDialog.kt │ │ │ ├── NacScanNfcTagDialog.kt │ │ │ ├── NacSelectNfcTagDialog.kt │ │ │ └── db │ │ │ │ ├── NacNfcTag.kt │ │ │ │ └── NacNfcTagDao.kt │ │ │ ├── snoozeoptions │ │ │ └── NacSnoozeOptionsDialog.kt │ │ │ ├── startweekon │ │ │ ├── NacStartWeekOnDialog.kt │ │ │ └── NacStartWeekOnPreference.kt │ │ │ ├── tts │ │ │ ├── NacTextToSpeech.kt │ │ │ ├── NacTextToSpeechDialog.kt │ │ │ └── NacTranslate.kt │ │ │ ├── upcomingreminder │ │ │ ├── NacUpcomingReminderDialog.kt │ │ │ ├── NacUpcomingReminderNotification.kt │ │ │ └── NacUpcomingReminderService.kt │ │ │ ├── vibrate │ │ │ ├── NacVibrateOptionsDialog.kt │ │ │ └── NacVibrator.kt │ │ │ └── volume │ │ │ └── NacVolumeOptionsDialog.kt │ │ ├── card │ │ ├── NacCardAdapter.kt │ │ ├── NacCardAdapterLiveData.kt │ │ ├── NacCardHolder.kt │ │ ├── NacCardLayoutManager.kt │ │ ├── NacCardPreference.kt │ │ ├── NacCardTouchHelper.kt │ │ └── NacHeightAnimator.kt │ │ ├── db │ │ ├── NacAlarmDatabase.kt │ │ └── NacOldDatabase.kt │ │ ├── main │ │ └── NacMainActivity.kt │ │ ├── settings │ │ ├── NacAboutSettingFragment.kt │ │ ├── NacAppearanceSettingFragment.kt │ │ ├── NacGeneralSettingFragment.kt │ │ ├── NacGenericSettingFragment.kt │ │ ├── NacMainSettingActivity.kt │ │ ├── NacMainSettingFragment.kt │ │ ├── importexport │ │ │ ├── NacExportManager.kt │ │ │ ├── NacImportExportDialog.kt │ │ │ └── NacImportManager.kt │ │ └── preference │ │ │ ├── NacCheckboxPreference.kt │ │ │ └── NacPreferenceCategory.kt │ │ ├── shared │ │ └── NacSharedPreferences.kt │ │ ├── statistics │ │ ├── AreYouSureResetStatisticsDialog.kt │ │ ├── NacAlarmStatisticRepository.kt │ │ ├── NacAlarmStatisticViewModel.kt │ │ ├── NacStatisticsSettingFragment.kt │ │ └── db │ │ │ ├── NacAlarmCreatedStatistic.kt │ │ │ ├── NacAlarmCreatedStatisticDao.kt │ │ │ ├── NacAlarmDeletedStatistic.kt │ │ │ ├── NacAlarmDeletedStatisticDao.kt │ │ │ ├── NacAlarmDismissedStatistic.kt │ │ │ ├── NacAlarmDismissedStatisticDao.kt │ │ │ ├── NacAlarmMissedStatistic.kt │ │ │ ├── NacAlarmMissedStatisticDao.kt │ │ │ ├── NacAlarmSnoozedStatistic.kt │ │ │ ├── NacAlarmSnoozedStatisticDao.kt │ │ │ ├── NacAlarmStatistic.kt │ │ │ ├── NacAlarmStatisticDao.kt │ │ │ └── NacStatisticTypeConverters.kt │ │ ├── system │ │ ├── file │ │ │ ├── NacFile.kt │ │ │ ├── NacFileTree.kt │ │ │ ├── NacTreeNode.kt │ │ │ ├── NacZip.kt │ │ │ └── browser │ │ │ │ ├── NacFileBrowser.kt │ │ │ │ ├── NacFileBrowserRepository.kt │ │ │ │ └── NacFileBrowserViewModel.kt │ │ ├── mediaplayer │ │ │ └── NacMediaPlayer.kt │ │ ├── permission │ │ │ ├── NacOptionalPermissionPreference.kt │ │ │ ├── NacPermissionRequestDialog.kt │ │ │ ├── NacPermissionRequestManager.kt │ │ │ ├── NacRequiredPermissionPreference.kt │ │ │ ├── ignorebatteryoptimization │ │ │ │ ├── NacIgnoreBatteryOptimizationPermission.kt │ │ │ │ └── NacIgnoreBatteryOptimizationPermissionRequestDialog.kt │ │ │ ├── postnotifications │ │ │ │ ├── NacPostNotificationsPermission.kt │ │ │ │ └── NacPostNotificationsPermissionRequestDialog.kt │ │ │ ├── readmediaaudio │ │ │ │ └── NacReadMediaAudioPermission.kt │ │ │ └── scheduleexactalarm │ │ │ │ ├── NacScheduleExactAlarmPermission.kt │ │ │ │ ├── NacScheduleExactAlarmPermissionChangedBroadcastReceiver.kt │ │ │ │ └── NacScheduleExactAlarmPermissionRequestDialog.kt │ │ ├── scheduler │ │ │ └── NacScheduler.kt │ │ └── triggers │ │ │ ├── appupdate │ │ │ └── NacAppUpdatedBroadcastReceiver.kt │ │ │ ├── shutdown │ │ │ └── NacShutdownBroadcastReceiver.kt │ │ │ ├── startup │ │ │ └── NacStartupBroadcastReceiver.kt │ │ │ └── timechange │ │ │ └── NacTimeChangeBroadcastReceiver.kt │ │ ├── util │ │ ├── NacBundle.kt │ │ ├── NacCalendar.kt │ │ ├── NacContext.kt │ │ ├── NacIntent.kt │ │ ├── NacUtility.kt │ │ └── media │ │ │ ├── NacAudioAttributes.kt │ │ │ ├── NacAudioManager.kt │ │ │ └── NacMedia.kt │ │ ├── view │ │ ├── NacView.kt │ │ ├── alarmoptionlayout │ │ │ └── NacAlarmOptionLayout.kt │ │ ├── colorpicker │ │ │ ├── NacColorPicker.kt │ │ │ ├── NacColorPickerDialog.kt │ │ │ └── NacColorPickerPreference.kt │ │ ├── dayofweek │ │ │ ├── NacDayButton.kt │ │ │ ├── NacDayButtonStylePreference.kt │ │ │ └── NacDayOfWeek.kt │ │ ├── dialog │ │ │ ├── NacBottomSheetDialogFragment.kt │ │ │ └── NacDialogFragment.kt │ │ └── notification │ │ │ └── NacNotification.kt │ │ ├── whatsnew │ │ ├── NacWhatsNewDialog.kt │ │ └── NacWhatsNewListAdapter.kt │ │ └── widget │ │ ├── NacClockWidgetConfigureActivity.kt │ │ └── NacClockWidgetProvider.kt │ └── res │ ├── anim │ ├── fade_in.xml │ ├── fade_out.xml │ └── pulse.xml │ ├── animator │ ├── card_color_collapse.xml │ ├── card_color_expand.xml │ ├── card_color_highlight.xml │ ├── nac_day_button_alpha.xml │ ├── nac_day_button_text.xml │ └── support_development.xml │ ├── color │ ├── card_toggle_button.xml │ └── nac_day_button_stroke.xml │ ├── drawable │ ├── alarm.xml │ ├── analytics.xml │ ├── arrow_left.xml │ ├── arrow_right.xml │ ├── auto_delete.xml │ ├── battery_alert.xml │ ├── cancel.xml │ ├── card_divider.xml │ ├── collapse.xml │ ├── color_example.xml │ ├── color_selector.xml │ ├── copy.xml │ ├── delete.xml │ ├── dismiss.xml │ ├── edit.xml │ ├── expand.xml │ ├── favorite.xml │ ├── flashlight_on_32.xml │ ├── folder.xml │ ├── format_align_left_23.xml │ ├── format_bold.xml │ ├── format_size.xml │ ├── ic_launcher_foreground.xml │ ├── import_export.xml │ ├── info.xml │ ├── label.xml │ ├── launch_32.xml │ ├── list.xml │ ├── minus_24.xml │ ├── music_note.xml │ ├── notifications.xml │ ├── opacity.xml │ ├── palette.xml │ ├── play.xml │ ├── plus.xml │ ├── position_32.xml │ ├── repeat.xml │ ├── reset.xml │ ├── round_action_button.xml │ ├── schedule.xml │ ├── send.xml │ ├── settings.xml │ ├── shader_selector.xml │ ├── slider_path.xml │ ├── snooze.xml │ ├── tap_and_play.xml │ ├── vibrate.xml │ ├── visibility.xml │ ├── volume_high.xml │ ├── volume_low.xml │ ├── volume_med.xml │ └── volume_off.xml │ ├── layout-land │ └── act_alarm_new.xml │ ├── layout │ ├── act_alarm.xml │ ├── act_alarm_new.xml │ ├── act_main.xml │ ├── act_setting.xml │ ├── act_sound.xml │ ├── card_copy.xml │ ├── card_delete.xml │ ├── card_frame.xml │ ├── card_view.xml │ ├── dlg_alarm_days.xml │ ├── dlg_alarm_name.xml │ ├── dlg_alarm_options.xml │ ├── dlg_audio_source.xml │ ├── dlg_color_picker.xml │ ├── dlg_delete_nfc_tag.xml │ ├── dlg_dismiss_options.xml │ ├── dlg_flashlight.xml │ ├── dlg_import_export.xml │ ├── dlg_media_playlist.xml │ ├── dlg_rename_nfc_tag.xml │ ├── dlg_request_ignore_battery_optimization_permission.xml │ ├── dlg_request_post_notifications_permission.xml │ ├── dlg_request_schedule_exact_alarm_permission.xml │ ├── dlg_save_nfc_tag.xml │ ├── dlg_scan_nfc_tag.xml │ ├── dlg_select_nfc_tag.xml │ ├── dlg_snooze_options.xml │ ├── dlg_text_to_speech.xml │ ├── dlg_upcoming_reminder.xml │ ├── dlg_vibrate.xml │ ├── dlg_volume_options.xml │ ├── dlg_whats_new.xml │ ├── frg_buttons.xml │ ├── frg_manage_nfc_tags.xml │ ├── frg_music.xml │ ├── frg_ringtone.xml │ ├── frg_statistics.xml │ ├── nac_alarm_option_layout.xml │ ├── nac_clock_widget.xml │ ├── nac_clock_widget_configure.xml │ ├── nac_color_picker.xml │ ├── nac_day_button_filled.xml │ ├── nac_day_button_outlined.xml │ ├── nac_day_of_week.xml │ ├── nac_file_entry.xml │ ├── nac_nfc_tag.xml │ ├── nac_preference.xml │ ├── nac_preference_alarm_card.xml │ ├── nac_preference_checkbox.xml │ ├── nac_preference_color.xml │ ├── nac_preference_day_button.xml │ ├── nac_preference_permission.xml │ ├── nac_preference_text.xml │ ├── nac_preference_volume.xml │ ├── nac_select_add_remove_nfc_tag.xml │ ├── nac_whats_new_entry.xml │ ├── nac_whats_new_entry_version.xml │ ├── radio_button.xml │ └── radio_button_ringtone.xml │ ├── menu │ ├── menu_action_bar.xml │ └── menu_card.xml │ ├── mipmap-anydpi-v26 │ ├── ic_launcher.xml │ └── ic_launcher_round.xml │ ├── mipmap-hdpi │ ├── app.webp │ ├── ic_launcher.webp │ └── ic_launcher_round.webp │ ├── mipmap-mdpi │ ├── app.webp │ ├── ic_launcher.webp │ └── ic_launcher_round.webp │ ├── mipmap-xhdpi │ ├── app.webp │ ├── ic_launcher.webp │ └── ic_launcher_round.webp │ ├── mipmap-xxhdpi │ ├── app.webp │ ├── ic_launcher.webp │ └── ic_launcher_round.webp │ ├── mipmap-xxxhdpi │ ├── app.webp │ ├── ic_launcher.webp │ └── ic_launcher_round.webp │ ├── navigation │ └── nav_alarm_options.xml │ ├── values-b+es │ ├── arrays.xml │ ├── arrays_alarm_options.xml │ ├── arrays_whats_new.xml │ └── strings.xml │ ├── values-bn │ ├── arrays.xml │ ├── arrays_alarm_options.xml │ ├── arrays_whats_new.xml │ └── strings.xml │ ├── values-de │ ├── arrays.xml │ ├── arrays_alarm_options.xml │ ├── arrays_whats_new.xml │ └── strings.xml │ ├── values-hi │ ├── arrays.xml │ ├── arrays_alarm_options.xml │ ├── arrays_whats_new.xml │ └── strings.xml │ ├── values-ht │ ├── arrays.xml │ ├── arrays_alarm_options.xml │ ├── arrays_whats_new.xml │ └── strings.xml │ ├── values-pt-rBR │ ├── arrays.xml │ ├── arrays_alarm_options.xml │ ├── arrays_whats_new.xml │ └── strings.xml │ ├── values-vi │ ├── arrays.xml │ ├── arrays_alarm_options.xml │ ├── arrays_whats_new.xml │ └── strings.xml │ ├── values │ ├── arrays.xml │ ├── arrays_alarm_options.xml │ ├── arrays_whats_new.xml │ ├── attrs.xml │ ├── bools.xml │ ├── colors.xml │ ├── dimens.xml │ ├── integers.xml │ ├── keys.xml │ ├── strings.xml │ ├── styles.xml │ ├── styles_alarm_option_dialog.xml │ ├── styles_alarm_option_main.xml │ └── styles_clock_widget.xml │ └── xml │ ├── about_preferences.xml │ ├── appearance_preferences.xml │ ├── data_extraction_rules.xml │ ├── full_backup_content.xml │ ├── general_preferences.xml │ ├── main_preferences.xml │ ├── nac_clock_widget_info.xml │ ├── nfc_tech_filter.xml │ └── provider_filepaths.xml ├── build.gradle.kts ├── fastlane └── metadata │ └── android │ └── en-US │ ├── changelogs │ ├── 406.txt │ ├── 436.txt │ ├── 439.txt │ ├── 440.txt │ ├── 442.txt │ ├── 462.txt │ ├── 466.txt │ ├── 471.txt │ └── 474.txt │ ├── full_description.txt │ ├── images │ ├── icon.png │ └── phoneScreenshots │ │ ├── 1.png │ │ ├── 2.png │ │ ├── 3.png │ │ ├── 4.png │ │ └── 5.png │ ├── short_description.txt │ └── title.txt ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── javadoc ├── allclasses-frame.html ├── allclasses-noframe.html ├── com │ └── nfcalarmclock │ │ ├── NacAboutSettingsFragment.html │ │ ├── NacActiveAlarmNotification.html │ │ ├── NacAlarm.Builder.html │ │ ├── NacAlarm.ChangeTracker.html │ │ ├── NacAlarm.OnAlarmChangeListener.html │ │ ├── NacAlarm.html │ │ ├── NacAlarmActivity.html │ │ ├── NacAlarmBroadcastReceiver.html │ │ ├── NacAppearanceSettingsFragment.html │ │ ├── NacAudio.Attributes.html │ │ ├── NacAudio.html │ │ ├── NacAudioSourceDialog.html │ │ ├── NacAutoDismissDialog.html │ │ ├── NacAutoDismissPreference.html │ │ ├── NacBootBroadcastReceiver.html │ │ ├── NacBundle.html │ │ ├── NacCalendar.Days.html │ │ ├── NacCalendar.Time.html │ │ ├── NacCalendar.html │ │ ├── NacCardAdapter.OnUseNfcChangeListener.html │ │ ├── NacCardAdapter.Undo.Type.html │ │ ├── NacCardAdapter.Undo.html │ │ ├── NacCardAdapter.html │ │ ├── NacCardHolder.OnCardCollapsedListener.html │ │ ├── NacCardHolder.OnCardExpandedListener.html │ │ ├── NacCardHolder.OnDeleteClickedListener.html │ │ ├── NacCardHolder.html │ │ ├── NacCardTouchHelper.Adapter.html │ │ ├── NacCardTouchHelper.Callback.html │ │ ├── NacCardTouchHelper.html │ │ ├── NacCheckboxPreference.html │ │ ├── NacColorPicker.Attributes.html │ │ ├── NacColorPicker.html │ │ ├── NacColorPickerDialog.html │ │ ├── NacColorPreference.html │ │ ├── NacContext.html │ │ ├── NacDatabase.BackgroundService.html │ │ ├── NacDatabase.Contract.AlarmTable.html │ │ ├── NacDatabase.Contract.html │ │ ├── NacDatabase.html │ │ ├── NacDayButton.Attributes.html │ │ ├── NacDayButton.OnDayChangedListener.html │ │ ├── NacDayButton.html │ │ ├── NacDayButtonPreference.html │ │ ├── NacDayOfWeek.OnWeekChangedListener.html │ │ ├── NacDayOfWeek.html │ │ ├── NacDayOfWeekDialog.html │ │ ├── NacDaysPreference.html │ │ ├── NacDialog.OnBuildListener.html │ │ ├── NacDialog.OnCancelListener.html │ │ ├── NacDialog.OnDismissListener.html │ │ ├── NacDialog.OnHideListener.html │ │ ├── NacDialog.OnNeutralActionListener.html │ │ ├── NacDialog.OnShowListener.html │ │ ├── NacDialog.Scaler.html │ │ ├── NacDialog.html │ │ ├── NacFile.Metadata.html │ │ ├── NacFile.Tree.html │ │ ├── NacFile.html │ │ ├── NacFileBrowser.OnBrowserClickedListener.html │ │ ├── NacFileBrowser.html │ │ ├── NacForegroundService.html │ │ ├── NacGeneralSettingsFragment.html │ │ ├── NacHeightAnimator.OnAnimateHeightListener.html │ │ ├── NacHeightAnimator.html │ │ ├── NacIntent.html │ │ ├── NacLayoutManager.SmoothScroller.html │ │ ├── NacLayoutManager.html │ │ ├── NacMainActivity.html │ │ ├── NacMaxSnoozeDialog.html │ │ ├── NacMaxSnoozePreference.html │ │ ├── NacMedia.Tree.html │ │ ├── NacMedia.html │ │ ├── NacMediaActivity.NacPagerAdapter.html │ │ ├── NacMediaActivity.html │ │ ├── NacMediaFragment.html │ │ ├── NacMediaPlayer.Playlist.html │ │ ├── NacMediaPlayer.html │ │ ├── NacMediaPreference.html │ │ ├── NacMiscellaneousSettingsFragment.html │ │ ├── NacMissedAlarmNotification.html │ │ ├── NacMusicFragment.html │ │ ├── NacNameDialog.html │ │ ├── NacNamePreference.html │ │ ├── NacNextAlarmFormatPreference.html │ │ ├── NacNfc.html │ │ ├── NacNotification.html │ │ ├── NacNotificationHelper.html │ │ ├── NacPermissions.OnResultListener.html │ │ ├── NacPermissions.html │ │ ├── NacPreferenceCategory.html │ │ ├── NacRateMyAppDialog.html │ │ ├── NacRingtoneFragment.html │ │ ├── NacScanNfcTagDialog.html │ │ ├── NacScheduler.html │ │ ├── NacSettingsActivity.SettingsFragment.html │ │ ├── NacSettingsActivity.html │ │ ├── NacSettingsFragment.html │ │ ├── NacSharedConstants.html │ │ ├── NacSharedDefaults.html │ │ ├── NacSharedKeys.html │ │ ├── NacSharedPreferences.html │ │ ├── NacSharedResource.html │ │ ├── NacSnackbar.html │ │ ├── NacSnoozeDurationDialog.html │ │ ├── NacSnoozeDurationPreference.html │ │ ├── NacSpeakFrequencyDialog.html │ │ ├── NacSpeakFrequencyPreference.html │ │ ├── NacSpinnerDialog.Direction.html │ │ ├── NacSpinnerDialog.html │ │ ├── NacSpotify.html │ │ ├── NacStartWeekOnPreference.html │ │ ├── NacTaskWorker.html │ │ ├── NacTextToSpeech.NacUtteranceListener.html │ │ ├── NacTextToSpeech.OnSpeakingListener.html │ │ ├── NacTextToSpeech.html │ │ ├── NacTimeChangeBroadcastReceiver.html │ │ ├── NacTreeNode.html │ │ ├── NacUpcomingAlarmNotification.html │ │ ├── NacUtility.html │ │ ├── NacVolumePreference.html │ │ ├── NacWakeupProcess.OnAutoDismissListener.html │ │ ├── NacWakeupProcess.html │ │ ├── package-frame.html │ │ ├── package-summary.html │ │ └── package-tree.html ├── constant-values.html ├── deprecated-list.html ├── help-doc.html ├── index-files │ ├── index-1.html │ ├── index-10.html │ ├── index-11.html │ ├── index-12.html │ ├── index-13.html │ ├── index-14.html │ ├── index-15.html │ ├── index-16.html │ ├── index-17.html │ ├── index-18.html │ ├── index-19.html │ ├── index-2.html │ ├── index-20.html │ ├── index-21.html │ ├── index-22.html │ ├── index-23.html │ ├── index-3.html │ ├── index-4.html │ ├── index-5.html │ ├── index-6.html │ ├── index-7.html │ ├── index-8.html │ └── index-9.html ├── index.html ├── overview-tree.html ├── package-list ├── script.js └── stylesheet.css └── settings.gradle.kts /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | captures 3 | .gradle 4 | .idea 5 | 6 | app/my-new-release-key.jks 7 | app/debug 8 | app/release 9 | app/googleplay 10 | app/foss 11 | keystore.properties 12 | local.properties 13 | *.iml 14 | *.swp 15 | *.apk 16 | *.aab 17 | .DS_Store 18 | .externalNativeBuild 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # NFC Alarm Clock 2 | 3 | Customizable and feature-rich alarm clock app that allows you to: 4 | 5 | * Customize the colors of your alarms 6 | * Customize what is shown in the alarm screen 7 | * Swipe to copy/delete an alarm 8 | * Play your own music to wake up 9 | * Use an NFC tag/card to dismiss an alarm 10 | * Gradually increase the volume when an alarm goes off 11 | * Restrict the volume when an alarm goes off 12 | * Use text-to-speech to say the time and/or the name of the alarm when it goes off 13 | * Dismiss the alarm early so that you don't have to wait for it to go off 14 | * Set reminders for an alarm 15 | * See (simple) statistics on your alarm usage 16 | * See how each permission in the app is used 17 | 18 | [Get it on F-Droid](https://f-droid.org/packages/com.nfcalarmclock/) 21 | [Get it on Google Play](https://play.google.com/store/apps/details?id=com.nfcalarmclock) 24 | 25 | Or download the latest APK from the [Releases Section](https://github.com/gabeg805/NFC-Alarm-Clock/releases/latest). 26 | 27 | # License 28 | 29 | Copyright (C) 2024 Gabriel Gonzalez 30 | 31 | This program is free software: you can redistribute it and/or modify 32 | it under the terms of the GNU General Public License as published by 33 | the Free Software Foundation, either version 3 of the License, or 34 | (at your option) any later version. 35 | 36 | This program is distributed in the hope that it will be useful, 37 | but WITHOUT ANY WARRANTY; without even the implied warranty of 38 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 39 | GNU General Public License for more details. 40 | 41 | You should have received a copy of the GNU General Public License 42 | along with this program. If not, see . 43 | 44 | -------------------------------------------------------------------------------- /TODO: -------------------------------------------------------------------------------- 1 | ---- 2 | TODO 3 | ---- 4 | 5 | * Add option to select multiple NFC tags. 6 | 7 | * Move easy to mess up strings to top 8 | 9 | * Improve look of warning selected directory dialog 10 | 11 | * Look up defaults like for auto dismiss, dismiss early, .... really all the 12 | times, and see if they should be in the resources directory? 13 | 14 | * Add languages for Pakistan, Japan, Thailand, Poland, Turkey, France, 15 | Serbia, Finland, Romania, Italy 16 | 17 | * Long press repeat, NFC, and flashlight for quick options 18 | 19 | * Overlay number over repeat button for custom repeat option? 20 | 21 | * Have option to scroll alarm name instead of marquee it 22 | 23 | * Figure out why I use home in NacFile 24 | 25 | ------ 26 | Issues 27 | ------ 28 | 29 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle.kts. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | #-keep class com.nfcalarmclock.** { *; } 9 | -verbose 10 | -keepattributes EnclosingMethod,LineNumberTable,SourceFile 11 | 12 | # If you keep the line number information, uncomment this to 13 | # hide the original source file name. 14 | #-renamesourcefileattribute SourceFile 15 | 16 | #-dontshrink 17 | #-dontoptimize 18 | 19 | #-dontusemixedcaseclassnames 20 | #-dontskipnonpubliclibraryclasses 21 | -------------------------------------------------------------------------------- /app/src/foss/kotlin/com/nfcalarmclock/ratemyapp/NacRateMyApp.kt: -------------------------------------------------------------------------------- 1 | package com.nfcalarmclock.ratemyapp 2 | 3 | import androidx.appcompat.app.AppCompatActivity 4 | import com.nfcalarmclock.shared.NacSharedPreferences 5 | 6 | /** 7 | * Handle when to prompt the user to rate my app. 8 | * 9 | * The FOSS version should never actually request to rate the app. This is just 10 | * here to satisfy the condititon of being able to check if should request, as 11 | * well as request, but the methods themselves do nothing. 12 | */ 13 | @Suppress("SameReturnValue") 14 | object NacRateMyApp 15 | { 16 | 17 | /** 18 | * The FOSS version should never request to rate the app. 19 | * 20 | * @return False because the FOSS version should never request to rate the 21 | * app. 22 | */ 23 | @Suppress("Unused") 24 | fun shouldRequest(shared: NacSharedPreferences): Boolean 25 | { 26 | return false 27 | } 28 | 29 | /** 30 | * Request to rate my app. 31 | * 32 | * This does nothing on the FOSS version. 33 | */ 34 | @Suppress("Unused", "EmptyMethod") 35 | fun request(activity:AppCompatActivity, shared: NacSharedPreferences) 36 | { 37 | } 38 | 39 | } -------------------------------------------------------------------------------- /app/src/foss/kotlin/com/nfcalarmclock/support/NacSupportSetting.kt: -------------------------------------------------------------------------------- 1 | package com.nfcalarmclock.support 2 | 3 | import androidx.fragment.app.FragmentActivity 4 | import android.net.Uri 5 | 6 | import android.content.Intent 7 | 8 | 9 | 10 | 11 | /** 12 | * Support setting that intentionally has the Google billing flow removed. 13 | */ 14 | class NacSupportSetting( 15 | 16 | /** 17 | * Fragment activity. 18 | */ 19 | private val fragmentActivity: FragmentActivity 20 | 21 | ) 22 | { 23 | 24 | /** 25 | * Listener for when a support event occurs. 26 | */ 27 | fun interface OnSupportEventListener 28 | { 29 | fun onSupported() 30 | } 31 | 32 | /** 33 | * Listener for when a support events occurs. 34 | */ 35 | var onSupportEventListener: OnSupportEventListener? = null 36 | 37 | /** 38 | * Start the support flow. 39 | */ 40 | fun start() 41 | { 42 | // Call the support listener 43 | onSupportEventListener?.onSupported() 44 | 45 | // Open the browser 46 | val uri = Uri.parse("https://www.nfcalarmclock.com") 47 | val intent = Intent(Intent.ACTION_VIEW, uri) 48 | 49 | fragmentActivity.startActivity(intent) 50 | } 51 | 52 | } -------------------------------------------------------------------------------- /app/src/main/ic_launcher-playstore.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gabeg805/NFC-Alarm-Clock/bad7b7da229b76d6b953ea08ef1f52663e51a4d2/app/src/main/ic_launcher-playstore.png -------------------------------------------------------------------------------- /app/src/main/kotlin/com/nfcalarmclock/NacNfcAlarmClockApplication.kt: -------------------------------------------------------------------------------- 1 | package com.nfcalarmclock 2 | 3 | import android.app.Application 4 | import dagger.hilt.android.HiltAndroidApp 5 | 6 | @HiltAndroidApp 7 | class NacNfcAlarmClockApplication : Application() -------------------------------------------------------------------------------- /app/src/main/kotlin/com/nfcalarmclock/alarm/activealarm/NacActiveAlarmBroadcastReceiver.kt: -------------------------------------------------------------------------------- 1 | package com.nfcalarmclock.alarm.activealarm 2 | 3 | import android.content.BroadcastReceiver 4 | import android.content.Context 5 | import android.content.Intent 6 | import androidx.annotation.OptIn 7 | import androidx.media3.common.util.UnstableApi 8 | import com.nfcalarmclock.util.getAlarm 9 | 10 | /** 11 | * Receive this signal from AlarmManager and start the foreground service. 12 | */ 13 | class NacActiveAlarmBroadcastReceiver 14 | : BroadcastReceiver() 15 | { 16 | 17 | /** 18 | * It is possible for another actor to send a spoofed intent with no 19 | * action string or a different action string and cause undesired behavior. 20 | * Ensure that the received Intent's action string matches the expected 21 | * value before restoring alarms. 22 | */ 23 | @OptIn(UnstableApi::class) 24 | override fun onReceive(context: Context, intent: Intent) 25 | { 26 | // Get the alarm 27 | val alarm = intent.getAlarm() 28 | 29 | // Start the alarm activity and service 30 | NacActiveAlarmActivity.startAlarmActivity(context, alarm) 31 | NacActiveAlarmService.startAlarmService(context, alarm) 32 | } 33 | 34 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/nfcalarmclock/alarm/activealarm/NacActiveAlarmLayoutHandler.kt: -------------------------------------------------------------------------------- 1 | package com.nfcalarmclock.alarm.activealarm 2 | 3 | import android.content.Context 4 | import androidx.appcompat.app.AppCompatActivity 5 | import com.nfcalarmclock.alarm.db.NacAlarm 6 | import com.nfcalarmclock.alarm.options.nfc.NacNfc 7 | import com.nfcalarmclock.shared.NacSharedPreferences 8 | 9 | abstract class NacActiveAlarmLayoutHandler( 10 | 11 | /** 12 | * Activity. 13 | */ 14 | activity: AppCompatActivity, 15 | 16 | /** 17 | * Alarm. 18 | */ 19 | val alarm: NacAlarm?, 20 | 21 | /** 22 | * Listener for an alarm action. 23 | */ 24 | val onAlarmActionListener: OnAlarmActionListener 25 | 26 | ) 27 | { 28 | 29 | /** 30 | * Listener for an alarm action, such as snooze or dismiss. 31 | */ 32 | interface OnAlarmActionListener 33 | { 34 | fun onSnooze(alarm: NacAlarm) 35 | fun onDismiss(alarm: NacAlarm) 36 | } 37 | 38 | /** 39 | * Shared preferences. 40 | */ 41 | val sharedPreferences: NacSharedPreferences = NacSharedPreferences(activity) 42 | 43 | /** 44 | * Whether the alarm should use NFC or not. 45 | */ 46 | val shouldUseNfc: Boolean = NacNfc.exists(activity) && (alarm != null) && alarm.shouldUseNfc && sharedPreferences.shouldShowNfcButton 47 | 48 | /** 49 | * Run any setup steps. 50 | */ 51 | open fun setup(context: Context) {} 52 | 53 | /** 54 | * Setup an NFC tag, if necessary. 55 | */ 56 | open fun setupNfcTag(context: Context, names: String?) {} 57 | 58 | /** 59 | * Start the layout and run any setup that needs to run. 60 | */ 61 | abstract fun start(context: Context) 62 | 63 | /** 64 | * Stop the layout handler. 65 | */ 66 | abstract fun stop(context: Context) 67 | 68 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/nfcalarmclock/alarm/db/NacAlarmTypeConverters.kt: -------------------------------------------------------------------------------- 1 | package com.nfcalarmclock.alarm.db 2 | 3 | import androidx.room.TypeConverter 4 | import com.nfcalarmclock.util.NacCalendar.Day 5 | import java.util.EnumSet 6 | 7 | /** 8 | * Type converters for when an object is retrieved from the database. 9 | */ 10 | object NacAlarmTypeConverters 11 | { 12 | 13 | /** 14 | * Convert from an integer day value to an enumerated set of days. 15 | */ 16 | @TypeConverter 17 | fun dayValueToDays(value: Int): EnumSet 18 | { 19 | return Day.valueToDays(value) 20 | } 21 | 22 | /** 23 | * Convert from an enumerated set of days to an integer day value. 24 | */ 25 | @TypeConverter 26 | fun daysToDayValue(days: EnumSet): Int 27 | { 28 | return Day.daysToValue(days) 29 | } 30 | 31 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/nfcalarmclock/alarm/options/nfc/NacDeleteNfcTagDialog.kt: -------------------------------------------------------------------------------- 1 | package com.nfcalarmclock.alarm.options.nfc 2 | 3 | import android.os.Bundle 4 | import android.view.LayoutInflater 5 | import android.view.View 6 | import android.view.ViewGroup 7 | import com.google.android.material.button.MaterialButton 8 | import com.nfcalarmclock.R 9 | import com.nfcalarmclock.view.dialog.NacBottomSheetDialogFragment 10 | 11 | /** 12 | * Ask the user if they want to delete an NFC tag. 13 | */ 14 | class NacDeleteNfcTagDialog 15 | : NacBottomSheetDialogFragment() 16 | { 17 | 18 | /** 19 | * Listener for deleting an NFC tag. 20 | */ 21 | fun interface OnDeleteNfcTagListener 22 | { 23 | fun onDelete() 24 | } 25 | 26 | /** 27 | * Listener for when the NFC tag is saved. 28 | */ 29 | var onDeleteNfcTagListener: OnDeleteNfcTagListener? = null 30 | 31 | /** 32 | * Called when the dialog view is created. 33 | */ 34 | override fun onCreateView( 35 | inflater: LayoutInflater, 36 | container: ViewGroup?, 37 | savedInstanceState: Bundle?) 38 | : View? 39 | { 40 | return inflater.inflate(R.layout.dlg_delete_nfc_tag, container, false) 41 | } 42 | 43 | /** 44 | * Called when the dialog view is created. 45 | */ 46 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) 47 | { 48 | // Super 49 | super.onViewCreated(view, savedInstanceState) 50 | 51 | // Get the views 52 | val deleteButton: MaterialButton = view.findViewById(R.id.delete_nfc_tag) 53 | val cancelButton: MaterialButton = view.findViewById(R.id.cancel_nfc_tag) 54 | 55 | // Setup the delete button 56 | setupPrimaryButton(deleteButton, listener = { 57 | 58 | // Call the listener 59 | onDeleteNfcTagListener?.onDelete() 60 | 61 | // Dismiss the dialog 62 | dismiss() 63 | 64 | }) 65 | 66 | // Setup the cancel button 67 | setupSecondaryButton(cancelButton) 68 | } 69 | 70 | companion object 71 | { 72 | 73 | /** 74 | * Tag for the class. 75 | */ 76 | const val TAG = "NacDeleteNfcTagDialog" 77 | 78 | } 79 | 80 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/nfcalarmclock/alarm/options/nfc/NacNfcTagViewHolder.kt: -------------------------------------------------------------------------------- 1 | package com.nfcalarmclock.alarm.options.nfc 2 | 3 | import android.view.View 4 | import android.widget.TextView 5 | import androidx.recyclerview.widget.RecyclerView.ViewHolder 6 | import com.google.android.material.button.MaterialButton 7 | import com.nfcalarmclock.R 8 | 9 | /** 10 | * View holder for an NFC tag. 11 | */ 12 | class NacNfcTagViewHolder( 13 | 14 | /** 15 | * Root view. 16 | */ 17 | val root: View 18 | 19 | // Constructor 20 | ) : ViewHolder(root) 21 | { 22 | 23 | /** 24 | * Name. 25 | */ 26 | val nameTextView: TextView = root.findViewById(R.id.nfc_tag_name) 27 | 28 | /** 29 | * NFC ID. 30 | */ 31 | val nfcIdTextView: TextView = root.findViewById(R.id.nfc_tag_id_value) 32 | 33 | /** 34 | * Delete button. 35 | */ 36 | val deleteButton: MaterialButton = root.findViewById(R.id.delete_button) 37 | 38 | /** 39 | * Rename button. 40 | */ 41 | val renameButton: MaterialButton = root.findViewById(R.id.rename_button) 42 | 43 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/nfcalarmclock/alarm/options/nfc/db/NacNfcTag.kt: -------------------------------------------------------------------------------- 1 | package com.nfcalarmclock.alarm.options.nfc.db 2 | 3 | import androidx.room.ColumnInfo 4 | import androidx.room.Entity 5 | import androidx.room.Index 6 | import androidx.room.PrimaryKey 7 | 8 | /** 9 | * An NFC tag. 10 | */ 11 | @Entity( 12 | tableName = "nfc_tag", 13 | indices = [Index(value=["nfc_id"], unique=true)] 14 | ) 15 | class NacNfcTag() 16 | { 17 | 18 | /** 19 | * Unique ID. 20 | */ 21 | @PrimaryKey(autoGenerate = true) 22 | @ColumnInfo(name = "id") 23 | var id: Long = 0 24 | 25 | /** 26 | * Name of the NFC tag. 27 | */ 28 | @ColumnInfo(name = "name") 29 | var name: String = "" 30 | 31 | /** 32 | * ID of the NFC tag. 33 | */ 34 | @ColumnInfo(name = "nfc_id") 35 | var nfcId: String = "" 36 | 37 | /** 38 | * Constructor. 39 | */ 40 | constructor(nfcTagName: String, nfcTagId: String) : this() 41 | { 42 | name = nfcTagName 43 | nfcId = nfcTagId 44 | } 45 | 46 | /** 47 | * Check if two tags are equal, except for the ID. 48 | */ 49 | fun equalsExceptId(tag: NacNfcTag): Boolean 50 | { 51 | return (name == tag.name) 52 | && (nfcId == tag.nfcId) 53 | } 54 | 55 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/nfcalarmclock/alarm/options/nfc/db/NacNfcTagDao.kt: -------------------------------------------------------------------------------- 1 | package com.nfcalarmclock.alarm.options.nfc.db 2 | 3 | import androidx.lifecycle.LiveData 4 | import androidx.room.Dao 5 | import androidx.room.Delete 6 | import androidx.room.Insert 7 | import androidx.room.Query 8 | import androidx.room.Update 9 | 10 | /** 11 | * Data access object for NFC tags. 12 | */ 13 | @Dao 14 | interface NacNfcTagDao 15 | { 16 | 17 | /** 18 | * Get all NFC tags. 19 | * 20 | * @return All NFC tags. 21 | */ 22 | @get:Query("SELECT * FROM nfc_tag ORDER BY name") 23 | val allNfcTags: LiveData> 24 | 25 | /** 26 | * Count the number of created alarm statistics. 27 | * 28 | * @return The number of created alarm statistics. 29 | */ 30 | @Query("SELECT COUNT(id) FROM nfc_tag") 31 | suspend fun count(): Long 32 | 33 | /** 34 | * Delete an NFC tag. 35 | * 36 | * @param nfcTag An NFC tag to delete. 37 | * 38 | * @return The number of rows deleted. 39 | */ 40 | @Delete 41 | suspend fun delete(nfcTag: NacNfcTag): Int 42 | 43 | /** 44 | * Find an NFC tag. 45 | * 46 | * @param nfcId The ID of the NFC tag to find. 47 | * 48 | * @return The NFC tag with the ID. 49 | */ 50 | @Query("SELECT * FROM nfc_tag WHERE nfc_id=:nfcId") 51 | suspend fun findNfcTag(nfcId: String): NacNfcTag? 52 | 53 | /** 54 | * Get all NFC tags. 55 | *

56 | * This will wait until all alarms are selected. 57 | * 58 | * @return All NFC tags. 59 | */ 60 | @Query("SELECT * FROM nfc_tag ORDER BY name") 61 | suspend fun getAllNfcTags(): List 62 | 63 | /** 64 | * Insert an NFC tag. 65 | * 66 | * @param nfcTag The NFC tag to insert. 67 | * 68 | * @return The row ID of the NFC tag that was inserted. 69 | */ 70 | @Insert 71 | suspend fun insert(nfcTag: NacNfcTag): Long 72 | 73 | /** 74 | * Update an existing NFC tag. 75 | * 76 | * @param nfcTag The NFC tag to update. 77 | * 78 | * @return The number of NFC tags updated. 79 | */ 80 | @Update 81 | suspend fun update(nfcTag: NacNfcTag): Int 82 | 83 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/nfcalarmclock/alarm/options/startweekon/NacStartWeekOnDialog.kt: -------------------------------------------------------------------------------- 1 | package com.nfcalarmclock.alarm.options.startweekon 2 | 3 | import android.app.AlertDialog 4 | import android.app.Dialog 5 | import android.os.Bundle 6 | import com.nfcalarmclock.R 7 | import com.nfcalarmclock.view.dialog.NacDialogFragment 8 | 9 | /** 10 | * Show a dialog asking the user which day they want to start the week on. 11 | */ 12 | class NacStartWeekOnDialog 13 | : NacDialogFragment() 14 | { 15 | 16 | /** 17 | * Listener for when a start week is selected. 18 | */ 19 | fun interface OnStartWeekSelectedListener 20 | { 21 | fun onStartWeekSelected(which: Int) 22 | } 23 | 24 | /** 25 | * Default start week on index. 26 | * 27 | * This will be changed externally. 28 | */ 29 | var defaultStartWeekOnIndex: Int = 0 30 | 31 | /** 32 | * The current start week on index. 33 | */ 34 | private var currentSelectedStartWeekOnIndex: Int = defaultStartWeekOnIndex 35 | 36 | /** 37 | * Listener for when an audio option is clicked. 38 | */ 39 | var onStartWeekSelectedListener: OnStartWeekSelectedListener? = null 40 | 41 | /** 42 | * Called when the dialog is created. 43 | */ 44 | override fun onCreateDialog(savedInstanceState: Bundle?): Dialog 45 | { 46 | // Setup the shared preferences 47 | setupSharedPreferences() 48 | 49 | // Create the dialog 50 | return AlertDialog.Builder(requireContext()) 51 | .setTitle(R.string.title_start_week_on) 52 | .setPositiveButton(R.string.action_ok) { _, _ -> 53 | 54 | // Call the listener 55 | onStartWeekSelectedListener?.onStartWeekSelected(currentSelectedStartWeekOnIndex) 56 | 57 | } 58 | .setNegativeButton(R.string.action_cancel, null) 59 | .setSingleChoiceItems(R.array.start_week_on, defaultStartWeekOnIndex) { _, which: Int -> 60 | 61 | // Set the current selected index 62 | currentSelectedStartWeekOnIndex = which 63 | 64 | } 65 | .create() 66 | } 67 | 68 | companion object 69 | { 70 | 71 | /** 72 | * Tag for the class. 73 | */ 74 | const val TAG = "NacStartWeekOnDialog" 75 | 76 | } 77 | 78 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/nfcalarmclock/alarm/options/tts/NacTranslate.kt: -------------------------------------------------------------------------------- 1 | package com.nfcalarmclock.alarm.options.tts 2 | 3 | import android.content.Context 4 | import com.nfcalarmclock.R 5 | import com.nfcalarmclock.util.NacCalendar 6 | import java.util.Calendar 7 | 8 | /** 9 | * How to translate certain phrases to other languages. 10 | */ 11 | object NacTranslate 12 | { 13 | 14 | /** 15 | * Get how to say the current time in any language. 16 | */ 17 | private fun getSayCurrentTime(context: Context): String 18 | { 19 | // Get the current hour and minute 20 | val calendar = Calendar.getInstance() 21 | val hour = calendar[Calendar.HOUR_OF_DAY] 22 | val minute = calendar[Calendar.MINUTE] 23 | 24 | // Get the meridian (if it should be used based on the user's preferences) 25 | val meridian = NacCalendar.getMeridian(context, hour) 26 | 27 | // Get the hour and minutes how they should be said by TTS 28 | val showHour = if (meridian.isNotEmpty()) hour.toString() else NacCalendar.to12HourFormat(hour) 29 | val showMinute = minute.toString().padStart(2, '0') 30 | 31 | // Return the TTS phrase 32 | return context.resources.getString(R.string.tts_say_time, showHour, showMinute, meridian) 33 | } 34 | 35 | /** 36 | * Get how to say the alarm reminder in any language. 37 | */ 38 | fun getSayReminder( 39 | context: Context, 40 | name: String, 41 | minute: Int 42 | ): String 43 | { 44 | // Get the alarm name if it is set, but if it is empty, then get the 45 | // generic name for an alarm 46 | val reminder = name.ifEmpty { context.resources.getString(R.string.word_alarm) } 47 | 48 | // Return the statement that should be said 49 | return context.resources.getQuantityString(R.plurals.tts_say_reminder, minute, 50 | reminder, minute) 51 | } 52 | 53 | /** 54 | * The text-to-speech phrase to say. 55 | */ 56 | fun getTtsPhrase( 57 | context: Context, 58 | shouldSayCurrentTime: Boolean, 59 | shouldSayAlarmName: Boolean, 60 | alarmName: String 61 | ): String 62 | { 63 | // Initialize the phrase 64 | var phrase = "" 65 | 66 | // Say the current time 67 | if (shouldSayCurrentTime) 68 | { 69 | phrase += getSayCurrentTime(context) 70 | } 71 | 72 | // Say the alarm name 73 | if (shouldSayAlarmName) 74 | { 75 | phrase += " " 76 | phrase += alarmName 77 | } 78 | 79 | return phrase 80 | } 81 | 82 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/nfcalarmclock/card/NacCardLayoutManager.kt: -------------------------------------------------------------------------------- 1 | package com.nfcalarmclock.card 2 | 3 | import android.content.Context 4 | import android.util.DisplayMetrics 5 | import androidx.recyclerview.widget.LinearLayoutManager 6 | import androidx.recyclerview.widget.LinearSmoothScroller 7 | import androidx.recyclerview.widget.RecyclerView 8 | 9 | /** 10 | * Linear layout manager for a recyclerview that is primarily used because it 11 | * handles the smooth scrolling. 12 | */ 13 | class NacCardLayoutManager(context: Context) 14 | : LinearLayoutManager(context) 15 | { 16 | 17 | /** 18 | * Smooth scrol lto the specified adapeter position. 19 | */ 20 | override fun smoothScrollToPosition( 21 | recyclerView: RecyclerView, 22 | state: RecyclerView.State, 23 | position: Int) 24 | { 25 | // Create a smooth scroller 26 | val smoothScroller = SmoothScroller(recyclerView.context, position) 27 | 28 | // Start the smooth scroll 29 | startSmoothScroll(smoothScroller) 30 | } 31 | 32 | /** 33 | * Smooth scroller 34 | */ 35 | class SmoothScroller(context: Context?, position: Int) 36 | : LinearSmoothScroller(context) 37 | { 38 | 39 | companion object 40 | { 41 | 42 | /** 43 | * Speed to scroll in millimeters per pixel. 44 | */ 45 | private const val SPEED = 50f 46 | 47 | } 48 | 49 | /** 50 | * Constructor. 51 | */ 52 | init 53 | { 54 | targetPosition = position 55 | } 56 | 57 | /** 58 | * Calculate the amount to scroll to consider the view visible. 59 | */ 60 | override fun calculateDtToFit(viewStart: Int, 61 | viewEnd: Int, 62 | boxStart: Int, 63 | boxEnd: Int, 64 | snapPreference: Int): Int 65 | { 66 | return (boxStart + (boxEnd - boxStart) / 2) - (viewStart + (viewEnd - viewStart) / 2) 67 | } 68 | 69 | /** 70 | * Calculate the scroll speed. 71 | */ 72 | override fun calculateSpeedPerPixel(dm: DisplayMetrics): Float 73 | { 74 | return SPEED / dm.densityDpi 75 | } 76 | 77 | } 78 | 79 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/nfcalarmclock/settings/NacGenericSettingFragment.kt: -------------------------------------------------------------------------------- 1 | package com.nfcalarmclock.settings 2 | 3 | import android.content.Context 4 | import androidx.preference.PreferenceFragmentCompat 5 | import com.nfcalarmclock.shared.NacSharedPreferences 6 | 7 | /** 8 | * Settings fragment. 9 | */ 10 | abstract class NacGenericSettingFragment 11 | : PreferenceFragmentCompat() 12 | { 13 | 14 | /** 15 | * Shared preference store. 16 | */ 17 | protected var sharedPreferences: NacSharedPreferences? = null 18 | 19 | /** 20 | * Called when the fragment is attached. 21 | */ 22 | override fun onAttach(context: Context) 23 | { 24 | // Super 25 | super.onAttach(context) 26 | 27 | // Set the shared preferences 28 | sharedPreferences = NacSharedPreferences(context) 29 | } 30 | 31 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/nfcalarmclock/settings/preference/NacPreferenceCategory.kt: -------------------------------------------------------------------------------- 1 | package com.nfcalarmclock.settings.preference 2 | 3 | import android.R 4 | import android.content.Context 5 | import android.util.AttributeSet 6 | import android.widget.TextView 7 | import androidx.preference.PreferenceCategory 8 | import androidx.preference.PreferenceViewHolder 9 | import com.nfcalarmclock.shared.NacSharedPreferences 10 | 11 | /** 12 | * Preference category. 13 | */ 14 | class NacPreferenceCategory 15 | : PreferenceCategory 16 | { 17 | 18 | /** 19 | * Shared preferences. 20 | */ 21 | private var sharedPreferences: NacSharedPreferences = NacSharedPreferences(context) 22 | 23 | /** 24 | * Constructor. 25 | */ 26 | constructor(context: Context) : super(context) 27 | { 28 | } 29 | 30 | /** 31 | * Constructor. 32 | */ 33 | constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) 34 | { 35 | } 36 | 37 | /** 38 | * Constructor. 39 | */ 40 | constructor(context: Context, attrs: AttributeSet?, style: Int) : super(context, attrs, style) 41 | { 42 | } 43 | 44 | /** 45 | * Constructor. 46 | */ 47 | init 48 | { 49 | isIconSpaceReserved = false 50 | } 51 | 52 | /** 53 | * Called when the view holder is bound. 54 | */ 55 | override fun onBindViewHolder(holder: PreferenceViewHolder) 56 | { 57 | // Super 58 | super.onBindViewHolder(holder) 59 | 60 | // Get the title text view 61 | val title = holder.findViewById(R.id.title) as TextView 62 | 63 | // Set the title text color 64 | title.setTextColor(sharedPreferences.themeColor) 65 | } 66 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/nfcalarmclock/statistics/AreYouSureResetStatisticsDialog.kt: -------------------------------------------------------------------------------- 1 | package com.nfcalarmclock.statistics 2 | 3 | import android.app.AlertDialog 4 | import android.app.Dialog 5 | import android.os.Bundle 6 | import com.nfcalarmclock.R 7 | import com.nfcalarmclock.view.dialog.NacDialogFragment 8 | 9 | class AreYouSureResetStatisticsDialog 10 | : NacDialogFragment() 11 | { 12 | 13 | companion object 14 | { 15 | 16 | /** 17 | * Tag for the class. 18 | */ 19 | const val TAG = "AreYouSureDialog" 20 | 21 | } 22 | 23 | /** 24 | * Listener for when the user has indicated they would like to reset 25 | * statistics. 26 | */ 27 | fun interface OnResetStatisticsListener 28 | { 29 | fun onResetStatistics() 30 | } 31 | 32 | /** 33 | * Listener for when the user has indicated they would like to reset 34 | * statistics. 35 | */ 36 | var onResetStatisticsListener: OnResetStatisticsListener? = null 37 | 38 | /** 39 | * Called when the dialog is created. 40 | */ 41 | override fun onCreateDialog(savedInstanceState: Bundle?): Dialog 42 | { 43 | // Setup the shared preferences 44 | setupSharedPreferences() 45 | 46 | // Build the dialog 47 | return AlertDialog.Builder(requireContext()) 48 | .setPositiveButton(R.string.action_yes) { _, _ -> 49 | 50 | // Call the listener 51 | onResetStatisticsListener?.onResetStatistics() 52 | 53 | } 54 | .setNegativeButton(R.string.action_no, null) 55 | .setTitle(R.string.title_reset_statistics) 56 | .setMessage(R.string.message_reset_statistics) 57 | .create() 58 | } 59 | 60 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/nfcalarmclock/statistics/db/NacAlarmCreatedStatistic.kt: -------------------------------------------------------------------------------- 1 | package com.nfcalarmclock.statistics.db 2 | 3 | import androidx.room.Entity 4 | import dagger.Module 5 | import dagger.Provides 6 | import dagger.hilt.InstallIn 7 | import dagger.hilt.components.SingletonComponent 8 | 9 | /** 10 | * Statistics for when an alarm is created. 11 | */ 12 | @Entity(tableName = "alarm_created_statistic", 13 | ignoredColumns = ["alarm_id", "hour", "minute", "name"]) 14 | class NacAlarmCreatedStatistic 15 | : NacAlarmStatistic() 16 | { 17 | 18 | /** 19 | * Check if two stats are equal, except for the ID. 20 | */ 21 | override fun equalsExceptId(stat: NacAlarmStatistic): Boolean 22 | { 23 | return (timestamp == stat.timestamp) 24 | } 25 | 26 | /** 27 | * Convert the data to a csv format so that it can be used to write to an 28 | * output file. 29 | */ 30 | override fun toCsvFormat(): String 31 | { 32 | return "$timestamp" 33 | } 34 | 35 | } 36 | 37 | /** 38 | * Hilt module to provide an instance of a created statistic. 39 | */ 40 | @InstallIn(SingletonComponent::class) 41 | @Module 42 | class NacAlarmCreatedStatisticModule 43 | { 44 | 45 | /** 46 | * Provide an instance of a created statistic. 47 | */ 48 | @Provides 49 | fun provideCreatedStatistic() : NacAlarmCreatedStatistic 50 | { 51 | return NacAlarmCreatedStatistic() 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /app/src/main/kotlin/com/nfcalarmclock/statistics/db/NacAlarmCreatedStatisticDao.kt: -------------------------------------------------------------------------------- 1 | package com.nfcalarmclock.statistics.db 2 | 3 | import androidx.room.Dao 4 | import androidx.room.Query 5 | 6 | /** 7 | * Data access object for storing when alarms were created. 8 | */ 9 | @Dao 10 | interface NacAlarmCreatedStatisticDao 11 | : NacAlarmStatisticDao 12 | { 13 | 14 | /** 15 | * Count the number of created alarm statistics. 16 | * 17 | * @return The number of created alarm statistics. 18 | */ 19 | @Query("SELECT COUNT(id) FROM alarm_created_statistic") 20 | suspend fun count(): Long 21 | 22 | /** 23 | * Delete all rows from the table. 24 | */ 25 | @Query("DELETE FROM alarm_created_statistic") 26 | suspend fun deleteAll(): Int 27 | 28 | /** 29 | * Get the date when the first alarm was created. 30 | * 31 | * @return The date when the first alarm was created. 32 | */ 33 | @Query("SELECT MIN(timestamp) FROM alarm_created_statistic LIMIT 1") 34 | suspend fun firstCreatedTimestamp(): Long 35 | 36 | /** 37 | * Get all instances when alarms were created. 38 | * 39 | * @return All instances when alarms were created. 40 | */ 41 | @Query("SELECT * FROM alarm_created_statistic") 42 | suspend fun getAll(): List 43 | 44 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/nfcalarmclock/statistics/db/NacAlarmDeletedStatistic.kt: -------------------------------------------------------------------------------- 1 | package com.nfcalarmclock.statistics.db 2 | 3 | import androidx.room.Entity 4 | import com.nfcalarmclock.alarm.db.NacAlarm 5 | import dagger.Module 6 | import dagger.Provides 7 | import dagger.hilt.InstallIn 8 | import dagger.hilt.components.SingletonComponent 9 | 10 | /** 11 | * Statistics for when an alarm is deleted. 12 | */ 13 | @Entity(tableName = "alarm_deleted_statistic", 14 | ignoredColumns = ["alarm_id"]) 15 | class NacAlarmDeletedStatistic 16 | : NacAlarmStatistic 17 | { 18 | 19 | /** 20 | * Constructor. 21 | */ 22 | constructor() : super() 23 | 24 | /** 25 | * Constructor. 26 | */ 27 | constructor(alarm: NacAlarm?) : super(alarm) 28 | 29 | } 30 | 31 | /** 32 | * Hilt module to provide an instance of a deleted statistic. 33 | */ 34 | @InstallIn(SingletonComponent::class) 35 | @Module 36 | class NacAlarmDeletedStatisticModule 37 | { 38 | 39 | /** 40 | * Provide an instance of a deleted statistic. 41 | */ 42 | @Provides 43 | fun provideDeletedStatistic() : NacAlarmDeletedStatistic 44 | { 45 | return NacAlarmDeletedStatistic() 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /app/src/main/kotlin/com/nfcalarmclock/statistics/db/NacAlarmDeletedStatisticDao.kt: -------------------------------------------------------------------------------- 1 | package com.nfcalarmclock.statistics.db 2 | 3 | import androidx.room.Dao 4 | import androidx.room.Query 5 | 6 | /** 7 | * Data access object for storing when alarms were deleted. 8 | */ 9 | @Dao 10 | interface NacAlarmDeletedStatisticDao 11 | : NacAlarmStatisticDao 12 | { 13 | 14 | /** 15 | * Count the number of deleted alarm statistics. 16 | * 17 | * @return The number of deleted alarm statistics. 18 | */ 19 | @Query("SELECT COUNT(id) FROM alarm_deleted_statistic") 20 | suspend fun count(): Long 21 | 22 | /** 23 | * Delete all rows from the table. 24 | */ 25 | @Query("DELETE FROM alarm_deleted_statistic") 26 | suspend fun deleteAll(): Int 27 | 28 | /** 29 | * Get all instances when alarms were deleted. 30 | * 31 | * @return All instances when alarms were deleted. 32 | */ 33 | @Query("SELECT * FROM alarm_deleted_statistic") 34 | suspend fun getAll(): List 35 | 36 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/nfcalarmclock/statistics/db/NacAlarmDismissedStatistic.kt: -------------------------------------------------------------------------------- 1 | package com.nfcalarmclock.statistics.db 2 | 3 | import androidx.room.ColumnInfo 4 | import androidx.room.Entity 5 | import com.nfcalarmclock.alarm.db.NacAlarm 6 | import dagger.Module 7 | import dagger.Provides 8 | import dagger.hilt.InstallIn 9 | import dagger.hilt.components.SingletonComponent 10 | 11 | /** 12 | * Statistics for when an alarm is dismissed. 13 | */ 14 | @Entity(tableName = "alarm_dismissed_statistic") 15 | class NacAlarmDismissedStatistic 16 | : NacAlarmStatistic 17 | { 18 | 19 | /** 20 | * Whether the alarm used NFC to dismiss or not. 21 | */ 22 | @ColumnInfo(name = "used_nfc") 23 | var usedNfc = false 24 | 25 | /** 26 | * Constructor. 27 | */ 28 | constructor() : super() 29 | 30 | /** 31 | * Constructor. 32 | */ 33 | constructor(alarm: NacAlarm?) : super(alarm) 34 | 35 | /** 36 | * Constructor. 37 | */ 38 | constructor(alarm: NacAlarm?, usedNfc: Boolean) : this(alarm) 39 | { 40 | this.usedNfc = usedNfc 41 | } 42 | 43 | /** 44 | * Check if two stats are equal, except for the ID. 45 | */ 46 | fun equalsExceptId(stat: NacAlarmDismissedStatistic): Boolean 47 | { 48 | return super.equalsExceptId(stat) 49 | && (usedNfc == stat.usedNfc) 50 | } 51 | 52 | /** 53 | * Convert the data to a csv format so that it can be used to write to an 54 | * output file. 55 | */ 56 | override fun toCsvFormat(): String 57 | { 58 | val csv = super.toCsvFormat() 59 | 60 | return "${csv},${usedNfc}" 61 | } 62 | 63 | } 64 | 65 | /** 66 | * Hilt module to provide an instance of a dismissed statistic. 67 | */ 68 | @InstallIn(SingletonComponent::class) 69 | @Module 70 | class NacAlarmDismissedStatisticModule 71 | { 72 | 73 | /** 74 | * Provide an instance of a dismissed statistic. 75 | */ 76 | @Provides 77 | fun provideDismissedStatistic() : NacAlarmDismissedStatistic 78 | { 79 | return NacAlarmDismissedStatistic() 80 | } 81 | 82 | } 83 | -------------------------------------------------------------------------------- /app/src/main/kotlin/com/nfcalarmclock/statistics/db/NacAlarmDismissedStatisticDao.kt: -------------------------------------------------------------------------------- 1 | package com.nfcalarmclock.statistics.db 2 | 3 | import androidx.room.Dao 4 | import androidx.room.Query 5 | 6 | /** 7 | * Data access object for storing when alarms were dismissed. 8 | */ 9 | @Dao 10 | interface NacAlarmDismissedStatisticDao 11 | : NacAlarmStatisticDao 12 | { 13 | 14 | /** 15 | * Cound the number of dismissed alarm statistics. 16 | * 17 | * @return The number of dismissed alarm statistics. 18 | */ 19 | @Query("SELECT COUNT(id) FROM alarm_dismissed_statistic") 20 | suspend fun count(): Long 21 | 22 | /** 23 | * Delete all rows from the table. 24 | */ 25 | @Query("DELETE FROM alarm_dismissed_statistic") 26 | suspend fun deleteAll(): Int 27 | 28 | /** 29 | * Count the number of dismissed with NFC alarm statistics. 30 | * 31 | * @return The number of dismissed with NFC alarm statistics. 32 | */ 33 | @Query("SELECT COUNT(id) FROM alarm_dismissed_statistic WHERE used_nfc=1") 34 | suspend fun nfcCount(): Long 35 | 36 | /** 37 | * Get all instances when alarms were dismissed. 38 | * 39 | * @return All instances when alarms were dismissed. 40 | */ 41 | @Query("SELECT * FROM alarm_dismissed_statistic") 42 | suspend fun getAll(): List 43 | 44 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/nfcalarmclock/statistics/db/NacAlarmMissedStatistic.kt: -------------------------------------------------------------------------------- 1 | package com.nfcalarmclock.statistics.db 2 | 3 | import androidx.room.Entity 4 | import com.nfcalarmclock.alarm.db.NacAlarm 5 | import dagger.Module 6 | import dagger.Provides 7 | import dagger.hilt.InstallIn 8 | import dagger.hilt.components.SingletonComponent 9 | 10 | /** 11 | * Statistics for when an alarm is missed. 12 | */ 13 | @Entity(tableName = "alarm_missed_statistic") 14 | class NacAlarmMissedStatistic 15 | : NacAlarmStatistic 16 | { 17 | 18 | /** 19 | * Constructor. 20 | */ 21 | constructor() : super() 22 | 23 | /** 24 | * Constructor. 25 | */ 26 | constructor(alarm: NacAlarm?) : super(alarm) 27 | 28 | } 29 | 30 | /** 31 | * Hilt module to provide an instance of a missed statistic. 32 | */ 33 | @InstallIn(SingletonComponent::class) 34 | @Module 35 | class NacAlarmMissedStatisticModule 36 | { 37 | 38 | /** 39 | * Provide an instance of a missed statistic. 40 | */ 41 | @Provides 42 | fun provideMissedStatistic() : NacAlarmMissedStatistic 43 | { 44 | return NacAlarmMissedStatistic() 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /app/src/main/kotlin/com/nfcalarmclock/statistics/db/NacAlarmMissedStatisticDao.kt: -------------------------------------------------------------------------------- 1 | package com.nfcalarmclock.statistics.db 2 | 3 | import androidx.room.Dao 4 | import androidx.room.Query 5 | 6 | /** 7 | * Data access object for storing when alarms were missed. 8 | */ 9 | @Dao 10 | interface NacAlarmMissedStatisticDao 11 | : NacAlarmStatisticDao 12 | { 13 | 14 | /** 15 | * Count the number of missed alarm statistics. 16 | * 17 | * @return The number of missed alarm statistics. 18 | */ 19 | @Query("SELECT COUNT(id) FROM alarm_missed_statistic") 20 | suspend fun count(): Long 21 | 22 | /** 23 | * Delete all rows from the table. 24 | */ 25 | @Query("DELETE FROM alarm_missed_statistic") 26 | suspend fun deleteAll(): Int 27 | 28 | /** 29 | * Get all instances when alarms were missed. 30 | * 31 | * @return All instances when alarms were missed. 32 | */ 33 | @Query("SELECT * FROM alarm_missed_statistic") 34 | suspend fun getAll(): List 35 | 36 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/nfcalarmclock/statistics/db/NacAlarmSnoozedStatistic.kt: -------------------------------------------------------------------------------- 1 | package com.nfcalarmclock.statistics.db 2 | 3 | import androidx.room.ColumnInfo 4 | import androidx.room.Entity 5 | import com.nfcalarmclock.alarm.db.NacAlarm 6 | import dagger.Module 7 | import dagger.Provides 8 | import dagger.hilt.InstallIn 9 | import dagger.hilt.components.SingletonComponent 10 | 11 | /** 12 | * Statistics for when an alarm is snoozed. 13 | */ 14 | @Entity(tableName = "alarm_snoozed_statistic") 15 | class NacAlarmSnoozedStatistic 16 | : NacAlarmStatistic 17 | { 18 | 19 | /** 20 | * Duration of the snooze. 21 | */ 22 | @ColumnInfo(name = "duration", defaultValue = "0") 23 | var duration: Long = 0 24 | 25 | /** 26 | * Constructor. 27 | */ 28 | constructor() : super() 29 | 30 | /** 31 | * Constructor. 32 | */ 33 | constructor(alarm: NacAlarm?) : super(alarm) 34 | 35 | /** 36 | * Constructor. 37 | */ 38 | constructor(alarm: NacAlarm?, duration: Long) : this(alarm) 39 | { 40 | this.duration = duration 41 | } 42 | 43 | /** 44 | * Check if two stats are equal, except for the ID. 45 | */ 46 | fun equalsExceptId(stat: NacAlarmSnoozedStatistic): Boolean 47 | { 48 | return super.equalsExceptId(stat) 49 | && (duration == stat.duration) 50 | } 51 | 52 | /** 53 | * Convert the data to a csv format so that it can be used to write to an 54 | * output file. 55 | */ 56 | override fun toCsvFormat(): String 57 | { 58 | val csv = super.toCsvFormat() 59 | 60 | return "${csv},${duration}" 61 | } 62 | 63 | } 64 | 65 | /** 66 | * Hilt module to provide an instance of a snoozed statistic. 67 | */ 68 | @InstallIn(SingletonComponent::class) 69 | @Module 70 | class NacAlarmSnoozedStatisticModule 71 | { 72 | 73 | /** 74 | * Provide an instance of a snoozed statistic. 75 | */ 76 | @Provides 77 | fun provideSnoozedStatistic() : NacAlarmSnoozedStatistic 78 | { 79 | return NacAlarmSnoozedStatistic() 80 | } 81 | 82 | } 83 | -------------------------------------------------------------------------------- /app/src/main/kotlin/com/nfcalarmclock/statistics/db/NacAlarmSnoozedStatisticDao.kt: -------------------------------------------------------------------------------- 1 | package com.nfcalarmclock.statistics.db 2 | 3 | import androidx.room.Dao 4 | import androidx.room.Query 5 | 6 | /** 7 | * Data access object for storing when alarms were snoozed. 8 | */ 9 | @Dao 10 | interface NacAlarmSnoozedStatisticDao 11 | : NacAlarmStatisticDao 12 | { 13 | 14 | /** 15 | * Count the number of snoozed alarm statistics. 16 | * 17 | * @return The number of snoozed alarm statistics. 18 | */ 19 | @Query("SELECT COUNT(id) FROM alarm_snoozed_statistic") 20 | suspend fun count(): Long 21 | 22 | /** 23 | * Delete all rows from the table. 24 | */ 25 | @Query("DELETE FROM alarm_snoozed_statistic") 26 | suspend fun deleteAll(): Int 27 | 28 | /** 29 | * Get the total snooze duration. 30 | * 31 | * @return The total snooze duration. 32 | */ 33 | @Query("SELECT SUM(duration) FROM alarm_snoozed_statistic") 34 | suspend fun totalDuration(): Long 35 | 36 | /** 37 | * Get all instances when alarms were snoozed. 38 | * 39 | * @return All instances when alarms were snoozed. 40 | */ 41 | @Query("SELECT * FROM alarm_snoozed_statistic") 42 | suspend fun getAll(): List 43 | 44 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/nfcalarmclock/statistics/db/NacAlarmStatistic.kt: -------------------------------------------------------------------------------- 1 | package com.nfcalarmclock.statistics.db 2 | 3 | import androidx.room.ColumnInfo 4 | import androidx.room.PrimaryKey 5 | import com.nfcalarmclock.alarm.db.NacAlarm 6 | import java.util.Date 7 | 8 | /** 9 | * Statistics for an alarm. 10 | */ 11 | abstract class NacAlarmStatistic() 12 | { 13 | 14 | /** 15 | * Unique ID. 16 | */ 17 | @PrimaryKey(autoGenerate = true) 18 | @ColumnInfo(name = "id") 19 | var id: Long = 0 20 | 21 | /** 22 | * Timestamp of when an alarm was snoozed. 23 | */ 24 | @ColumnInfo(name = "timestamp") 25 | var timestamp: Date 26 | 27 | /** 28 | * The ID of the alarm. 29 | */ 30 | @ColumnInfo(name = "alarm_id", index = true) 31 | var alarmId: Long? = null 32 | 33 | /** 34 | * The hour the alarm ran at. 35 | */ 36 | @ColumnInfo(name = "hour") 37 | var hour = 0 38 | 39 | /** 40 | * The minute the alarm ran at. 41 | */ 42 | @ColumnInfo(name = "minute") 43 | var minute = 0 44 | 45 | /** 46 | * The name of the alarm. 47 | */ 48 | @ColumnInfo(name = "name") 49 | var name: String = "" 50 | 51 | /** 52 | * Constructor. 53 | */ 54 | init 55 | { 56 | val timestamp = Date() 57 | this.timestamp = timestamp 58 | } 59 | 60 | /** 61 | * Constructor. 62 | */ 63 | constructor(alarm: NacAlarm?) : this() 64 | { 65 | if (alarm != null) 66 | { 67 | alarmId = alarm.id 68 | hour = alarm.hour 69 | minute = alarm.minute 70 | name = alarm.name 71 | } 72 | } 73 | 74 | /** 75 | * Check if two stats are equal, except for the ID. 76 | */ 77 | open fun equalsExceptId(stat: NacAlarmStatistic): Boolean 78 | { 79 | return (timestamp == stat.timestamp) 80 | && (hour == stat.hour) 81 | && (minute == stat.minute) 82 | && (name == stat.name) 83 | } 84 | 85 | /** 86 | * Convert the data to a csv format so that it can be used to write to an 87 | * output file. 88 | */ 89 | open fun toCsvFormat(): String 90 | { 91 | // Zero pad the hour and minute so that the time looks more uniform 92 | val clockHour = hour.toString().padStart(2, '0') 93 | val clockMinute = minute.toString().padStart(2, '0') 94 | 95 | return "${timestamp},${name},${clockHour}:${clockMinute}" 96 | } 97 | 98 | } 99 | -------------------------------------------------------------------------------- /app/src/main/kotlin/com/nfcalarmclock/statistics/db/NacAlarmStatisticDao.kt: -------------------------------------------------------------------------------- 1 | package com.nfcalarmclock.statistics.db 2 | 3 | import androidx.room.Insert 4 | 5 | /** 6 | * Data access object for storing when alarms were created. 7 | */ 8 | interface NacAlarmStatisticDao 9 | { 10 | 11 | /** 12 | * Insert an instance of an alarm statistic. 13 | */ 14 | @Insert 15 | suspend fun insert(stat: T): Long 16 | 17 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/nfcalarmclock/statistics/db/NacStatisticTypeConverters.kt: -------------------------------------------------------------------------------- 1 | package com.nfcalarmclock.statistics.db 2 | 3 | import androidx.room.TypeConverter 4 | import java.util.Date 5 | 6 | /** 7 | * Type converters for when an object is retrieved from the database. 8 | */ 9 | object NacStatisticTypeConverters 10 | { 11 | 12 | /** 13 | * Convert from a Long to a Date. 14 | */ 15 | @TypeConverter 16 | fun fromTimestamp(value: Long): Date 17 | { 18 | return Date(value) 19 | } 20 | 21 | /** 22 | * Convert from a Date to a Long. 23 | */ 24 | @TypeConverter 25 | fun dateToTimestamp(date: Date): Long 26 | { 27 | return date.time 28 | } 29 | 30 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/nfcalarmclock/system/file/NacTreeNode.kt: -------------------------------------------------------------------------------- 1 | package com.nfcalarmclock.system.file 2 | 3 | /** 4 | * Node in a tree. 5 | */ 6 | open class NacTreeNode( 7 | 8 | /** 9 | * Key. 10 | */ 11 | var key: T, 12 | 13 | /** 14 | * Value. 15 | */ 16 | var value: Any? = null, 17 | 18 | /** 19 | * Root node of this node. 20 | */ 21 | var root: NacTreeNode? = null 22 | 23 | ) 24 | { 25 | 26 | /** 27 | * Children of this node. 28 | */ 29 | val children: MutableList> = ArrayList() 30 | 31 | /** 32 | * Add a child. 33 | */ 34 | private fun addChild(child: NacTreeNode) 35 | { 36 | // Child already exists. Do not add it 37 | if (this.exists(child)) 38 | { 39 | return 40 | } 41 | 42 | // Add child 43 | children.add(child) 44 | } 45 | 46 | /** 47 | * @see .addChild 48 | */ 49 | fun addChild(key: T, value: Any?) 50 | { 51 | // Create child 52 | val child = NacTreeNode(key, value, this) 53 | 54 | // Add child 55 | this.addChild(child) 56 | } 57 | 58 | /** 59 | * Check if the child exists as a child of the node. 60 | * 61 | * @return True if the child exists as a child of the node, and False 62 | * otherwise. 63 | */ 64 | fun exists(child: NacTreeNode?): Boolean 65 | { 66 | return this.getChild(child) != null 67 | } 68 | 69 | /** 70 | * Get the child with the key. 71 | * 72 | * @return The child with the key. 73 | */ 74 | fun getChild(key: T): NacTreeNode? 75 | { 76 | // Iterate over each child 77 | for (c in children) 78 | { 79 | // Key matches 80 | if (c.key == key) 81 | { 82 | // Return the child 83 | return c 84 | } 85 | } 86 | 87 | // Unable to find the child 88 | return null 89 | } 90 | 91 | /** 92 | * @see .getChild 93 | */ 94 | private fun getChild(child: NacTreeNode?): NacTreeNode? 95 | { 96 | return if (child != null) 97 | { 98 | this.getChild(child.key) 99 | } 100 | else 101 | { 102 | null 103 | } 104 | } 105 | 106 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/nfcalarmclock/system/file/browser/NacFileBrowserViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.nfcalarmclock.system.file.browser 2 | 3 | import android.app.Application 4 | import android.content.Context 5 | import androidx.lifecycle.AndroidViewModel 6 | import androidx.lifecycle.viewModelScope 7 | import com.nfcalarmclock.system.file.NacFile 8 | import kotlinx.coroutines.launch 9 | 10 | /** 11 | * File browser view model. 12 | */ 13 | class NacFileBrowserViewModel(app: Application) 14 | : AndroidViewModel(app) 15 | { 16 | 17 | /** 18 | * Repository of file browser information. 19 | */ 20 | private val repository: NacFileBrowserRepository = NacFileBrowserRepository() 21 | 22 | /** 23 | * Current metadata added to the repository. 24 | */ 25 | val currentMetadata = repository.currentMetadata 26 | 27 | /** 28 | * Constructor. 29 | */ 30 | init 31 | { 32 | // Scan the repository 33 | viewModelScope.launch { 34 | repository.scan(app) 35 | } 36 | } 37 | 38 | /** 39 | * Change directory. 40 | */ 41 | fun cd(metadata: NacFile.Metadata) : String 42 | { 43 | // Change directory 44 | repository.fileTree.cd(metadata.name) 45 | 46 | // Determine the path of the directory that was clicked 47 | return if (metadata.name == NacFile.PREVIOUS_DIRECTORY) 48 | { 49 | // Previous directory 50 | repository.fileTree.directoryPath 51 | } 52 | else 53 | { 54 | // Current directory 55 | metadata.path 56 | } 57 | } 58 | 59 | /** 60 | * Clear the listing. 61 | */ 62 | fun clear() 63 | { 64 | viewModelScope.launch { 65 | repository.clear() 66 | } 67 | } 68 | 69 | /** 70 | * Show the listing at the given path. 71 | */ 72 | fun show(path: String, unit: () -> Unit = {}) 73 | { 74 | val context: Context = getApplication() 75 | 76 | viewModelScope.launch { 77 | 78 | // Show the listing 79 | repository.show(context, path) 80 | 81 | // Call the unit 82 | unit() 83 | 84 | } 85 | } 86 | 87 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/nfcalarmclock/system/permission/NacOptionalPermissionPreference.kt: -------------------------------------------------------------------------------- 1 | package com.nfcalarmclock.system.permission 2 | 3 | import android.content.Context 4 | import android.util.AttributeSet 5 | import android.widget.TextView 6 | import androidx.preference.Preference 7 | import androidx.preference.PreferenceViewHolder 8 | import com.nfcalarmclock.R 9 | import java.util.Locale 10 | 11 | /** 12 | * A preference that is used to display optional permissions. 13 | */ 14 | class NacOptionalPermissionPreference @JvmOverloads constructor( 15 | 16 | /** 17 | * Context. 18 | */ 19 | context: Context, 20 | 21 | /** 22 | * Attribute set. 23 | */ 24 | attrs: AttributeSet? = null, 25 | 26 | /** 27 | * Default style. 28 | */ 29 | style: Int = 0 30 | 31 | // Constructor 32 | ) : Preference(context, attrs, style) 33 | { 34 | 35 | /** 36 | * Constructor. 37 | */ 38 | init 39 | { 40 | layoutResource = R.layout.nac_preference_permission 41 | } 42 | 43 | /** 44 | * Called when the view holder is bound. 45 | */ 46 | override fun onBindViewHolder(holder: PreferenceViewHolder) 47 | { 48 | // Super 49 | super.onBindViewHolder(holder) 50 | 51 | // Get the textview 52 | val permissionType = holder.findViewById(R.id.permission_type) as TextView 53 | 54 | // Setup the textview 55 | setPermissionText(permissionType) 56 | setPermissionTextColor(permissionType) 57 | } 58 | 59 | /** 60 | * Set the permission text. 61 | */ 62 | private fun setPermissionText(textView: TextView) 63 | { 64 | // Get the message 65 | val locale = Locale.getDefault() 66 | val message = context.resources.getString(R.string.message_optional) 67 | 68 | // Set the text 69 | textView.text = message.lowercase(locale) 70 | } 71 | 72 | /** 73 | * Set the color of the permission text. 74 | */ 75 | private fun setPermissionTextColor(textView: TextView) 76 | { 77 | // Get the color based on the API 78 | val color = context.getColor(R.color.green) 79 | 80 | // Set the color 81 | textView.setTextColor(color) 82 | } 83 | 84 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/nfcalarmclock/system/permission/NacRequiredPermissionPreference.kt: -------------------------------------------------------------------------------- 1 | package com.nfcalarmclock.system.permission 2 | 3 | import android.content.Context 4 | import android.util.AttributeSet 5 | import android.widget.TextView 6 | import androidx.preference.Preference 7 | import androidx.preference.PreferenceViewHolder 8 | import com.nfcalarmclock.R 9 | import java.util.Locale 10 | 11 | /** 12 | * A preference that is used to display optional permissions. 13 | */ 14 | class NacRequiredPermissionPreference @JvmOverloads constructor( 15 | 16 | /** 17 | * Context. 18 | */ 19 | context: Context, 20 | 21 | /** 22 | * Attribute set. 23 | */ 24 | attrs: AttributeSet? = null, 25 | 26 | /** 27 | * Default style. 28 | */ 29 | style: Int = 0 30 | 31 | // Constructor 32 | ): Preference(context, attrs, style) 33 | { 34 | 35 | /** 36 | * Constructor. 37 | */ 38 | init 39 | { 40 | layoutResource = R.layout.nac_preference_permission 41 | } 42 | 43 | /** 44 | * Called when the view holder is bound. 45 | */ 46 | override fun onBindViewHolder(holder: PreferenceViewHolder) 47 | { 48 | // Super 49 | super.onBindViewHolder(holder) 50 | 51 | // Get the text view 52 | val permissionType = holder.findViewById(R.id.permission_type) as TextView 53 | 54 | // Setup the textview 55 | setPermissionText(permissionType) 56 | setPermissionTextColor(permissionType) 57 | } 58 | 59 | /** 60 | * Set the permission text. 61 | */ 62 | private fun setPermissionText(textView: TextView) 63 | { 64 | // Get the message 65 | val locale = Locale.getDefault() 66 | val message = context.resources.getString(R.string.message_required) 67 | 68 | // Set the text 69 | textView.text = message.lowercase(locale) 70 | } 71 | 72 | /** 73 | * Set the color of the permission text. 74 | */ 75 | private fun setPermissionTextColor(textView: TextView) 76 | { 77 | // Get the color based on the API 78 | val color = context.getColor(R.color.red) 79 | 80 | // Set the color 81 | textView.setTextColor(color) 82 | } 83 | 84 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/nfcalarmclock/system/permission/ignorebatteryoptimization/NacIgnoreBatteryOptimizationPermissionRequestDialog.kt: -------------------------------------------------------------------------------- 1 | package com.nfcalarmclock.system.permission.ignorebatteryoptimization 2 | 3 | import com.nfcalarmclock.R 4 | import com.nfcalarmclock.system.permission.NacPermissionRequestDialog 5 | 6 | /** 7 | * Dialog to request to ignore battery optimization. 8 | */ 9 | class NacIgnoreBatteryOptimizationPermissionRequestDialog 10 | : NacPermissionRequestDialog() 11 | { 12 | 13 | /** 14 | * The actions to execute when the permission request is accepted. 15 | */ 16 | override fun doPermissionRequestAccepted() 17 | { 18 | // Set the flag that the permission was requested 19 | sharedPreferences!!.wasIgnoreBatteryOptimizationPermissionRequested = true 20 | 21 | // Call the accepeted listeners 22 | super.doPermissionRequestAccepted() 23 | } 24 | 25 | /** 26 | * The actions to execute when the permission request is canceled. 27 | */ 28 | override fun doPermissionRequestCanceled() 29 | { 30 | // Set the flag that the permission was requested 31 | sharedPreferences!!.wasIgnoreBatteryOptimizationPermissionRequested = true 32 | 33 | // Call the accepeted listeners 34 | super.doPermissionRequestCanceled() 35 | } 36 | 37 | /** 38 | * The ID of the layout. 39 | */ 40 | override val layoutId: Int 41 | get() = R.layout.dlg_request_ignore_battery_optimization_permission 42 | 43 | /** 44 | * The ID of the title string. 45 | */ 46 | override val titleId: Int 47 | get() = R.string.title_permission_disable_battery_optimization 48 | 49 | companion object 50 | { 51 | 52 | /** 53 | * Tag for the class. 54 | */ 55 | const val TAG = "NacIgnoreBatteryOptimizationPermissionDialog" 56 | 57 | } 58 | 59 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/nfcalarmclock/system/permission/postnotifications/NacPostNotificationsPermissionRequestDialog.kt: -------------------------------------------------------------------------------- 1 | package com.nfcalarmclock.system.permission.postnotifications 2 | 3 | import android.os.Build 4 | import androidx.annotation.RequiresApi 5 | import com.nfcalarmclock.R 6 | import com.nfcalarmclock.system.permission.NacPermissionRequestDialog 7 | 8 | /** 9 | * Dialog to request to allow posting notifications. 10 | */ 11 | @RequiresApi(api = Build.VERSION_CODES.TIRAMISU) 12 | class NacPostNotificationsPermissionRequestDialog 13 | : NacPermissionRequestDialog() 14 | { 15 | 16 | /** 17 | * The name of the permission. 18 | */ 19 | override val permission: String = NacPostNotificationsPermission.permissionName 20 | 21 | /** 22 | * The ID of the layout. 23 | */ 24 | override val layoutId: Int = R.layout.dlg_request_post_notifications_permission 25 | 26 | /** 27 | * The ID of the title string. 28 | */ 29 | override val titleId: Int = R.string.title_permission_show_notifications 30 | 31 | /** 32 | * The ID of the text string. 33 | */ 34 | override val textId: Int = R.string.message_permission_post_notifications_request 35 | 36 | /** 37 | * The actions to execute when the permission request is accepted. 38 | */ 39 | override fun doPermissionRequestAccepted() 40 | { 41 | // Set the flag that the permission was requested 42 | sharedPreferences!!.wasPostNotificationsPermissionRequested = true 43 | 44 | // Call the accepeted listeners 45 | super.doPermissionRequestAccepted() 46 | } 47 | 48 | /** 49 | * The actions to execute when the permission request is canceled. 50 | */ 51 | override fun doPermissionRequestCanceled() 52 | { 53 | // Set the flag that the permission was requested 54 | sharedPreferences!!.wasPostNotificationsPermissionRequested = true 55 | 56 | // Call the canceled listeners 57 | super.doPermissionRequestCanceled() 58 | } 59 | 60 | companion object 61 | { 62 | 63 | /** 64 | * Tag for the class. 65 | */ 66 | const val TAG = "NacPostNotificationsPermissionDialog" 67 | 68 | } 69 | 70 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/nfcalarmclock/system/permission/readmediaaudio/NacReadMediaAudioPermission.kt: -------------------------------------------------------------------------------- 1 | package com.nfcalarmclock.system.permission.readmediaaudio 2 | 3 | import android.Manifest 4 | import android.app.Activity 5 | import android.content.Context 6 | import android.content.pm.PackageManager 7 | import android.os.Build 8 | import androidx.core.app.ActivityCompat 9 | import androidx.core.content.ContextCompat 10 | 11 | /** 12 | * Helper functions for the READ_MEDIA_AUDIO/READ_EXTERNAL_STORAGE permission. 13 | */ 14 | object NacReadMediaAudioPermission 15 | { 16 | 17 | /** 18 | * The name of the READ_MEDIA_AUDIO/READ_EXTERNAL_STORAGE permission. 19 | */ 20 | private val permissionName: String 21 | get() { 22 | 23 | // Define the permission string based on which version of Android is 24 | // running. Later versions need the more granular READ_MEDIA_AUDIO 25 | // permission whereas older versions can request READ_EXTERNAL_STORAGE 26 | // which allows an app to see a lot more files that are stored on the 27 | // user's phone 28 | return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) 29 | { 30 | Manifest.permission.READ_MEDIA_AUDIO 31 | } 32 | else 33 | { 34 | Manifest.permission.READ_EXTERNAL_STORAGE 35 | } 36 | } 37 | 38 | /** 39 | * Check if the app has the READ_MEDIA_AUDIO/READ_EXTERNAL_STORAGE 40 | * permission or not. 41 | * 42 | * @return True if the app has the READ_MEDIA_AUDIO/READ_EXTERNAL_STORAGE 43 | * permission, and False otherwise. 44 | */ 45 | @JvmStatic 46 | fun hasPermission(context: Context): Boolean 47 | { 48 | // Check if the app has permission to read external storage/media audio 49 | // (depending on version) 50 | return (ContextCompat.checkSelfPermission(context, permissionName) 51 | == PackageManager.PERMISSION_GRANTED) 52 | } 53 | 54 | /** 55 | * Request the READ_MEDIA_AUDIO/READ_EXTERNAL_STORAGE permission. 56 | */ 57 | @JvmStatic 58 | fun requestPermission(activity: Activity, requestCode: Int) 59 | { 60 | // Request the permission 61 | ActivityCompat.requestPermissions(activity, arrayOf(permissionName), requestCode) 62 | } 63 | 64 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/nfcalarmclock/system/permission/scheduleexactalarm/NacScheduleExactAlarmPermissionChangedBroadcastReceiver.kt: -------------------------------------------------------------------------------- 1 | package com.nfcalarmclock.system.permission.scheduleexactalarm 2 | 3 | import android.app.AlarmManager 4 | import android.content.BroadcastReceiver 5 | import android.content.Context 6 | import android.content.Intent 7 | import android.os.Build 8 | import com.nfcalarmclock.alarm.NacAlarmRepository 9 | import com.nfcalarmclock.system.scheduler.NacScheduler 10 | import com.nfcalarmclock.util.goAsync 11 | import dagger.hilt.android.AndroidEntryPoint 12 | import javax.inject.Inject 13 | 14 | /** 15 | * Refresh all alarms when the Schedule Exact Alarm Permission is granted. 16 | */ 17 | @AndroidEntryPoint 18 | class NacScheduleExactAlarmPermissionChangedBroadcastReceiver 19 | : BroadcastReceiver() 20 | { 21 | 22 | /** 23 | * Alarm repository. 24 | */ 25 | @Inject 26 | lateinit var alarmRepository: NacAlarmRepository 27 | 28 | /** 29 | * It is possible for another actor to send a spoofed intent with no 30 | * action string or a different action string and cause undesired behavior. 31 | * Ensure that the received Intent's action string matches the expected 32 | * value before restoring alarms. 33 | */ 34 | override fun onReceive(context: Context, intent: Intent) = goAsync { 35 | 36 | // Do not do anything if the Android version is not correct 37 | if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) 38 | { 39 | return@goAsync 40 | } 41 | 42 | // Check the intent action corresponds to the permission being changed 43 | if (intent.action == AlarmManager.ACTION_SCHEDULE_EXACT_ALARM_PERMISSION_STATE_CHANGED) 44 | { 45 | val alarmManager = context.getSystemService( 46 | Context.ALARM_SERVICE) as AlarmManager 47 | 48 | // Make sure the permission was changed such that exact alarms can be 49 | // scheduled 50 | if (alarmManager.canScheduleExactAlarms()) 51 | { 52 | // Get all alarms from the repository 53 | val alarms = alarmRepository.getAllAlarms() 54 | 55 | // Refresh all alarms 56 | NacScheduler.refreshAll(context, alarms) 57 | } 58 | } 59 | 60 | } 61 | 62 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/nfcalarmclock/system/permission/scheduleexactalarm/NacScheduleExactAlarmPermissionRequestDialog.kt: -------------------------------------------------------------------------------- 1 | package com.nfcalarmclock.system.permission.scheduleexactalarm 2 | 3 | import android.os.Build 4 | import androidx.annotation.RequiresApi 5 | import com.nfcalarmclock.R 6 | import com.nfcalarmclock.system.permission.NacPermissionRequestDialog 7 | 8 | /** 9 | * Dialog to request to allow scheduling an exact alarm. 10 | */ 11 | @RequiresApi(api = Build.VERSION_CODES.S) 12 | class NacScheduleExactAlarmPermissionRequestDialog 13 | : NacPermissionRequestDialog() 14 | { 15 | 16 | /** 17 | * The name of the permission. 18 | */ 19 | override val permission: String 20 | get() = NacScheduleExactAlarmPermission.permissionName 21 | 22 | /** 23 | * The ID of the layout. 24 | */ 25 | override val layoutId: Int 26 | get() = R.layout.dlg_request_schedule_exact_alarm_permission 27 | 28 | /** 29 | * The ID of the title string. 30 | */ 31 | override val titleId: Int 32 | get() = R.string.title_request_permission_schedule_exact_alarm 33 | 34 | /** 35 | * The ID of the text string. 36 | */ 37 | override val textId: Int = R.string.message_permission_schedule_exact_alarm_request 38 | 39 | /** 40 | * The actions to execute when the permission request is accepted. 41 | */ 42 | override fun doPermissionRequestAccepted() 43 | { 44 | // Set the flag that the permission was requested 45 | sharedPreferences!!.wasScheduleExactAlarmPermissionRequested = true 46 | 47 | // Call the accepeted listeners 48 | super.doPermissionRequestAccepted() 49 | } 50 | 51 | /** 52 | * The actions to execute when the permission request is canceled. 53 | */ 54 | override fun doPermissionRequestCanceled() 55 | { 56 | // Set the flag that the permission was requested 57 | sharedPreferences!!.wasScheduleExactAlarmPermissionRequested = true 58 | 59 | // Call the accepeted listeners 60 | super.doPermissionRequestCanceled() 61 | } 62 | 63 | companion object 64 | { 65 | 66 | /** 67 | * Tag for the class. 68 | */ 69 | const val TAG = "NacScheduleExactAlarmPermissionDialog" 70 | 71 | } 72 | 73 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/nfcalarmclock/system/triggers/appupdate/NacAppUpdatedBroadcastReceiver.kt: -------------------------------------------------------------------------------- 1 | package com.nfcalarmclock.system.triggers.appupdate 2 | 3 | import android.content.BroadcastReceiver 4 | import android.content.Context 5 | import android.content.Intent 6 | import com.nfcalarmclock.db.NacAlarmDatabase 7 | import com.nfcalarmclock.shared.NacSharedPreferences 8 | import com.nfcalarmclock.system.scheduler.NacScheduler 9 | import com.nfcalarmclock.util.goAsync 10 | 11 | /** 12 | * After the app is updated, reapply the alarms. 13 | * 14 | * When the app is updated, any alarms that were set are lost. This will attempt to restore those 15 | * alarms. 16 | */ 17 | class NacAppUpdatedBroadcastReceiver 18 | : BroadcastReceiver() 19 | { 20 | 21 | /** 22 | * It is possible for another actor to send a spoofed intent with no 23 | * action string or a different action string and cause undesired behavior. 24 | * Ensure that the received Intent's action string matches the expected 25 | * value before restoring alarms. 26 | */ 27 | override fun onReceive(context: Context, intent: Intent) = goAsync { 28 | 29 | // Check that the action is correct 30 | if (intent.action == Intent.ACTION_MY_PACKAGE_REPLACED) 31 | { 32 | // Move shared preferences to device protected storage 33 | NacSharedPreferences.moveToDeviceProtectedStorage(context) 34 | 35 | // Get the database. Before opening it, a check will run to move the database 36 | // to device protected storage 37 | val db = NacAlarmDatabase.getInstance(context) 38 | val sharedPreferences = NacSharedPreferences(context) 39 | 40 | // Get all the alarms 41 | val alarmDao = db.alarmDao() 42 | val allAlarms = alarmDao.getAllAlarms() 43 | 44 | // Reschedule all the alarms 45 | NacScheduler.updateAll(context, allAlarms) 46 | 47 | // Check if should fix any auto dismiss, auto snooze, or snooze duration values 48 | // that are set to 0 in alarms. 49 | if (!sharedPreferences.eventFixZeroAutoDismissAndSnooze) 50 | { 51 | sharedPreferences.runEventFixZeroAutoDismissAndSnooze( 52 | allAlarms, 53 | onAlarmChanged = { alarm -> 54 | 55 | // Update the database and reschedule the alarm 56 | alarmDao.update(alarm) 57 | NacScheduler.update(context, alarm) 58 | 59 | }) 60 | } 61 | 62 | // Save the next alarm 63 | sharedPreferences.saveNextAlarm(allAlarms) 64 | } 65 | 66 | } 67 | 68 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/nfcalarmclock/system/triggers/shutdown/NacShutdownBroadcastReceiver.kt: -------------------------------------------------------------------------------- 1 | package com.nfcalarmclock.system.triggers.shutdown 2 | 3 | import android.content.BroadcastReceiver 4 | import android.content.Context 5 | import android.content.Intent 6 | import com.nfcalarmclock.alarm.NacAlarmRepository 7 | import com.nfcalarmclock.system.scheduler.NacScheduler 8 | import com.nfcalarmclock.util.goAsync 9 | import dagger.hilt.android.AndroidEntryPoint 10 | import javax.inject.Inject 11 | 12 | /** 13 | * Remove any active alarms on shutdown or reboot. 14 | * 15 | * Note: This needs to be registered in NacMainActivity because: 16 | * 17 | * "As of Build.VERSION_CODES#P this broadcast is only sent to receivers 18 | * registered through Context.registerReceiver." 19 | */ 20 | @AndroidEntryPoint 21 | class NacShutdownBroadcastReceiver 22 | : BroadcastReceiver() 23 | { 24 | 25 | /** 26 | * Alarm repository. 27 | */ 28 | @Inject 29 | lateinit var alarmRepository: NacAlarmRepository 30 | 31 | /** 32 | * It is possible for another actor to send a spoofed intent with no 33 | * action string or a different action string and cause undesired behavior. 34 | * Ensure that the received Intent's action string matches the expected 35 | * value before restoring alarms. 36 | */ 37 | override fun onReceive(context: Context, intent: Intent) = goAsync { 38 | 39 | // Check that the intent action is correct 40 | if ((intent.action == Intent.ACTION_SHUTDOWN) 41 | || (intent.action == Intent.ACTION_REBOOT)) 42 | { 43 | // Get the active alarms from the repository 44 | val alarms = alarmRepository.getActiveAlarms() 45 | 46 | // Iterate over each active alarm 47 | for (a in alarms) 48 | { 49 | // Dismiss the alarm 50 | a.dismiss() 51 | 52 | // Update the repo now that the alarm is no longer active 53 | alarmRepository.update(a) 54 | 55 | // Cancel the alarm 56 | NacScheduler.cancel(context, a) 57 | } 58 | } 59 | 60 | } 61 | 62 | } 63 | -------------------------------------------------------------------------------- /app/src/main/kotlin/com/nfcalarmclock/system/triggers/startup/NacStartupBroadcastReceiver.kt: -------------------------------------------------------------------------------- 1 | package com.nfcalarmclock.system.triggers.startup 2 | 3 | import android.content.BroadcastReceiver 4 | import android.content.Context 5 | import android.content.Intent 6 | import com.nfcalarmclock.alarm.NacAlarmRepository 7 | import com.nfcalarmclock.system.scheduler.NacScheduler 8 | import com.nfcalarmclock.util.goAsync 9 | import dagger.hilt.android.AndroidEntryPoint 10 | import javax.inject.Inject 11 | 12 | /** 13 | * Restore alarms on startup. This should support direct boot mode as well. 14 | */ 15 | @AndroidEntryPoint 16 | class NacStartupBroadcastReceiver 17 | : BroadcastReceiver() 18 | { 19 | 20 | /** 21 | * Alarm repository. 22 | */ 23 | @Inject 24 | lateinit var alarmRepository: NacAlarmRepository 25 | 26 | /** 27 | * It is possible for another actor to send a spoofed intent with no 28 | * action string or a different action string and cause undesired behavior. 29 | * Ensure that the received Intent's action string matches the expected 30 | * value before restoring alarms. 31 | */ 32 | override fun onReceive(context: Context, intent: Intent) = goAsync { 33 | 34 | //println("RECEIVED THE BROADCAST : ${intent.action}") 35 | // Check that the intent action is correct 36 | if ((intent.action == Intent.ACTION_BOOT_COMPLETED) 37 | || (intent.action == Intent.ACTION_LOCKED_BOOT_COMPLETED)) 38 | { 39 | //println("READING ALARMS") 40 | // Get all the alarms 41 | val alarms = alarmRepository.getAllAlarms() 42 | //println("FOUND ALARMS : ${alarms.size}") 43 | 44 | // Update all the alarms 45 | //println("UPDATING SCHEDULE OF ALARMS") 46 | NacScheduler.updateAll(context, alarms) 47 | } 48 | 49 | } 50 | 51 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/nfcalarmclock/system/triggers/timechange/NacTimeChangeBroadcastReceiver.kt: -------------------------------------------------------------------------------- 1 | package com.nfcalarmclock.system.triggers.timechange 2 | 3 | import android.content.BroadcastReceiver 4 | import android.content.Context 5 | import android.content.Intent 6 | import com.nfcalarmclock.alarm.NacAlarmRepository 7 | import com.nfcalarmclock.shared.NacSharedPreferences 8 | import com.nfcalarmclock.system.scheduler.NacScheduler 9 | import com.nfcalarmclock.util.goAsync 10 | import dagger.hilt.android.AndroidEntryPoint 11 | import javax.inject.Inject 12 | 13 | /** 14 | * Receive this signal from AlarmManager and start the foreground service. 15 | */ 16 | @AndroidEntryPoint 17 | class NacTimeChangeBroadcastReceiver 18 | : BroadcastReceiver() 19 | { 20 | 21 | /** 22 | * Alarm repository. 23 | */ 24 | @Inject 25 | lateinit var alarmRepository: NacAlarmRepository 26 | 27 | /** 28 | * It is possible for another actor to send a spoofed intent with no 29 | * action string or a different action string and cause undesired behavior. 30 | * Ensure that the received Intent's action string matches the expected 31 | * value before updating alarms. 32 | */ 33 | override fun onReceive(context: Context, intent: Intent) = goAsync { 34 | 35 | // Check that the intent action is correct 36 | if ((intent.action == Intent.ACTION_DATE_CHANGED) 37 | || (intent.action == Intent.ACTION_TIME_CHANGED) 38 | || (intent.action == Intent.ACTION_TIMEZONE_CHANGED) 39 | || (intent.action == Intent.ACTION_LOCALE_CHANGED)) 40 | { 41 | // Get all the alarms 42 | val sharedPreferences = NacSharedPreferences(context) 43 | val allAlarms = alarmRepository.getAllAlarms() 44 | 45 | // Update all the alarms 46 | NacScheduler.updateAll(context, allAlarms) 47 | 48 | // Save the next alarm 49 | sharedPreferences.saveNextAlarm(allAlarms) 50 | } 51 | 52 | } 53 | 54 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/nfcalarmclock/util/NacUtility.kt: -------------------------------------------------------------------------------- 1 | package com.nfcalarmclock.util 2 | 3 | import android.content.Context 4 | import android.view.View 5 | import android.view.View.MeasureSpec 6 | import android.view.ViewGroup.MarginLayoutParams 7 | import android.widget.Toast 8 | 9 | /** 10 | * NFC Alarm Clock Utility class. 11 | *

12 | * Composed of static methods that can be used for various things. 13 | */ 14 | object NacUtility 15 | { 16 | 17 | /** 18 | * Determine the height of the view. 19 | * 20 | * @param view The view. 21 | * 22 | * @return The height of the view. 23 | */ 24 | fun getHeight(view: View): Int 25 | { 26 | // Measure the view 27 | view.measure(MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED), 28 | MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED)) 29 | 30 | // Calculate the height, including the margins 31 | val lp = view.layoutParams as MarginLayoutParams 32 | val margins = lp.topMargin + lp.bottomMargin 33 | val height = view.measuredHeight 34 | 35 | return height + margins 36 | } 37 | 38 | /** 39 | * Create a toast that displays for a short period of time. 40 | */ 41 | fun quickToast(context: Context, message: String): Toast 42 | { 43 | return toast(context, message, Toast.LENGTH_SHORT) 44 | } 45 | 46 | /** 47 | * Create a toast that displays for a short period of time. 48 | */ 49 | fun quickToast(context: Context, resId: Int): Toast 50 | { 51 | // Get the message 52 | val message = context.getString(resId) 53 | 54 | // Show the toast 55 | return toast(context, message, Toast.LENGTH_SHORT) 56 | } 57 | 58 | /** 59 | * Create a toast. 60 | */ 61 | fun toast(context: Context, resId: Int): Toast 62 | { 63 | // Get the message 64 | val message = context.getString(resId) 65 | 66 | // Show the toast 67 | return toast(context, message, Toast.LENGTH_LONG) 68 | } 69 | 70 | /** 71 | * Create a toast. 72 | */ 73 | fun toast(context: Context, message: String): Toast 74 | { 75 | return toast(context, message, Toast.LENGTH_LONG) 76 | } 77 | 78 | /** 79 | * Create a toast. 80 | */ 81 | fun toast(context: Context, message: String, duration: Int): Toast 82 | { 83 | // Create the toast 84 | val toast = Toast.makeText(context, message, duration) 85 | 86 | // Show the toast 87 | toast.show() 88 | 89 | return toast 90 | } 91 | 92 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/nfcalarmclock/view/alarmoptionlayout/NacAlarmOptionLayout.kt: -------------------------------------------------------------------------------- 1 | package com.nfcalarmclock.view.alarmoptionlayout 2 | 3 | import android.content.Context 4 | import android.util.AttributeSet 5 | import android.widget.LinearLayout 6 | import com.google.android.material.button.MaterialButton 7 | import com.nfcalarmclock.R 8 | 9 | /** 10 | * Alarm option layout so that the layout can be consistent across each new alarm option. 11 | */ 12 | class NacAlarmOptionLayout @JvmOverloads constructor( 13 | context: Context, 14 | attrs: AttributeSet? = null 15 | ) : LinearLayout(context, attrs) 16 | { 17 | 18 | /** 19 | * Constructor. 20 | */ 21 | init 22 | { 23 | inflate(context, R.layout.nac_alarm_option_layout, this) 24 | } 25 | 26 | /** 27 | * Add child views when finished being inflated. 28 | */ 29 | override fun onFinishInflate() 30 | { 31 | // Super 32 | super.onFinishInflate() 33 | 34 | // Check if has more than one child. This should always be the case, but better to 35 | // be cautious 36 | if (childCount <= 1) 37 | { 38 | return 39 | } 40 | 41 | // Get the main and inner most layouts 42 | val mainLayout: LinearLayout = getChildAt(0) as LinearLayout 43 | val innerLayout: LinearLayout = findViewById(R.id.container) 44 | 45 | // Iterate over each child 46 | for (i in 1.. 2 | 3 | 4 | 15 | 16 | 21 | 22 | -------------------------------------------------------------------------------- /app/src/main/res/anim/fade_out.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 15 | 16 | 21 | 22 | -------------------------------------------------------------------------------- /app/src/main/res/anim/pulse.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 17 | 18 | 24 | 25 | -------------------------------------------------------------------------------- /app/src/main/res/animator/card_color_collapse.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/animator/card_color_expand.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/animator/card_color_highlight.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/animator/nac_day_button_alpha.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 15 | 16 | 17 | 18 | 19 | 20 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /app/src/main/res/animator/nac_day_button_text.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 14 | 15 | 16 | 17 | 18 | 19 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /app/src/main/res/animator/support_development.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/color/card_toggle_button.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 9 | 10 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /app/src/main/res/color/nac_day_button_stroke.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 9 | 10 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/alarm.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/analytics.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/arrow_left.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/arrow_right.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/auto_delete.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/battery_alert.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/cancel.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/card_divider.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 10 | 11 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/collapse.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/color_example.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 9 | 10 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/color_selector.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/copy.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/delete.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/dismiss.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/edit.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/expand.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/favorite.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/flashlight_on_32.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/folder.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/format_align_left_23.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/format_bold.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/format_size.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 11 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/import_export.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/info.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/label.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/launch_32.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/list.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/minus_24.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/music_note.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/notifications.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/opacity.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/palette.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/play.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/plus.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/position_32.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/repeat.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/reset.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/round_action_button.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/schedule.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/send.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/settings.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/shader_selector.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 9 | 10 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/slider_path.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/snooze.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/tap_and_play.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/vibrate.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/visibility.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/volume_high.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/volume_low.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/volume_med.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/volume_off.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/layout/act_setting.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 11 | 18 | 19 | -------------------------------------------------------------------------------- /app/src/main/res/layout/act_sound.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 15 | 16 | 17 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /app/src/main/res/layout/card_copy.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 21 | 22 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /app/src/main/res/layout/card_delete.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 21 | 22 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /app/src/main/res/layout/card_frame.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/layout/dlg_alarm_days.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /app/src/main/res/layout/dlg_alarm_name.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 20 | 21 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /app/src/main/res/layout/dlg_audio_source.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | 15 | 16 | 17 | 23 | 24 | 25 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /app/src/main/res/layout/dlg_delete_nfc_tag.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 13 | 21 | 22 | 23 | 31 | 32 | 33 | 34 | 35 | 36 | 44 | 45 | 46 | 54 | 55 | -------------------------------------------------------------------------------- /app/src/main/res/layout/dlg_import_export.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 13 | 21 | 22 | 23 | 31 | 32 | -------------------------------------------------------------------------------- /app/src/main/res/layout/dlg_media_playlist.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 18 | 19 | 25 | 26 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /app/src/main/res/layout/dlg_save_nfc_tag.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | 15 | 16 | 17 | 23 | 24 | 25 | 37 | 38 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /app/src/main/res/layout/dlg_select_nfc_tag.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | 15 | 16 | 17 | 23 | 24 | 25 | 29 | 30 | 31 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /app/src/main/res/layout/dlg_whats_new.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 24 | 25 | 38 | 39 | 40 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /app/src/main/res/layout/frg_buttons.xml: -------------------------------------------------------------------------------- 1 | 9 | 10 |