├── .github └── workflows │ └── android.yml ├── .gitignore ├── LICENSE ├── README.md ├── app-analytics-fake ├── build.gradle └── src │ └── main │ ├── AndroidManifest.xml │ └── java │ └── xyz │ └── aprildown │ └── timer │ └── app │ └── analytics │ ├── AnalyticsModule.kt │ └── AppTrackerImpl.kt ├── app-backup ├── build.gradle └── src │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── xyz │ │ │ └── aprildown │ │ │ └── timer │ │ │ └── app │ │ │ └── backup │ │ │ ├── AppPreferencesProviderImpl.kt │ │ │ ├── BackupComposables.kt │ │ │ ├── BackupFragment.kt │ │ │ ├── BaseBackupViewModel.kt │ │ │ ├── CloudBackupPreference.kt │ │ │ ├── ExportFragment.kt │ │ │ ├── ExportViewModel.kt │ │ │ ├── ImportFragment.kt │ │ │ ├── ImportViewModel.kt │ │ │ └── SafIntentSafeBelt.kt │ └── res │ │ ├── layout │ │ └── layout_cloud_backup_widget.xml │ │ └── xml │ │ └── pref_backup.xml │ └── test │ └── java │ └── xyz │ └── aprildown │ └── timer │ └── app │ └── backup │ ├── BaseBackupViewModelTest.kt │ ├── ExportViewModelTest.kt │ └── ImportViewModelTest.kt ├── app-base ├── build.gradle └── src │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── xyz │ │ │ └── aprildown │ │ │ └── timer │ │ │ └── app │ │ │ └── base │ │ │ ├── data │ │ │ ├── DarkTheme.kt │ │ │ ├── FlavorData.kt │ │ │ ├── FloatingWindowPip.kt │ │ │ ├── PreferenceData.kt │ │ │ └── ShowcaseData.kt │ │ │ ├── media │ │ │ ├── AsyncRingtonePlayer.kt │ │ │ ├── AudioFocusManager.kt │ │ │ ├── Beeper.kt │ │ │ ├── MediaMetadataUtils.kt │ │ │ ├── RingtonePreviewKlaxon.kt │ │ │ ├── Torch.kt │ │ │ └── VibrateHelper.kt │ │ │ ├── ui │ │ │ ├── AppNavigator.kt │ │ │ ├── AppTheme.kt │ │ │ ├── BaseActivity.kt │ │ │ ├── BasePreferenceFragmentCompat.kt │ │ │ ├── DynamicTheme.kt │ │ │ ├── EdgeToEdgeUtils.kt │ │ │ ├── FlavorUiInjector.kt │ │ │ ├── ListEmptyView.kt │ │ │ ├── MainCallback.kt │ │ │ ├── SpecialItemTouchHelperCallback.kt │ │ │ └── StepUpdater.kt │ │ │ ├── utils │ │ │ ├── AppInfoNotificationManager.kt │ │ │ ├── AppThemeUtils.kt │ │ │ ├── LogToFileTree.kt │ │ │ ├── NavigationUtils.kt │ │ │ ├── ScreenWakeLock.kt │ │ │ ├── ShortcutHelper.kt │ │ │ ├── TimeConverter.kt │ │ │ ├── UiNameTranslators.kt │ │ │ ├── ViewUtils.kt │ │ │ ├── WebsiteOpener.kt │ │ │ └── WeekdaysFormatter.kt │ │ │ └── widgets │ │ │ └── TimePickerFix.kt │ └── res │ │ ├── anim │ │ ├── close_enter.xml │ │ ├── close_exit.xml │ │ ├── ic_anim_pause_play_interpolator_0.xml │ │ ├── ic_anim_pause_play_interpolator_1.xml │ │ ├── ic_anim_play_pause_interpolator_0.xml │ │ ├── ic_anim_play_pause_interpolator_1.xml │ │ ├── ic_anim_ringing.xml │ │ ├── open_enter.xml │ │ ├── open_exit.xml │ │ ├── slide_in_bottom.xml │ │ ├── slide_in_left.xml │ │ ├── slide_in_right.xml │ │ ├── slide_out_bottom.xml │ │ ├── slide_out_left.xml │ │ └── slide_out_right.xml │ │ ├── drawable-v24 │ │ └── ic_launcher_foreground.xml │ │ ├── drawable │ │ ├── background_step_number.xml │ │ ├── ic_ad_off.xml │ │ ├── ic_add.xml │ │ ├── ic_anim_arrow_right_indicator.xml │ │ ├── ic_anim_pause.xml │ │ ├── ic_anim_pause_to_play.xml │ │ ├── ic_anim_play.xml │ │ ├── ic_anim_play_to_pause.xml │ │ ├── ic_anim_ring.xml │ │ ├── ic_anim_ringing.xml │ │ ├── ic_arrow_down.xml │ │ ├── ic_arrow_left_indicator.xml │ │ ├── ic_arrow_up.xml │ │ ├── ic_back.xml │ │ ├── ic_backup.xml │ │ ├── ic_beep.xml │ │ ├── ic_calendar_more_events.xml │ │ ├── ic_check.xml │ │ ├── ic_count.xml │ │ ├── ic_cross.xml │ │ ├── ic_delete.xml │ │ ├── ic_dewey_reed.xml │ │ ├── ic_edit.xml │ │ ├── ic_exit.xml │ │ ├── ic_expand.xml │ │ ├── ic_flashlight.xml │ │ ├── ic_grid_view.xml │ │ ├── ic_half.xml │ │ ├── ic_halt.xml │ │ ├── ic_heart.xml │ │ ├── ic_image.xml │ │ ├── ic_launcher_background.xml │ │ ├── ic_left.xml │ │ ├── ic_list_view.xml │ │ ├── ic_locked.xml │ │ ├── ic_menu.xml │ │ ├── ic_minus.xml │ │ ├── ic_music.xml │ │ ├── ic_notification.xml │ │ ├── ic_overflow.xml │ │ ├── ic_pause.xml │ │ ├── ic_plus_one.xml │ │ ├── ic_record_predefined_time.xml │ │ ├── ic_scheduler.xml │ │ ├── ic_screen.xml │ │ ├── ic_settings.xml │ │ ├── ic_sort.xml │ │ ├── ic_start.xml │ │ ├── ic_stat.xml │ │ ├── ic_stop.xml │ │ ├── ic_time_panel_elapsed.xml │ │ ├── ic_time_panel_remaining.xml │ │ ├── ic_time_panel_step_end_time.xml │ │ ├── ic_time_panel_timer_end_time.xml │ │ ├── ic_timer.xml │ │ ├── ic_timer_duration.xml │ │ ├── ic_unlocked.xml │ │ ├── ic_vibration.xml │ │ ├── ic_voice.xml │ │ ├── ic_warning.xml │ │ ├── ic_watch.xml │ │ ├── settings_about.xml │ │ ├── settings_audio_focus.xml │ │ ├── settings_audio_type.xml │ │ ├── settings_brightness.xml │ │ ├── settings_cloud_backup.xml │ │ ├── settings_code.xml │ │ ├── settings_count.xml │ │ ├── settings_customize.xml │ │ ├── settings_day_night.xml │ │ ├── settings_email.xml │ │ ├── settings_export.xml │ │ ├── settings_file.xml │ │ ├── settings_google_play.xml │ │ ├── settings_help.xml │ │ ├── settings_import.xml │ │ ├── settings_material_you.xml │ │ ├── settings_media_style_notification.xml │ │ ├── settings_notifications.xml │ │ ├── settings_phone_call.xml │ │ ├── settings_pip.xml │ │ ├── settings_plus_time.xml │ │ ├── settings_premium.xml │ │ ├── settings_rate.xml │ │ ├── settings_share.xml │ │ ├── settings_system_settings.xml │ │ ├── settings_theme.xml │ │ ├── settings_time_duration.xml │ │ ├── settings_tips_and_tricks.xml │ │ ├── settings_tts_bakery.xml │ │ ├── settings_tutorial.xml │ │ ├── settings_tweak_time.xml │ │ ├── settings_voice.xml │ │ ├── settings_volume_on.xml │ │ ├── settings_week_start.xml │ │ └── shortcut_timer.xml │ │ ├── font │ │ └── digital_numbers_colon.ttf │ │ ├── layout │ │ ├── dialog_time_picker_dialog_fix.xml │ │ ├── view_list_empty_view.xml │ │ ├── view_time_picker_fix_edit.xml │ │ ├── view_time_picker_fix_normal_clock.xml │ │ └── view_time_picker_fix_normal_spinner.xml │ │ ├── mipmap-anydpi-v26 │ │ └── app_icon_adaptive.xml │ │ ├── mipmap-hdpi │ │ ├── app_icon_round.png │ │ └── app_icon_square.png │ │ ├── mipmap-mdpi │ │ ├── app_icon_round.png │ │ └── app_icon_square.png │ │ ├── mipmap-xhdpi │ │ ├── app_icon_round.png │ │ └── app_icon_square.png │ │ ├── mipmap-xxhdpi │ │ ├── app_icon_round.png │ │ └── app_icon_square.png │ │ ├── mipmap-xxxhdpi │ │ ├── app_icon_round.png │ │ └── app_icon_square.png │ │ ├── raw │ │ └── default_ringtone.mp3 │ │ ├── values-anydpi-v26 │ │ └── drawables.xml │ │ ├── values-de │ │ └── strings.xml │ │ ├── values-es │ │ └── strings.xml │ │ ├── values-fr │ │ └── strings.xml │ │ ├── values-it │ │ └── strings.xml │ │ ├── values-nb-rNO │ │ └── strings.xml │ │ ├── values-night │ │ └── styles.xml │ │ ├── values-nl │ │ └── strings.xml │ │ ├── values-pt │ │ └── strings.xml │ │ ├── values-ru │ │ └── strings.xml │ │ ├── values-ta │ │ └── strings.xml │ │ ├── values-zh-rCN │ │ └── strings.xml │ │ ├── values-zh-rHK │ │ └── strings.xml │ │ ├── values-zh-rTW │ │ └── strings.xml │ │ └── values │ │ ├── attrs.xml │ │ ├── colors.xml │ │ ├── dimens.xml │ │ ├── drawables.xml │ │ ├── ids.xml │ │ ├── strings.xml │ │ ├── styles.xml │ │ └── themes.xml │ └── test │ └── java │ └── xyz │ └── aprildown │ └── timer │ └── app │ └── base │ └── utils │ └── WeekdaysFormatterTest.kt ├── app-intro ├── build.gradle └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── xyz │ │ └── aprildown │ │ └── timer │ │ └── app │ │ └── intro │ │ ├── Instruction.kt │ │ ├── InstructionIndicators.kt │ │ ├── IntroActivity.kt │ │ ├── IntroPanelView.kt │ │ └── start │ │ ├── InstructionViews.kt │ │ ├── Insturctions.kt │ │ ├── StartEditInstructionView.kt │ │ ├── StartListInstructionView.kt │ │ └── StartRunInstructionView.kt │ └── res │ ├── drawable-land │ └── gradient_intro_panel_view_top.xml │ ├── drawable │ └── gradient_intro_panel_view_top.xml │ ├── layout-land │ └── activity_intro.xml │ ├── layout │ ├── activity_intro.xml │ ├── layout_intro_start_edit.xml │ ├── layout_intro_start_list.xml │ ├── layout_intro_start_run.xml │ └── view_intro_panel.xml │ ├── menu │ └── intro_edit.xml │ ├── values │ ├── dimens.xml │ └── ids.xml │ ├── xml-land │ └── activity_intro_scene.xml │ └── xml │ └── activity_intro_scene.xml ├── app-scheduler ├── build.gradle └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── xyz │ │ └── aprildown │ │ └── timer │ │ └── app │ │ └── scheduler │ │ ├── EditSchedulerFragment.kt │ │ ├── SchedulerAdapter.kt │ │ ├── SchedulerFragment.kt │ │ └── VisibleScheduler.kt │ └── res │ ├── layout │ ├── fragment_edit_scheduler.xml │ ├── fragment_scheduler.xml │ └── item_scheduler.xml │ └── menu │ └── edit_scheduler.xml ├── app-settings ├── build.gradle └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── xyz │ │ └── aprildown │ │ └── timer │ │ └── app │ │ └── settings │ │ ├── BakedCountPreference.kt │ │ ├── LogFragment.kt │ │ ├── PhoneCallPreference.kt │ │ ├── SettingsFragment.kt │ │ └── theme │ │ ├── CustomThemeDialog.kt │ │ └── ThemeFragment.kt │ └── res │ ├── drawable │ ├── theme_bg_round_corner_line.xml │ └── theme_white_ripple.xml │ ├── layout │ ├── dialog_custom_theme.xml │ ├── fragment_log.xml │ ├── item_theme_color.xml │ ├── item_theme_group.xml │ ├── layout_baked_count_widget.xml │ └── widget_color_swatch.xml │ ├── values-night │ └── colors.xml │ ├── values │ └── colors.xml │ └── xml │ └── pref_settings.xml ├── app-tasker ├── build.gradle └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── xyz │ │ └── aprildown │ │ └── timer │ │ └── app │ │ └── tasker │ │ ├── BroadcastReceiverActionTweak.kt │ │ ├── Constants.kt │ │ ├── TaskerEditActivity.kt │ │ ├── TaskerEditViewModel.kt │ │ ├── TaskerModule.kt │ │ ├── TaskerRunPresenter.kt │ │ └── TaskerTimerEventActivity.kt │ └── res │ ├── layout │ └── activity_tasker_edit.xml │ └── menu │ └── tasker_edit.xml ├── app-timer-edit ├── build.gradle └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── xyz │ │ └── aprildown │ │ └── timer │ │ └── app │ │ └── timer │ │ └── edit │ │ ├── BehaviourSettingsView.kt │ │ ├── EditActivity.kt │ │ ├── EditActivityMoreDialog.kt │ │ ├── EditableFooter.kt │ │ ├── EditableGroup.kt │ │ ├── EditableGroupEnd.kt │ │ ├── EditableStep.kt │ │ ├── RingtonePickerActivity.kt │ │ ├── SampleTimers.kt │ │ ├── StepInfoView.kt │ │ ├── StepTouchHelper.kt │ │ ├── UpdateStepDialog.kt │ │ ├── media │ │ ├── BeepDialog.kt │ │ ├── HalfDialog.kt │ │ ├── VibrationDialog.kt │ │ └── VoiceDialog.kt │ │ ├── utils │ │ └── SaveInstanceHelper.kt │ │ └── voice │ │ ├── ArrayAdapter.java │ │ ├── VoiceVariableContentView.kt │ │ ├── VoiceVariableDialog.kt │ │ └── VoiceVariableTableView.kt │ └── res │ ├── color │ └── voice_variable_chip_stroke.xml │ ├── drawable │ ├── background_step_info.xml │ └── voice_variable_table_divider.xml │ ├── layout-land │ └── layout_voice_variable_content.xml │ ├── layout-sw600dp │ └── dialog_voice_variable.xml │ ├── layout │ ├── activity_edit_timer.xml │ ├── activity_ringtone_picker.xml │ ├── dialog_edit_more.xml │ ├── dialog_half_option.xml │ ├── dialog_update_step.xml │ ├── dialog_vibration_count.xml │ ├── dialog_vibration_pattern.xml │ ├── dialog_voice_content.xml │ ├── dialog_voice_variable.xml │ ├── item_edit_group.xml │ ├── item_edit_group_end.xml │ ├── item_edit_step.xml │ ├── layout_edit_add_buttons.xml │ ├── layout_voice_variable.xml │ ├── layout_voice_variable_content.xml │ ├── layout_voice_variable_dialog_buttons.xml │ ├── layout_voice_variable_table.xml │ ├── layout_voice_variable_usage.xml │ ├── list_item_voice_variable_auto_completion.xml │ └── view_step_info.xml │ ├── menu │ └── edit.xml │ ├── values-night │ └── colors.xml │ └── values │ ├── colors.xml │ ├── dimens.xml │ └── styles.xml ├── app-timer-list ├── build.gradle └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── xyz │ │ └── aprildown │ │ └── timer │ │ └── app │ │ └── timer │ │ └── list │ │ ├── CollapsedViewHolder.kt │ │ ├── ExpandedViewHolder.kt │ │ ├── FolderToolbar.kt │ │ ├── MutableTimerItem.kt │ │ ├── TimerAdapter.kt │ │ ├── TimerFragment.kt │ │ ├── TimerPicker.kt │ │ └── record │ │ ├── CalendarEventView.kt │ │ ├── RecordCalendarFragment.kt │ │ ├── RecordFragment.kt │ │ ├── RecordOverviewFragment.kt │ │ ├── RecordTimelineFragment.kt │ │ ├── RecordTimelineMarker.kt │ │ ├── RecordTimersButton.kt │ │ └── RecordViewUtils.kt │ └── res │ ├── drawable │ └── background_record_calendar_circle.xml │ ├── layout-land │ └── fragment_record.xml │ ├── layout │ ├── fragment_record.xml │ ├── fragment_record_calendar.xml │ ├── fragment_record_overview.xml │ ├── fragment_record_timeline.xml │ ├── fragment_timer.xml │ ├── fragment_timer_picker.xml │ ├── list_item_calendar_day_event.xml │ ├── list_item_record_calendar.xml │ ├── list_item_record_calendar_legend.xml │ ├── list_item_timer_collapsed.xml │ ├── list_item_timer_collapsed_grid.xml │ ├── list_item_timer_expanded.xml │ ├── list_item_timer_expanded_gird.xml │ ├── list_item_timer_picker_folder.xml │ ├── list_item_timer_picker_timer.xml │ ├── view_calendar_event.xml │ ├── view_folder_toolbar.xml │ ├── view_record_timeline_marker.xml │ ├── view_tip_missed_timer.xml │ └── view_tip_whitelist.xml │ ├── menu │ ├── record_predefined_time.xml │ └── timer.xml │ ├── values-w480dp │ └── integer.xml │ ├── values-w600dp │ └── integer.xml │ ├── values-w720dp │ └── integer.xml │ ├── values-w840dp │ └── integer.xml │ └── values │ ├── dimens.xml │ └── integer.xml ├── app-timer-one ├── build.gradle └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── xyz │ │ └── aprildown │ │ └── timer │ │ └── app │ │ └── timer │ │ └── one │ │ ├── BaseOneFragment.kt │ │ ├── FiveActionsView.kt │ │ ├── OneActivity.kt │ │ ├── OneFragment.kt │ │ ├── PipHelper.kt │ │ ├── float │ │ ├── FloatViewTouchListener.kt │ │ ├── Floater.kt │ │ ├── FloatingTimer.kt │ │ └── FloatingWindowPipFragment.kt │ │ ├── layout │ │ ├── OneLayoutFragment.kt │ │ ├── TimePanelDialog.kt │ │ ├── TweakTimeLayout.kt │ │ └── one │ │ │ └── OneLayoutOneFragment.kt │ │ └── step │ │ ├── OffsetEndsItemDecoration.kt │ │ ├── StepListListeners.kt │ │ ├── StepListView.kt │ │ ├── VisibleGroup.kt │ │ ├── VisibleGroupEnd.kt │ │ └── VisibleStep.kt │ └── res │ ├── layout-land │ └── fragment_one.xml │ ├── layout │ ├── activity_one.xml │ ├── dialog_one_create_shortcut.xml │ ├── dialog_time_panel_picker.xml │ ├── fragment_floating_window_pip.xml │ ├── fragment_one.xml │ ├── fragment_one_layout.xml │ ├── item_step_group.xml │ ├── item_step_group_end.xml │ ├── item_step_step.xml │ ├── layout_five_actions.xml │ ├── layout_floating_window.xml │ ├── layout_one_settings_one.xml │ ├── layout_one_widget_time_panel.xml │ ├── layout_one_widget_timing_bar.xml │ ├── layout_pip.xml │ ├── layout_tweak_time_main_button.xml │ └── layout_tweak_time_tweak_button.xml │ ├── menu │ └── one_tweak_time.xml │ ├── navigation │ └── nav_graph_one.xml │ ├── values-night │ └── colors.xml │ └── values │ ├── colors.xml │ ├── dimens.xml │ └── styles.xml ├── app-timer-run ├── build.gradle └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── xyz │ │ └── aprildown │ │ └── timer │ │ └── app │ │ └── timer │ │ └── run │ │ ├── MachineNotif.kt │ │ ├── MachineService.kt │ │ ├── MachineServiceModule.kt │ │ ├── NotificationBuilders.kt │ │ ├── PhantomActivity.kt │ │ ├── PhoneCallReceiver.kt │ │ ├── ServiceWakeLock.kt │ │ ├── receiver │ │ └── SchedulerReceiver.kt │ │ └── screen │ │ └── ScreenActivity.kt │ └── res │ ├── layout-land │ └── activity_screen.xml │ ├── layout │ ├── activity_screen.xml │ └── layout_notif.xml │ ├── values-night │ └── colors.xml │ └── values │ ├── colors.xml │ └── themes.xml ├── app ├── build.gradle ├── dependencies │ ├── dogReleaseRuntimeClasspath.txt │ ├── googleReleaseRuntimeClasspath.txt │ └── otherReleaseRuntimeClasspath.txt ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── io │ │ └── github │ │ └── deweyreed │ │ └── timer │ │ ├── TestApp.kt │ │ ├── TestAppComponent.kt │ │ ├── TestRunner.kt │ │ └── ui │ │ ├── EditActivityTest.kt │ │ └── TaskerEditActivityTest.kt │ ├── debug │ └── java │ │ └── io │ │ └── github │ │ └── deweyreed │ │ └── timer │ │ └── ui │ │ └── DebugSetUp.kt │ ├── dog │ └── java │ │ └── io │ │ └── github │ │ └── deweyreed │ │ └── timer │ │ ├── FlavorDataImpl.kt │ │ └── FlavorModule.kt │ ├── dogRelease │ └── generated │ │ └── baselineProfiles │ │ ├── baseline-prof.txt │ │ └── startup-prof.txt │ ├── google │ └── java │ │ └── io │ │ └── github │ │ └── deweyreed │ │ └── timer │ │ ├── FlavorDataImpl.kt │ │ └── FlavorModule.kt │ ├── googleRelease │ └── generated │ │ └── baselineProfiles │ │ ├── baseline-prof.txt │ │ └── startup-prof.txt │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── io │ │ │ └── github │ │ │ └── deweyreed │ │ │ └── timer │ │ │ ├── App.kt │ │ │ ├── di │ │ │ └── OtherModule.kt │ │ │ ├── ui │ │ │ ├── AppNavigatorImpl.kt │ │ │ ├── DrawerDividerItem.kt │ │ │ ├── DrawerItemTint.kt │ │ │ ├── MainActivity.kt │ │ │ └── single │ │ │ │ ├── AboutFragment.kt │ │ │ │ └── HelpFragment.kt │ │ │ └── utils │ │ │ └── DynamicThemeDelegate.kt │ └── res │ │ ├── layout │ │ ├── activity_main.xml │ │ ├── content_main.xml │ │ ├── dialog_tts_test.xml │ │ └── fragment_about.xml │ │ ├── navigation │ │ └── nav_graph.xml │ │ ├── values-night │ │ └── colors.xml │ │ ├── values │ │ ├── attrs.xml │ │ ├── colors.xml │ │ └── themes.xml │ │ └── xml │ │ ├── auto_backup_rules.xml │ │ ├── data_extraction_rules.xml │ │ ├── locales_config.xml │ │ ├── pref_about.xml │ │ └── pref_help.xml │ ├── other │ └── java │ │ └── io │ │ └── github │ │ └── deweyreed │ │ └── timer │ │ ├── FlavorDataImpl.kt │ │ └── FlavorModule.kt │ ├── otherRelease │ └── generated │ │ └── baselineProfiles │ │ ├── baseline-prof.txt │ │ └── startup-prof.txt │ └── release │ └── java │ └── io │ └── github │ └── deweyreed │ └── timer │ └── ui │ └── DebugSetUp.kt ├── baselineProfile ├── build.gradle └── src │ └── main │ ├── AndroidManifest.xml │ └── java │ └── io │ └── github │ └── deweyreed │ └── timer │ └── baselineprofile │ ├── BaselineProfileGenerator.kt │ └── StartupBenchmarks.kt ├── build.gradle ├── component-key ├── build.gradle └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ ├── androidx │ │ └── appcompat │ │ │ └── widget │ │ │ └── TooltipCompatFix.kt │ └── xyz │ │ └── aprildown │ │ └── timer │ │ └── component │ │ └── key │ │ ├── DurationPicker.kt │ │ ├── ImageActionMapper.kt │ │ ├── ImagePreviewActivity.kt │ │ ├── ListItem.kt │ │ ├── ListItemWithLayout.kt │ │ ├── MaterialPopupMenuHelper.kt │ │ ├── NameLoopView.kt │ │ ├── PreferenceStyleListFragment.kt │ │ ├── RoundTextView.kt │ │ ├── SimpleInputDialog.kt │ │ ├── TimePanelLayout.kt │ │ └── behaviour │ │ ├── BehaviourLayout.kt │ │ ├── BehaviourLayoutUtils.kt │ │ ├── BehaviourTypeUtils.kt │ │ └── EditableBehaviourLayout.kt │ └── res │ ├── drawable │ └── divider_normal_flexbox.xml │ ├── layout │ ├── dialog_simple_input.xml │ ├── item_time_panel.xml │ ├── layout_behaviour.xml │ ├── layout_editable_behaviour.xml │ ├── layout_editable_behaviour_image.xml │ ├── layout_list_item.xml │ ├── layout_list_item_with_layout.xml │ ├── layout_name_loop.xml │ ├── layout_time_picker_panel.xml │ ├── layout_time_picker_scroll.xml │ ├── mpm_popup_menu_switch.xml │ ├── view_behavior_chip.xml │ ├── view_list_item.xml │ ├── view_list_item_with_layout.xml │ ├── view_list_item_with_layout_check.xml │ ├── view_list_item_with_layout_radio.xml │ ├── view_list_item_with_layout_switch.xml │ └── view_list_item_with_layout_text.xml │ └── values │ ├── attrs.xml │ ├── dimens.xml │ ├── styles.xml │ └── themes.xml ├── component-main ├── build.gradle └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── xyz │ │ └── aprildown │ │ └── timer │ │ └── workshop │ │ ├── Monika.kt │ │ └── WhitelistFragment.kt │ └── res │ └── xml │ └── pref_whitelist.xml ├── component-settings ├── build.gradle └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── xyz │ │ └── aprildown │ │ └── timer │ │ └── component │ │ └── settings │ │ ├── DarkThemeDialog.kt │ │ └── TweakTimeDialog.kt │ └── res │ └── layout │ ├── dialog_dark_theme.xml │ ├── dialog_tweak_time.xml │ └── layout_tweak_time_item.xml ├── component-tts ├── build.gradle └── src │ └── main │ ├── AndroidManifest.xml │ └── java │ └── com │ └── github │ └── deweyreed │ └── timer │ └── component │ └── tts │ ├── TtsBakery.kt │ ├── TtsBakeryDiskCache.kt │ ├── TtsBakeryWorker.kt │ └── TtsSpeaker.kt ├── compose_compiler_config.conf ├── data ├── build.gradle ├── schemas │ └── xyz.aprildown.timer.data.db.MachineDatabase │ │ ├── 1.json │ │ ├── 2.json │ │ ├── 3.json │ │ ├── 4.json │ │ ├── 5.json │ │ ├── 6.json │ │ ├── 7.json │ │ └── 8.json └── src │ ├── androidTest │ └── java │ │ └── xyz │ │ └── aprildown │ │ └── timer │ │ └── data │ │ ├── db │ │ └── MachineDatabaseMigratingTest.kt │ │ └── repositories │ │ ├── AppDataRepositoryImplTest.kt │ │ ├── NotifierRepositoryImplTest.kt │ │ ├── SchedulerRepositoryImplTest.kt │ │ ├── TimerRepositoryImplTest.kt │ │ └── TimerStampRepositoryImplTest.kt │ └── main │ ├── AndroidManifest.xml │ └── java │ └── xyz │ └── aprildown │ └── timer │ └── data │ ├── datas │ ├── AppDataData.kt │ ├── BehaviourData.kt │ ├── FolderData.kt │ ├── SchedulerData.kt │ ├── StepData.kt │ ├── TimerData.kt │ ├── TimerMoreData.kt │ └── TimerStampData.kt │ ├── db │ ├── Converters.kt │ ├── Daos.kt │ └── MachineDatabase.kt │ ├── di │ ├── DataModule.kt │ └── RepoModule.kt │ ├── job │ ├── JobDirector.kt │ ├── NewJobCreator.kt │ └── SchedulerJob.kt │ ├── json │ ├── BehaviourDataJsonAdapter.kt │ ├── PolymorphicJsonAdapterFactory.java │ └── TimerMoreDataJsonAdapter.kt │ ├── mappers │ ├── AppDataMapper.kt │ ├── BehaviourMapper.kt │ ├── FolderMapper.kt │ ├── MapperUtils.kt │ ├── SchedulerMapper.kt │ ├── StepMapper.kt │ ├── TimerMapper.kt │ ├── TimerMoreMapper.kt │ └── TimerStampMapper.kt │ └── repositories │ ├── AppDataRepositoryImpl.kt │ ├── FolderRepositoryImpl.kt │ ├── NotifierRepositoryImpl.kt │ ├── PreferencesRepoImpl.kt │ ├── SchedulerExecutorImpl.kt │ ├── SchedulerRepositoryImpl.kt │ ├── TimerRepositoryImpl.kt │ └── TimerStampRepositoryImpl.kt ├── detekt-config.yml ├── detekt.gradle ├── domain ├── build.gradle └── src │ ├── main │ ├── AndroidManifest.xml │ └── java │ │ └── xyz │ │ └── aprildown │ │ ├── timer │ │ └── domain │ │ │ ├── Mapper.kt │ │ │ ├── TestData.kt │ │ │ ├── TimeUtils.kt │ │ │ ├── di │ │ │ └── CoroutinesDispatcherModule.kt │ │ │ ├── entities │ │ │ ├── AppDataEntity.kt │ │ │ ├── BehaviourEntity.kt │ │ │ ├── FolderEntity.kt │ │ │ ├── SchedulerEntity.kt │ │ │ ├── StepEntity.kt │ │ │ ├── TimerEntity.kt │ │ │ └── TimerStampEntity.kt │ │ │ ├── repositories │ │ │ ├── AppDataRepository.kt │ │ │ ├── AppPreferencesProvider.kt │ │ │ ├── FolderRepository.kt │ │ │ ├── NotifierRepository.kt │ │ │ ├── PreferencesRepository.kt │ │ │ ├── SchedulerExecutor.kt │ │ │ ├── SchedulerRepository.kt │ │ │ ├── TaskerEventTrigger.kt │ │ │ ├── TimerRepository.kt │ │ │ └── TimerStampRepository.kt │ │ │ ├── usecases │ │ │ ├── CoroutinesUseCase.kt │ │ │ ├── Fruit.kt │ │ │ ├── data │ │ │ │ ├── ExportAppData.kt │ │ │ │ ├── ImportAppData.kt │ │ │ │ └── NotifyDataChanged.kt │ │ │ ├── folder │ │ │ │ ├── AddFolder.kt │ │ │ │ ├── DeleteFolder.kt │ │ │ │ ├── FolderSortByRule.kt │ │ │ │ ├── GetFolders.kt │ │ │ │ ├── RecentFolder.kt │ │ │ │ └── UpdateFolder.kt │ │ │ ├── home │ │ │ │ └── TipManager.kt │ │ │ ├── notifier │ │ │ │ ├── GetNotifier.kt │ │ │ │ └── SaveNotifier.kt │ │ │ ├── record │ │ │ │ ├── AddTimerStamp.kt │ │ │ │ └── GetRecords.kt │ │ │ ├── scheduler │ │ │ │ ├── AddScheduler.kt │ │ │ │ ├── DeleteScheduler.kt │ │ │ │ ├── GetScheduler.kt │ │ │ │ ├── GetSchedulers.kt │ │ │ │ ├── SaveScheduler.kt │ │ │ │ └── SetSchedulerEnable.kt │ │ │ └── timer │ │ │ │ ├── AddTimer.kt │ │ │ │ ├── ChangeTimerFolder.kt │ │ │ │ ├── DeleteTimer.kt │ │ │ │ ├── FindTimerInfo.kt │ │ │ │ ├── GetTimer.kt │ │ │ │ ├── GetTimerInfo.kt │ │ │ │ ├── GetTimerInfoFlow.kt │ │ │ │ ├── SaveTimer.kt │ │ │ │ └── ShareTimer.kt │ │ │ └── utils │ │ │ ├── AppConfig.kt │ │ │ ├── AppTracker.kt │ │ │ ├── ApplicationScope.kt │ │ │ ├── Constants.kt │ │ │ └── FileUtils.kt │ │ └── tools │ │ └── helper │ │ └── SharedPreferencesHelper.kt │ └── test │ └── java │ └── xyz │ └── aprildown │ └── timer │ └── domain │ ├── TimeUtilsTest.kt │ ├── repositories │ └── TestPreferencesRepository.kt │ └── usecases │ ├── data │ └── DataUseCaseTest.kt │ ├── folder │ └── FolderUseCaseTest.kt │ ├── notifier │ └── NotifierUseCaseTest.kt │ ├── record │ └── RecordUseCasesTest.kt │ ├── scheduler │ ├── SchedulerRepeatContentTest.kt │ └── SchedulerUseCasesTest.kt │ └── timer │ └── TimerUseCasesTest.kt ├── fastlane └── metadata │ └── android │ └── en-US │ ├── full_description.txt │ ├── images │ ├── featureGraphic.jpg │ ├── icon.png │ ├── phoneScreenshots │ │ ├── 1.png │ │ ├── 2.png │ │ ├── 3.png │ │ └── 4.png │ ├── sevenInchScreenshots │ │ └── 1.png │ └── tenInchScreenshots │ │ └── 1.png │ ├── short_description.txt │ └── title.txt ├── flavor-google ├── build.gradle └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── xyz │ │ └── aprildown │ │ └── timer │ │ ├── app │ │ └── analytics │ │ │ ├── AnalyticsModule.kt │ │ │ └── AppTrackerImpl.kt │ │ └── flavor │ │ └── google │ │ ├── BillingActivity.kt │ │ ├── BillingFragment.kt │ │ ├── BillingSupervisor.kt │ │ ├── FlavorUiInjectorImpl.kt │ │ ├── NavigationUtils.kt │ │ ├── backup │ │ ├── BackupRepoImpl.kt │ │ ├── CloudBackupFragment.kt │ │ ├── CloudBackupViewModel.kt │ │ ├── CloudBackupWorker.kt │ │ ├── RestoreFragment.kt │ │ └── usecases │ │ │ ├── AutoCloudBackup.kt │ │ │ ├── CloudBackup.kt │ │ │ ├── CloudBackupState.kt │ │ │ ├── GetRestoreReferences.kt │ │ │ └── RestoreFromCloud.kt │ │ ├── count │ │ ├── BakedCountDialog.kt │ │ ├── BakedCountViewModel.kt │ │ ├── ClearBakedCountResources.kt │ │ └── LoadBakedCountResources.kt │ │ └── utils │ │ ├── FileUtils.kt │ │ ├── FirebaseExceptionUtils.kt │ │ ├── FirebaseStorageSetUp.kt │ │ └── IapPromotionDialog.kt │ └── res │ ├── drawable │ ├── backup_auto_backup.xml │ ├── backup_cloud_restore.xml │ ├── backup_cloud_state.xml │ ├── backup_delete.xml │ ├── backup_sign_in.xml │ └── backup_sign_out.xml │ ├── layout │ ├── activity_billing.xml │ ├── dialog_baked_count.xml │ ├── dialog_loading.xml │ ├── dialog_title_subscription.xml │ ├── fragment_billing.xml │ ├── fragment_restore.xml │ └── list_item_restore.xml │ ├── menu │ └── flavor_help.xml │ ├── navigation │ └── backup_graph.xml │ ├── values │ └── strings.xml │ └── xml │ └── pref_cloud_back.xml ├── functions └── index.js ├── gradle.properties ├── gradle ├── libs.versions.toml └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── images ├── google-play-badge.png └── showcase.jpg ├── presentation ├── build.gradle └── src │ ├── androidTest │ └── java │ │ └── xyz │ │ └── aprildown │ │ └── timer │ │ └── presentation │ │ └── stream │ │ └── MachinePresenterTest.kt │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── xyz │ │ │ └── aprildown │ │ │ └── timer │ │ │ └── presentation │ │ │ ├── BaseViewModel.kt │ │ │ ├── StreamMachineIntentProvider.kt │ │ │ ├── di │ │ │ └── ViewModelModule.kt │ │ │ ├── edit │ │ │ └── EditViewModel.kt │ │ │ ├── intro │ │ │ └── IntroViewModel.kt │ │ │ ├── one │ │ │ └── OneViewModel.kt │ │ │ ├── scheduler │ │ │ ├── EditSchedulerViewModel.kt │ │ │ ├── SchedulerReceiverPresenter.kt │ │ │ └── SchedulerViewModel.kt │ │ │ ├── screen │ │ │ └── ScreenViewModel.kt │ │ │ ├── stream │ │ │ ├── MachineContract.kt │ │ │ ├── MachinePresenter.kt │ │ │ ├── StreamState.kt │ │ │ ├── TimerIndex.kt │ │ │ ├── TimerMachine.kt │ │ │ ├── TimerMachineHelper.kt │ │ │ ├── TimerMachineListener.kt │ │ │ └── task │ │ │ │ ├── CountDownTimerTask.kt │ │ │ │ ├── StopwatchTask.kt │ │ │ │ ├── Task.kt │ │ │ │ ├── TaskManager.kt │ │ │ │ └── TickListener.kt │ │ │ └── timer │ │ │ ├── RecordViewModel.kt │ │ │ ├── TimerPickerViewModel.kt │ │ │ └── TimerViewModel.kt │ └── res │ │ └── values │ │ └── ids.xml │ └── test │ └── java │ └── xyz │ └── aprildown │ └── timer │ └── presentation │ ├── edit │ └── EditViewModelTest.kt │ ├── one │ └── OneViewModelTest.kt │ ├── scheduler │ ├── EditSchedulerViewModelTest.kt │ ├── SchedulerReceiverPresenterTest.kt │ └── SchedulerViewModelTest.kt │ ├── screen │ └── ScreenViewModelTest.kt │ ├── stream │ ├── MachinePresenterUnitTest.kt │ └── TimerMachineHelperKtTest.kt │ └── timer │ ├── RecordViewModelTest.kt │ └── TimerViewModelTest.kt └── settings.gradle /.github/workflows/android.yml: -------------------------------------------------------------------------------- 1 | name: Android CI 2 | 3 | on: 4 | push: 5 | branches: [ "main", "develop" ] 6 | pull_request: 7 | branches: [ "main", "develop" ] 8 | 9 | jobs: 10 | build: 11 | 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - uses: actions/checkout@v3 16 | - name: Set up JDK 17 17 | uses: actions/setup-java@v3 18 | with: 19 | java-version: '17' 20 | distribution: 'temurin' 21 | cache: gradle 22 | 23 | - name: Grant execute permission for gradlew 24 | run: chmod +x gradlew 25 | - name: Guard dependencies 26 | run: ./gradlew dependencyGuard 27 | - name: Build with Gradle 28 | run: ./gradlew app:assembleDogDebug 29 | -------------------------------------------------------------------------------- /app-analytics-fake/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | alias(libs.plugins.android.library) 3 | alias(libs.plugins.kotlin.android) 4 | alias(libs.plugins.kotlin.kapt) 5 | alias(libs.plugins.hilt) 6 | } 7 | 8 | android { 9 | namespace 'xyz.aprildown.timer.app.analytics' 10 | } 11 | 12 | dependencies { 13 | implementation project(':app-base') 14 | 15 | implementation libs.hilt.android 16 | kapt libs.hilt.compiler 17 | } 18 | -------------------------------------------------------------------------------- /app-analytics-fake/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /app-analytics-fake/src/main/java/xyz/aprildown/timer/app/analytics/AnalyticsModule.kt: -------------------------------------------------------------------------------- 1 | package xyz.aprildown.timer.app.analytics 2 | 3 | import dagger.Binds 4 | import dagger.Module 5 | import dagger.hilt.InstallIn 6 | import dagger.hilt.components.SingletonComponent 7 | import xyz.aprildown.timer.domain.utils.AppTracker 8 | 9 | @Module 10 | @InstallIn(SingletonComponent::class) 11 | internal abstract class AnalyticsModule { 12 | @Binds 13 | abstract fun bindAppTracker(impl: AppTrackerImpl): AppTracker 14 | } 15 | -------------------------------------------------------------------------------- /app-analytics-fake/src/main/java/xyz/aprildown/timer/app/analytics/AppTrackerImpl.kt: -------------------------------------------------------------------------------- 1 | package xyz.aprildown.timer.app.analytics 2 | 3 | import android.content.Context 4 | import dagger.Reusable 5 | import timber.log.Timber 6 | import xyz.aprildown.timer.domain.utils.AppTracker 7 | import javax.inject.Inject 8 | 9 | @Reusable 10 | internal class AppTrackerImpl @Inject constructor() : AppTracker { 11 | override fun init(context: Context) = Unit 12 | 13 | override fun trackError(throwable: Throwable, message: String?) { 14 | if (message != null) { 15 | Timber.e(throwable, message) 16 | } else { 17 | Timber.e(throwable) 18 | } 19 | } 20 | 21 | override suspend fun hasCrashedInLastSession(): Boolean { 22 | return false 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /app-backup/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | alias(libs.plugins.android.library) 3 | alias(libs.plugins.kotlin.android) 4 | alias(libs.plugins.kotlin.kapt) 5 | alias(libs.plugins.hilt) 6 | } 7 | 8 | android { 9 | namespace 'xyz.aprildown.timer.app.backup' 10 | buildFeatures { 11 | compose true 12 | } 13 | composeOptions { 14 | kotlinCompilerExtensionVersion = libs.versions.composeCompiler.get() 15 | } 16 | } 17 | 18 | dependencies { 19 | implementation project(':app-base') 20 | implementation project(':component-key') 21 | 22 | testImplementation libs.junit 23 | testImplementation libs.kotlin.coroutines.test 24 | testImplementation libs.bundles.mockito.core 25 | 26 | implementation libs.androidx.preference 27 | 28 | implementation platform(libs.compose.bom) 29 | implementation libs.bundles.compose 30 | 31 | implementation libs.hilt.android 32 | kapt libs.hilt.compiler 33 | 34 | implementation libs.okio 35 | implementation libs.permission 36 | 37 | } 38 | -------------------------------------------------------------------------------- /app-backup/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app-backup/src/main/java/xyz/aprildown/timer/app/backup/CloudBackupPreference.kt: -------------------------------------------------------------------------------- 1 | package xyz.aprildown.timer.app.backup 2 | 3 | import android.content.Context 4 | import android.util.AttributeSet 5 | import android.widget.ImageView 6 | import androidx.core.view.isVisible 7 | import androidx.preference.Preference 8 | import androidx.preference.PreferenceViewHolder 9 | import xyz.aprildown.timer.domain.utils.Constants 10 | import xyz.aprildown.tools.helper.safeSharedPreference 11 | 12 | internal class CloudBackupPreference( 13 | context: Context, 14 | attrs: AttributeSet? = null 15 | ) : Preference(context, attrs) { 16 | override fun onBindViewHolder(holder: PreferenceViewHolder) { 17 | super.onBindViewHolder(holder) 18 | val imageView = holder.findViewById(R.id.image) as? ImageView? ?: return 19 | imageView.isVisible = 20 | !context.safeSharedPreference.getBoolean(Constants.PREF_HAS_BACKUP_SUB, false) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /app-backup/src/main/res/layout/layout_cloud_backup_widget.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | -------------------------------------------------------------------------------- /app-base/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /app-base/src/main/java/xyz/aprildown/timer/app/base/data/FlavorData.kt: -------------------------------------------------------------------------------- 1 | package xyz.aprildown.timer.app.base.data 2 | 3 | interface FlavorData { 4 | 5 | enum class Flavor { 6 | Dog, Google, Other, 7 | } 8 | 9 | val flavor: Flavor 10 | 11 | val email: String get() = "ligrsidfd@gmail.com" 12 | val appDownloadLink: String 13 | } 14 | -------------------------------------------------------------------------------- /app-base/src/main/java/xyz/aprildown/timer/app/base/ui/MainCallback.kt: -------------------------------------------------------------------------------- 1 | package xyz.aprildown.timer.app.base.ui 2 | 3 | import android.os.Bundle 4 | import android.view.View 5 | import androidx.annotation.IdRes 6 | import com.google.android.material.floatingactionbutton.FloatingActionButton 7 | 8 | interface MainCallback { 9 | interface ActivityCallback { 10 | val actionFab: FloatingActionButton 11 | val snackbarView: View 12 | fun enterTimerScreen(itemView: View, id: Int) 13 | fun enterEditScreen(timerId: Int, folderId: Long) 14 | fun restartWithDestination( 15 | @IdRes destinationId: Int, 16 | destinationArguments: Bundle = Bundle.EMPTY 17 | ) 18 | 19 | fun recreateThemeItem() 20 | } 21 | 22 | interface FragmentCallback { 23 | fun onFabClick(view: View) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /app-base/src/main/java/xyz/aprildown/timer/app/base/ui/StepUpdater.kt: -------------------------------------------------------------------------------- 1 | package xyz.aprildown.timer.app.base.ui 2 | 3 | import androidx.fragment.app.DialogFragment 4 | import xyz.aprildown.timer.domain.entities.StepEntity 5 | 6 | interface StepUpdater { 7 | fun updateStep(step: StepEntity.Step, onUpdate: (StepEntity.Step) -> Unit): DialogFragment 8 | } 9 | -------------------------------------------------------------------------------- /app-base/src/main/java/xyz/aprildown/timer/app/base/utils/UiNameTranslators.kt: -------------------------------------------------------------------------------- 1 | package xyz.aprildown.timer.app.base.utils 2 | 3 | import android.content.Context 4 | import xyz.aprildown.timer.app.base.R 5 | import xyz.aprildown.timer.domain.entities.FolderEntity 6 | 7 | fun FolderEntity.getDisplayName(context: Context): String { 8 | return when { 9 | isDefault -> context.getString(R.string.folder_default) 10 | isTrash -> context.getString(R.string.folder_trash) 11 | else -> name 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /app-base/src/main/java/xyz/aprildown/timer/app/base/utils/ViewUtils.kt: -------------------------------------------------------------------------------- 1 | package xyz.aprildown.timer.app.base.utils 2 | 3 | import android.widget.TextView 4 | import com.github.deweyreed.tools.helper.setTextIfChanged 5 | 6 | fun TextView.setTime(value: Long?) { 7 | setTextIfChanged((value ?: 0).produceTime()) 8 | } 9 | -------------------------------------------------------------------------------- /app-base/src/main/res/anim/close_enter.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 14 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /app-base/src/main/res/anim/close_exit.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 14 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /app-base/src/main/res/anim/ic_anim_pause_play_interpolator_0.xml: -------------------------------------------------------------------------------- 1 | 15 | 16 | 18 | -------------------------------------------------------------------------------- /app-base/src/main/res/anim/ic_anim_pause_play_interpolator_1.xml: -------------------------------------------------------------------------------- 1 | 15 | 16 | 18 | -------------------------------------------------------------------------------- /app-base/src/main/res/anim/ic_anim_play_pause_interpolator_0.xml: -------------------------------------------------------------------------------- 1 | 15 | 16 | 18 | -------------------------------------------------------------------------------- /app-base/src/main/res/anim/ic_anim_play_pause_interpolator_1.xml: -------------------------------------------------------------------------------- 1 | 15 | 16 | 18 | -------------------------------------------------------------------------------- /app-base/src/main/res/anim/ic_anim_ringing.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | -------------------------------------------------------------------------------- /app-base/src/main/res/anim/open_enter.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 14 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /app-base/src/main/res/anim/open_exit.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 14 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /app-base/src/main/res/anim/slide_in_bottom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 13 | 14 | -------------------------------------------------------------------------------- /app-base/src/main/res/anim/slide_in_left.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | -------------------------------------------------------------------------------- /app-base/src/main/res/anim/slide_in_right.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | -------------------------------------------------------------------------------- /app-base/src/main/res/anim/slide_out_bottom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 13 | 14 | -------------------------------------------------------------------------------- /app-base/src/main/res/anim/slide_out_left.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | -------------------------------------------------------------------------------- /app-base/src/main/res/anim/slide_out_right.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | -------------------------------------------------------------------------------- /app-base/src/main/res/drawable/background_step_number.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app-base/src/main/res/drawable/ic_ad_off.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app-base/src/main/res/drawable/ic_add.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app-base/src/main/res/drawable/ic_arrow_down.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app-base/src/main/res/drawable/ic_arrow_left_indicator.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app-base/src/main/res/drawable/ic_arrow_up.xml: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /app-base/src/main/res/drawable/ic_back.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app-base/src/main/res/drawable/ic_backup.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app-base/src/main/res/drawable/ic_beep.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app-base/src/main/res/drawable/ic_calendar_more_events.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app-base/src/main/res/drawable/ic_check.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app-base/src/main/res/drawable/ic_count.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app-base/src/main/res/drawable/ic_cross.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app-base/src/main/res/drawable/ic_delete.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app-base/src/main/res/drawable/ic_dewey_reed.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app-base/src/main/res/drawable/ic_edit.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app-base/src/main/res/drawable/ic_exit.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 11 | 12 | -------------------------------------------------------------------------------- /app-base/src/main/res/drawable/ic_expand.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app-base/src/main/res/drawable/ic_flashlight.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app-base/src/main/res/drawable/ic_grid_view.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app-base/src/main/res/drawable/ic_half.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app-base/src/main/res/drawable/ic_halt.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app-base/src/main/res/drawable/ic_heart.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app-base/src/main/res/drawable/ic_image.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app-base/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 13 | 16 | 19 | 20 | -------------------------------------------------------------------------------- /app-base/src/main/res/drawable/ic_left.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app-base/src/main/res/drawable/ic_list_view.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app-base/src/main/res/drawable/ic_locked.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app-base/src/main/res/drawable/ic_menu.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app-base/src/main/res/drawable/ic_minus.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app-base/src/main/res/drawable/ic_music.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app-base/src/main/res/drawable/ic_notification.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app-base/src/main/res/drawable/ic_overflow.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app-base/src/main/res/drawable/ic_pause.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app-base/src/main/res/drawable/ic_plus_one.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app-base/src/main/res/drawable/ic_record_predefined_time.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app-base/src/main/res/drawable/ic_scheduler.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app-base/src/main/res/drawable/ic_screen.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app-base/src/main/res/drawable/ic_sort.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app-base/src/main/res/drawable/ic_start.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app-base/src/main/res/drawable/ic_stat.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app-base/src/main/res/drawable/ic_stop.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app-base/src/main/res/drawable/ic_time_panel_elapsed.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app-base/src/main/res/drawable/ic_time_panel_remaining.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app-base/src/main/res/drawable/ic_time_panel_step_end_time.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app-base/src/main/res/drawable/ic_time_panel_timer_end_time.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app-base/src/main/res/drawable/ic_timer.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app-base/src/main/res/drawable/ic_timer_duration.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app-base/src/main/res/drawable/ic_unlocked.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app-base/src/main/res/drawable/ic_vibration.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app-base/src/main/res/drawable/ic_voice.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app-base/src/main/res/drawable/ic_warning.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app-base/src/main/res/drawable/ic_watch.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app-base/src/main/res/drawable/settings_about.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app-base/src/main/res/drawable/settings_audio_type.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app-base/src/main/res/drawable/settings_brightness.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app-base/src/main/res/drawable/settings_cloud_backup.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app-base/src/main/res/drawable/settings_code.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app-base/src/main/res/drawable/settings_count.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 11 | 12 | -------------------------------------------------------------------------------- /app-base/src/main/res/drawable/settings_customize.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app-base/src/main/res/drawable/settings_day_night.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 11 | 12 | -------------------------------------------------------------------------------- /app-base/src/main/res/drawable/settings_email.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app-base/src/main/res/drawable/settings_export.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 11 | 12 | -------------------------------------------------------------------------------- /app-base/src/main/res/drawable/settings_file.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 11 | 12 | -------------------------------------------------------------------------------- /app-base/src/main/res/drawable/settings_google_play.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 11 | 12 | -------------------------------------------------------------------------------- /app-base/src/main/res/drawable/settings_help.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app-base/src/main/res/drawable/settings_import.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 11 | 12 | -------------------------------------------------------------------------------- /app-base/src/main/res/drawable/settings_material_you.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app-base/src/main/res/drawable/settings_media_style_notification.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app-base/src/main/res/drawable/settings_notifications.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app-base/src/main/res/drawable/settings_phone_call.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app-base/src/main/res/drawable/settings_pip.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app-base/src/main/res/drawable/settings_plus_time.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app-base/src/main/res/drawable/settings_premium.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app-base/src/main/res/drawable/settings_rate.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app-base/src/main/res/drawable/settings_share.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app-base/src/main/res/drawable/settings_theme.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app-base/src/main/res/drawable/settings_time_duration.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app-base/src/main/res/drawable/settings_tips_and_tricks.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app-base/src/main/res/drawable/settings_tutorial.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app-base/src/main/res/drawable/settings_tweak_time.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 11 | 12 | -------------------------------------------------------------------------------- /app-base/src/main/res/drawable/settings_voice.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 11 | 12 | -------------------------------------------------------------------------------- /app-base/src/main/res/drawable/settings_volume_on.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app-base/src/main/res/drawable/settings_week_start.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 11 | 12 | -------------------------------------------------------------------------------- /app-base/src/main/res/drawable/shortcut_timer.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 13 | 14 | -------------------------------------------------------------------------------- /app-base/src/main/res/font/digital_numbers_colon.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timer-machine/timer-machine-android/699caa3dc0065722a4e6f0a76b02009e253dc280/app-base/src/main/res/font/digital_numbers_colon.ttf -------------------------------------------------------------------------------- /app-base/src/main/res/layout/dialog_time_picker_dialog_fix.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | -------------------------------------------------------------------------------- /app-base/src/main/res/layout/view_time_picker_fix_normal_clock.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | -------------------------------------------------------------------------------- /app-base/src/main/res/layout/view_time_picker_fix_normal_spinner.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | -------------------------------------------------------------------------------- /app-base/src/main/res/mipmap-anydpi-v26/app_icon_adaptive.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /app-base/src/main/res/mipmap-hdpi/app_icon_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timer-machine/timer-machine-android/699caa3dc0065722a4e6f0a76b02009e253dc280/app-base/src/main/res/mipmap-hdpi/app_icon_round.png -------------------------------------------------------------------------------- /app-base/src/main/res/mipmap-hdpi/app_icon_square.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timer-machine/timer-machine-android/699caa3dc0065722a4e6f0a76b02009e253dc280/app-base/src/main/res/mipmap-hdpi/app_icon_square.png -------------------------------------------------------------------------------- /app-base/src/main/res/mipmap-mdpi/app_icon_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timer-machine/timer-machine-android/699caa3dc0065722a4e6f0a76b02009e253dc280/app-base/src/main/res/mipmap-mdpi/app_icon_round.png -------------------------------------------------------------------------------- /app-base/src/main/res/mipmap-mdpi/app_icon_square.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timer-machine/timer-machine-android/699caa3dc0065722a4e6f0a76b02009e253dc280/app-base/src/main/res/mipmap-mdpi/app_icon_square.png -------------------------------------------------------------------------------- /app-base/src/main/res/mipmap-xhdpi/app_icon_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timer-machine/timer-machine-android/699caa3dc0065722a4e6f0a76b02009e253dc280/app-base/src/main/res/mipmap-xhdpi/app_icon_round.png -------------------------------------------------------------------------------- /app-base/src/main/res/mipmap-xhdpi/app_icon_square.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timer-machine/timer-machine-android/699caa3dc0065722a4e6f0a76b02009e253dc280/app-base/src/main/res/mipmap-xhdpi/app_icon_square.png -------------------------------------------------------------------------------- /app-base/src/main/res/mipmap-xxhdpi/app_icon_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timer-machine/timer-machine-android/699caa3dc0065722a4e6f0a76b02009e253dc280/app-base/src/main/res/mipmap-xxhdpi/app_icon_round.png -------------------------------------------------------------------------------- /app-base/src/main/res/mipmap-xxhdpi/app_icon_square.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timer-machine/timer-machine-android/699caa3dc0065722a4e6f0a76b02009e253dc280/app-base/src/main/res/mipmap-xxhdpi/app_icon_square.png -------------------------------------------------------------------------------- /app-base/src/main/res/mipmap-xxxhdpi/app_icon_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timer-machine/timer-machine-android/699caa3dc0065722a4e6f0a76b02009e253dc280/app-base/src/main/res/mipmap-xxxhdpi/app_icon_round.png -------------------------------------------------------------------------------- /app-base/src/main/res/mipmap-xxxhdpi/app_icon_square.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timer-machine/timer-machine-android/699caa3dc0065722a4e6f0a76b02009e253dc280/app-base/src/main/res/mipmap-xxxhdpi/app_icon_square.png -------------------------------------------------------------------------------- /app-base/src/main/res/raw/default_ringtone.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timer-machine/timer-machine-android/699caa3dc0065722a4e6f0a76b02009e253dc280/app-base/src/main/res/raw/default_ringtone.mp3 -------------------------------------------------------------------------------- /app-base/src/main/res/values-anydpi-v26/drawables.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | @mipmap/app_icon_adaptive 4 | @mipmap/app_icon_adaptive 5 | -------------------------------------------------------------------------------- /app-base/src/main/res/values-night/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | 9 | 13 | 14 | 20 | 21 | -------------------------------------------------------------------------------- /app-timer-run/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | alias(libs.plugins.android.library) 3 | alias(libs.plugins.kotlin.android) 4 | alias(libs.plugins.kotlin.kapt) 5 | alias(libs.plugins.hilt) 6 | } 7 | 8 | android { 9 | namespace 'xyz.aprildown.timer.app.timer.run' 10 | } 11 | 12 | dependencies { 13 | implementation project(':data') 14 | implementation project(':app-base') 15 | implementation project(':component-tts') 16 | 17 | implementation libs.androidx.media 18 | 19 | implementation libs.hilt.android 20 | kapt libs.hilt.compiler 21 | 22 | } 23 | -------------------------------------------------------------------------------- /app-timer-run/src/main/java/xyz/aprildown/timer/app/timer/run/MachineServiceModule.kt: -------------------------------------------------------------------------------- 1 | package xyz.aprildown.timer.app.timer.run 2 | 3 | import dagger.Binds 4 | import dagger.Module 5 | import dagger.hilt.InstallIn 6 | import dagger.hilt.components.SingletonComponent 7 | import xyz.aprildown.timer.presentation.stream.MachineContract 8 | import xyz.aprildown.timer.presentation.stream.MachinePresenter 9 | 10 | @Module 11 | @InstallIn(SingletonComponent::class) 12 | internal abstract class MachineServiceModule { 13 | @Binds 14 | abstract fun provideMachineServicePresenter(presenter: MachinePresenter): MachineContract.Presenter 15 | } 16 | -------------------------------------------------------------------------------- /app-timer-run/src/main/res/values-night/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | @color/color_notification_background_dark 4 | 5 | -------------------------------------------------------------------------------- /app-timer-run/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #FFFFFF 4 | #000000 5 | @color/color_notification_background_light 6 | 7 | -------------------------------------------------------------------------------- /app-timer-run/src/main/res/values/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | -------------------------------------------------------------------------------- /app/src/androidTest/java/io/github/deweyreed/timer/TestApp.kt: -------------------------------------------------------------------------------- 1 | package io.github.deweyreed.timer 2 | 3 | import android.app.Application 4 | import dagger.hilt.android.testing.CustomTestApplication 5 | import xyz.aprildown.theme.Theme 6 | import xyz.aprildown.timer.app.base.R as RBase 7 | 8 | /** 9 | * We use a separate App for tests to prevent initializing dependency injection. 10 | */ 11 | open class TestApp : Application() { 12 | override fun onCreate() { 13 | super.onCreate() 14 | Theme.init(context = this, themeRes = RBase.style.AppTheme) 15 | } 16 | } 17 | 18 | @CustomTestApplication(TestApp::class) 19 | interface HiltTestApplication 20 | -------------------------------------------------------------------------------- /app/src/androidTest/java/io/github/deweyreed/timer/TestRunner.kt: -------------------------------------------------------------------------------- 1 | package io.github.deweyreed.timer 2 | 3 | import android.app.Application 4 | import android.content.Context 5 | import androidx.test.runner.AndroidJUnitRunner 6 | 7 | @Suppress("unused") 8 | class TestRunner : AndroidJUnitRunner() { 9 | override fun newApplication(cl: ClassLoader?, name: String?, context: Context?): Application { 10 | return super.newApplication(cl, HiltTestApplication_Application::class.java.name, context) 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /app/src/debug/java/io/github/deweyreed/timer/ui/DebugSetUp.kt: -------------------------------------------------------------------------------- 1 | package io.github.deweyreed.timer.ui 2 | 3 | import android.view.View 4 | import io.github.deweyreed.timer.R 5 | 6 | fun MainActivity.setUpDebug() { 7 | findViewById(R.id.toolbar).let { 8 | it.setOnClickListener { 9 | 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /app/src/dog/java/io/github/deweyreed/timer/FlavorDataImpl.kt: -------------------------------------------------------------------------------- 1 | package io.github.deweyreed.timer 2 | 3 | import dagger.Reusable 4 | import xyz.aprildown.timer.app.base.data.FlavorData 5 | import javax.inject.Inject 6 | 7 | @Reusable 8 | class FlavorDataImpl @Inject constructor() : FlavorData { 9 | override val flavor: FlavorData.Flavor = FlavorData.Flavor.Dog 10 | override val appDownloadLink: String = 11 | "https://github.com/timer-machine/timer-machine-android/releases" 12 | } 13 | -------------------------------------------------------------------------------- /app/src/dog/java/io/github/deweyreed/timer/FlavorModule.kt: -------------------------------------------------------------------------------- 1 | package io.github.deweyreed.timer 2 | 3 | import dagger.Binds 4 | import dagger.Module 5 | import dagger.hilt.InstallIn 6 | import dagger.hilt.components.SingletonComponent 7 | import xyz.aprildown.timer.app.base.data.FlavorData 8 | 9 | @Module 10 | @InstallIn(SingletonComponent::class) 11 | abstract class FlavorModule { 12 | @Binds 13 | abstract fun bindFlavorData(impl: FlavorDataImpl): FlavorData 14 | } 15 | -------------------------------------------------------------------------------- /app/src/google/java/io/github/deweyreed/timer/FlavorDataImpl.kt: -------------------------------------------------------------------------------- 1 | package io.github.deweyreed.timer 2 | 3 | import dagger.Reusable 4 | import xyz.aprildown.timer.app.base.data.FlavorData 5 | import javax.inject.Inject 6 | 7 | @Reusable 8 | class FlavorDataImpl @Inject constructor() : FlavorData { 9 | override val flavor: FlavorData.Flavor = FlavorData.Flavor.Google 10 | override val appDownloadLink: String = 11 | "https://play.google.com/store/apps/details?id=io.github.deweyreed.timer.google" 12 | } 13 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 14 | 15 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_about.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/values-night/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #FFF 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/values/attrs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #757575 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/values/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/xml/auto_backup_rules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/xml/data_extraction_rules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/xml/locales_config.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /app/src/other/java/io/github/deweyreed/timer/FlavorDataImpl.kt: -------------------------------------------------------------------------------- 1 | package io.github.deweyreed.timer 2 | 3 | import dagger.Reusable 4 | import xyz.aprildown.timer.app.base.data.FlavorData 5 | import javax.inject.Inject 6 | 7 | @Reusable 8 | class FlavorDataImpl @Inject constructor() : FlavorData { 9 | override val flavor: FlavorData.Flavor = FlavorData.Flavor.Other 10 | override val appDownloadLink: String = 11 | "https://github.com/timer-machine/timer-machine-android/releases" 12 | } 13 | -------------------------------------------------------------------------------- /app/src/other/java/io/github/deweyreed/timer/FlavorModule.kt: -------------------------------------------------------------------------------- 1 | package io.github.deweyreed.timer 2 | 3 | import dagger.Binds 4 | import dagger.Module 5 | import dagger.hilt.InstallIn 6 | import dagger.hilt.components.SingletonComponent 7 | import xyz.aprildown.timer.app.base.data.FlavorData 8 | 9 | @Module 10 | @InstallIn(SingletonComponent::class) 11 | abstract class FlavorModule { 12 | @Binds 13 | abstract fun bindFlavorData(impl: FlavorDataImpl): FlavorData 14 | } 15 | -------------------------------------------------------------------------------- /app/src/release/java/io/github/deweyreed/timer/ui/DebugSetUp.kt: -------------------------------------------------------------------------------- 1 | package io.github.deweyreed.timer.ui 2 | 3 | @Suppress("unused") 4 | fun MainActivity.setUpDebug() = Unit 5 | -------------------------------------------------------------------------------- /baselineProfile/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /component-key/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | alias(libs.plugins.android.library) 3 | alias(libs.plugins.kotlin.android) 4 | alias(libs.plugins.kotlin.parcelize) 5 | } 6 | 7 | android { 8 | namespace 'xyz.aprildown.timer.component.key' 9 | buildFeatures { 10 | compose true 11 | } 12 | composeOptions { 13 | kotlinCompilerExtensionVersion = libs.versions.composeCompiler.get() 14 | } 15 | } 16 | 17 | dependencies { 18 | implementation project(':app-base') 19 | 20 | implementation libs.androidx.preference 21 | 22 | implementation platform(libs.compose.bom) 23 | implementation libs.bundles.compose 24 | 25 | implementation libs.flexbox 26 | implementation libs.hmsPicker 27 | implementation libs.materialPopupMenu 28 | implementation libs.scrollHmsPicker 29 | implementation libs.coil 30 | implementation libs.coil.compose 31 | implementation libs.zoomable 32 | 33 | } 34 | -------------------------------------------------------------------------------- /component-key/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /component-key/src/main/java/androidx/appcompat/widget/TooltipCompatFix.kt: -------------------------------------------------------------------------------- 1 | package androidx.appcompat.widget 2 | 3 | import android.annotation.SuppressLint 4 | import android.os.Build 5 | import android.view.View 6 | 7 | object TooltipCompatFix { 8 | /** 9 | * Tooltip' platform implementation on Android O(API 26) is broken and crashes. 10 | * So I use the compat version on all pre-Q devices. 11 | * 12 | * https://issuetracker.google.com/issues/64461213 13 | * [TooltipCompat.setTooltipText] 14 | */ 15 | @SuppressLint("RestrictedApi") 16 | fun setTooltipText(view: View, tooltipText: CharSequence?) { 17 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { 18 | view.tooltipText = tooltipText 19 | } else { 20 | TooltipCompatHandler.setTooltipText(view, tooltipText) 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /component-key/src/main/java/xyz/aprildown/timer/component/key/ImageActionMapper.kt: -------------------------------------------------------------------------------- 1 | package xyz.aprildown.timer.component.key 2 | 3 | import androidx.core.net.toUri 4 | import coil.map.Mapper 5 | import coil.request.Options 6 | import xyz.aprildown.timer.domain.entities.ImageAction 7 | import xyz.aprildown.timer.domain.entities.ResourceContentType 8 | import java.io.File 9 | 10 | class ImageActionMapper : Mapper { 11 | override fun map(data: ImageAction, options: Options): Any { 12 | return when (data.type) { 13 | ResourceContentType.CanonicalPath -> File(data.data) 14 | ResourceContentType.RelativePath -> error("Relative path can't be loaded") 15 | ResourceContentType.Uri -> data.data.toUri() 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /component-key/src/main/res/drawable/divider_normal_flexbox.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | -------------------------------------------------------------------------------- /component-key/src/main/res/layout/dialog_simple_input.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 15 | 16 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /component-key/src/main/res/layout/layout_behaviour.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 15 | 16 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /component-key/src/main/res/layout/layout_editable_behaviour_image.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | -------------------------------------------------------------------------------- /component-key/src/main/res/layout/layout_list_item.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | -------------------------------------------------------------------------------- /component-key/src/main/res/layout/layout_list_item_with_layout.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | -------------------------------------------------------------------------------- /component-key/src/main/res/layout/layout_time_picker_panel.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 14 | -------------------------------------------------------------------------------- /component-key/src/main/res/layout/layout_time_picker_scroll.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 14 | -------------------------------------------------------------------------------- /component-key/src/main/res/layout/view_behavior_chip.xml: -------------------------------------------------------------------------------- 1 | 2 | 19 | -------------------------------------------------------------------------------- /component-key/src/main/res/layout/view_list_item_with_layout_check.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | -------------------------------------------------------------------------------- /component-key/src/main/res/layout/view_list_item_with_layout_radio.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | -------------------------------------------------------------------------------- /component-key/src/main/res/layout/view_list_item_with_layout_switch.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | -------------------------------------------------------------------------------- /component-key/src/main/res/layout/view_list_item_with_layout_text.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | -------------------------------------------------------------------------------- /component-key/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8dp 4 | 4dp 5 | 6 | 8dp 7 | 56dp 8 | 64dp 9 | 88dp 10 | 11 | -------------------------------------------------------------------------------- /component-key/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | -------------------------------------------------------------------------------- /component-key/src/main/res/values/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | -------------------------------------------------------------------------------- /component-main/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | alias(libs.plugins.android.library) 3 | alias(libs.plugins.kotlin.android) 4 | } 5 | 6 | android { 7 | namespace 'xyz.aprildown.timer.workshop' 8 | } 9 | 10 | dependencies { 11 | implementation project(':app-base') 12 | 13 | implementation libs.androidx.preference 14 | 15 | implementation libs.fastAdapter 16 | implementation libs.fastAdapter.binding 17 | 18 | } 19 | -------------------------------------------------------------------------------- /component-main/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /component-main/src/main/res/xml/pref_whitelist.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | 13 | 14 | 18 | 19 | -------------------------------------------------------------------------------- /component-settings/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | alias(libs.plugins.android.library) 3 | alias(libs.plugins.kotlin.android) 4 | } 5 | 6 | android { 7 | namespace 'xyz.aprildown.timer.component.settings' 8 | } 9 | 10 | dependencies { 11 | implementation project(':app-base') 12 | implementation project(':component-key') 13 | } 14 | -------------------------------------------------------------------------------- /component-settings/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /component-tts/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | alias(libs.plugins.android.library) 3 | alias(libs.plugins.kotlin.android) 4 | alias(libs.plugins.kotlin.kapt) 5 | alias(libs.plugins.hilt) 6 | } 7 | 8 | android { 9 | namespace 'com.github.deweyreed.timer.component.tts' 10 | } 11 | 12 | dependencies { 13 | implementation project(':app-base') 14 | 15 | implementation libs.androidx.work 16 | 17 | implementation libs.hilt.android 18 | kapt libs.hilt.compiler 19 | implementation libs.hilt.work 20 | kapt libs.hilt.work.compiler 21 | 22 | implementation libs.diskLruCache 23 | } 24 | -------------------------------------------------------------------------------- /component-tts/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /compose_compiler_config.conf: -------------------------------------------------------------------------------- 1 | kotlin.collections.* 2 | xyz.aprildown.timer.domain.usecases.Fruit 3 | -------------------------------------------------------------------------------- /data/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /data/src/main/java/xyz/aprildown/timer/data/datas/AppDataData.kt: -------------------------------------------------------------------------------- 1 | package xyz.aprildown.timer.data.datas 2 | 3 | import androidx.annotation.Keep 4 | import com.squareup.moshi.Json 5 | import com.squareup.moshi.JsonClass 6 | 7 | @Keep 8 | @JsonClass(generateAdapter = true) 9 | internal data class AppDataData( 10 | @Json(name = "folders") 11 | val folders: List = emptyList(), 12 | 13 | @Json(name = "timers") 14 | val timers: List = emptyList(), 15 | 16 | @Json(name = "notifier") 17 | val notifier: StepData.Step? = null, 18 | 19 | @Json(name = "timerStamps") 20 | val timerStamps: List = emptyList(), 21 | 22 | @Json(name = "schedulers") 23 | val schedulers: List = emptyList(), 24 | 25 | @Json(name = "prefs") 26 | val prefs: Map = emptyMap() 27 | ) 28 | -------------------------------------------------------------------------------- /data/src/main/java/xyz/aprildown/timer/data/datas/BehaviourData.kt: -------------------------------------------------------------------------------- 1 | package xyz.aprildown.timer.data.datas 2 | 3 | import androidx.annotation.Keep 4 | import com.squareup.moshi.Json 5 | import xyz.aprildown.timer.domain.entities.BehaviourType 6 | 7 | @Keep 8 | internal data class BehaviourData( 9 | @Json(name = "type") 10 | val type: BehaviourType, 11 | 12 | @Json(name = "label") 13 | val label: String = "", 14 | 15 | @Json(name = "content") 16 | val content: String = "", 17 | 18 | @Json(name = "loop") 19 | val loop: Boolean = true 20 | ) 21 | -------------------------------------------------------------------------------- /data/src/main/java/xyz/aprildown/timer/data/datas/FolderData.kt: -------------------------------------------------------------------------------- 1 | package xyz.aprildown.timer.data.datas 2 | 3 | import androidx.room.ColumnInfo 4 | import androidx.room.Entity 5 | import androidx.room.PrimaryKey 6 | import com.squareup.moshi.Json 7 | import com.squareup.moshi.JsonClass 8 | 9 | @JsonClass(generateAdapter = true) 10 | @Entity(tableName = "Folder") 11 | internal data class FolderData( 12 | @Json(name = "id") 13 | @PrimaryKey(autoGenerate = true) 14 | @ColumnInfo(name = "id") 15 | val id: Long, 16 | 17 | @Json(name = "name") 18 | @ColumnInfo(name = "name") 19 | val name: String 20 | 21 | ) 22 | -------------------------------------------------------------------------------- /data/src/main/java/xyz/aprildown/timer/data/datas/TimerMoreData.kt: -------------------------------------------------------------------------------- 1 | package xyz.aprildown.timer.data.datas 2 | 3 | import androidx.annotation.Keep 4 | import com.squareup.moshi.Json 5 | import xyz.aprildown.timer.domain.entities.TimerEntity 6 | 7 | @Keep 8 | internal data class TimerMoreData( 9 | @Json(name = "showNotif") 10 | val showNotif: Boolean = true, 11 | 12 | @Json(name = "notifCount") 13 | val notifCount: Boolean = true, 14 | 15 | @Json(name = "triggerTimerId") 16 | val triggerTimerId: Int = TimerEntity.NULL_ID 17 | ) 18 | -------------------------------------------------------------------------------- /data/src/main/java/xyz/aprildown/timer/data/di/DataModule.kt: -------------------------------------------------------------------------------- 1 | package xyz.aprildown.timer.data.di 2 | 3 | import android.content.Context 4 | import android.content.SharedPreferences 5 | import dagger.Module 6 | import dagger.Provides 7 | import dagger.Reusable 8 | import dagger.hilt.InstallIn 9 | import dagger.hilt.android.qualifiers.ApplicationContext 10 | import dagger.hilt.components.SingletonComponent 11 | import xyz.aprildown.timer.data.db.MachineDatabase 12 | import xyz.aprildown.tools.helper.safeSharedPreference 13 | import javax.inject.Singleton 14 | 15 | @Module 16 | @InstallIn(SingletonComponent::class) 17 | object DataModule { 18 | @Singleton 19 | @Provides 20 | fun provideDatabase(@ApplicationContext context: Context): MachineDatabase { 21 | return MachineDatabase.createPersistentDatabase(context) 22 | } 23 | 24 | @Reusable 25 | @Provides 26 | fun provideSharedPreferences(@ApplicationContext context: Context): SharedPreferences { 27 | return context.safeSharedPreference 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /data/src/main/java/xyz/aprildown/timer/data/job/JobDirector.kt: -------------------------------------------------------------------------------- 1 | package xyz.aprildown.timer.data.job 2 | 3 | import android.app.Application 4 | import com.evernote.android.job.JobManager 5 | 6 | fun Application.initJob() { 7 | JobManager.create(this).addJobCreator(NewJobCreator()) 8 | } 9 | -------------------------------------------------------------------------------- /data/src/main/java/xyz/aprildown/timer/data/job/NewJobCreator.kt: -------------------------------------------------------------------------------- 1 | package xyz.aprildown.timer.data.job 2 | 3 | import com.evernote.android.job.Job 4 | import com.evernote.android.job.JobCreator 5 | 6 | internal class NewJobCreator : JobCreator { 7 | override fun create(tag: String): Job? = when { 8 | tag.startsWith(SchedulerJob.TAG_PREFIX) -> SchedulerJob() 9 | else -> null 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /data/src/main/java/xyz/aprildown/timer/data/mappers/BehaviourMapper.kt: -------------------------------------------------------------------------------- 1 | package xyz.aprildown.timer.data.mappers 2 | 3 | import dagger.Reusable 4 | import xyz.aprildown.timer.data.datas.BehaviourData 5 | import xyz.aprildown.timer.domain.Mapper 6 | import xyz.aprildown.timer.domain.entities.BehaviourEntity 7 | import javax.inject.Inject 8 | 9 | @Reusable 10 | internal class BehaviourMapper @Inject constructor() : Mapper() { 11 | override fun mapFrom(from: BehaviourData): BehaviourEntity { 12 | return BehaviourEntity( 13 | type = from.type, 14 | str1 = from.label, 15 | str2 = from.content, 16 | bool = from.loop 17 | ) 18 | } 19 | 20 | override fun mapTo(from: BehaviourEntity): BehaviourData { 21 | return BehaviourData( 22 | type = from.type, 23 | label = from.str1, 24 | content = from.str2, 25 | loop = from.bool 26 | ) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /data/src/main/java/xyz/aprildown/timer/data/mappers/FolderMapper.kt: -------------------------------------------------------------------------------- 1 | package xyz.aprildown.timer.data.mappers 2 | 3 | import dagger.Reusable 4 | import xyz.aprildown.timer.data.datas.FolderData 5 | import xyz.aprildown.timer.domain.Mapper 6 | import xyz.aprildown.timer.domain.entities.FolderEntity 7 | import javax.inject.Inject 8 | 9 | @Reusable 10 | internal class FolderMapper @Inject constructor() : Mapper() { 11 | 12 | override fun mapFrom(from: FolderData): FolderEntity { 13 | return FolderEntity( 14 | id = from.id, 15 | name = from.name 16 | ) 17 | } 18 | 19 | override fun mapTo(from: FolderEntity): FolderData { 20 | return FolderData( 21 | id = from.id, 22 | name = from.name 23 | ) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /data/src/main/java/xyz/aprildown/timer/data/mappers/MapperUtils.kt: -------------------------------------------------------------------------------- 1 | package xyz.aprildown.timer.data.mappers 2 | 3 | import xyz.aprildown.timer.domain.Mapper 4 | 5 | internal fun Data.fromWithMapper(mapper: Mapper): Entity = 6 | mapper.mapFrom(this) 7 | 8 | internal fun List.fromWithMapper(mapper: Mapper): List = 9 | mapper.mapFrom(this) 10 | 11 | internal fun Entity.toWithMapper(mapper: Mapper): Data = 12 | mapper.mapTo(this) 13 | 14 | internal fun List.toWithMapper(mapper: Mapper): List = 15 | mapper.mapTo(this) 16 | -------------------------------------------------------------------------------- /data/src/main/java/xyz/aprildown/timer/data/mappers/TimerMoreMapper.kt: -------------------------------------------------------------------------------- 1 | package xyz.aprildown.timer.data.mappers 2 | 3 | import dagger.Reusable 4 | import xyz.aprildown.timer.data.datas.TimerMoreData 5 | import xyz.aprildown.timer.domain.Mapper 6 | import xyz.aprildown.timer.domain.entities.TimerMoreEntity 7 | import javax.inject.Inject 8 | 9 | @Reusable 10 | internal class TimerMoreMapper @Inject constructor() : Mapper() { 11 | 12 | override fun mapFrom(from: TimerMoreData): TimerMoreEntity { 13 | return TimerMoreEntity( 14 | showNotif = from.showNotif, 15 | notifCount = from.notifCount, 16 | triggerTimerId = from.triggerTimerId 17 | ) 18 | } 19 | 20 | override fun mapTo(from: TimerMoreEntity): TimerMoreData { 21 | return TimerMoreData( 22 | showNotif = from.showNotif, 23 | notifCount = from.notifCount, 24 | triggerTimerId = from.triggerTimerId 25 | ) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /data/src/main/java/xyz/aprildown/timer/data/mappers/TimerStampMapper.kt: -------------------------------------------------------------------------------- 1 | package xyz.aprildown.timer.data.mappers 2 | 3 | import dagger.Reusable 4 | import xyz.aprildown.timer.data.datas.TimerStampData 5 | import xyz.aprildown.timer.domain.Mapper 6 | import xyz.aprildown.timer.domain.entities.TimerStampEntity 7 | import javax.inject.Inject 8 | 9 | @Reusable 10 | internal class TimerStampMapper @Inject constructor() : Mapper() { 11 | override fun mapFrom(from: TimerStampData): TimerStampEntity { 12 | return TimerStampEntity( 13 | id = from.id, 14 | timerId = from.timerId, 15 | start = if (from.start == 0L) from.end else from.start, 16 | end = from.end 17 | ) 18 | } 19 | 20 | override fun mapTo(from: TimerStampEntity): TimerStampData { 21 | return TimerStampData( 22 | id = from.id, 23 | timerId = from.timerId, 24 | start = from.start, 25 | end = from.end 26 | ) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /detekt-config.yml: -------------------------------------------------------------------------------- 1 | complexity: 2 | ComplexCondition: 3 | threshold: 6 4 | CyclomaticComplexMethod: 5 | active: false 6 | LargeClass: 7 | threshold: 2000 8 | LongMethod: 9 | active: false 10 | LongParameterList: 11 | functionThreshold: 22 12 | constructorThreshold: 22 13 | NestedBlockDepth: 14 | active: false 15 | TooManyFunctions: 16 | active: false 17 | 18 | exceptions: 19 | TooGenericExceptionCaught: 20 | active: false 21 | TooGenericExceptionThrown: 22 | active: false 23 | 24 | naming: 25 | FunctionNaming: 26 | ignoreAnnotated: 27 | - "Composable" 28 | 29 | style: 30 | LoopWithTooManyJumpStatements: 31 | maxJumpCount: 2 32 | MagicNumber: 33 | active: false 34 | ReturnCount: 35 | active: false 36 | ThrowsCount: 37 | max: 10 38 | UnnecessaryAbstractClass: 39 | active: false 40 | UnusedPrivateMember: 41 | active: false 42 | -------------------------------------------------------------------------------- /detekt.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: "io.gitlab.arturbosch.detekt" 2 | 3 | dependencies { 4 | detektPlugins libs.detekt.formatting 5 | } 6 | 7 | detekt { 8 | toolVersion = libs.versions.detekt.get() 9 | config.setFrom(buildscript.sourceFile.getParent().toString() + "/detekt-config.yml") 10 | buildUponDefaultConfig = true 11 | } 12 | 13 | tasks.named("detekt").configure { 14 | reports { 15 | xml.required.set(false) 16 | txt.required.set(false) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /domain/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /domain/src/main/java/xyz/aprildown/timer/domain/Mapper.kt: -------------------------------------------------------------------------------- 1 | package xyz.aprildown.timer.domain 2 | 3 | abstract class Mapper { 4 | open fun mapFrom(from: Data): Entity { 5 | throw UnsupportedOperationException() 6 | } 7 | 8 | fun mapFrom(from: List): List { 9 | return from.map { mapFrom(it) } 10 | } 11 | 12 | open fun mapTo(from: Entity): Data { 13 | throw UnsupportedOperationException() 14 | } 15 | 16 | fun mapTo(from: List): List { 17 | return from.map { mapTo(it) } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /domain/src/main/java/xyz/aprildown/timer/domain/entities/AppDataEntity.kt: -------------------------------------------------------------------------------- 1 | package xyz.aprildown.timer.domain.entities 2 | 3 | /** 4 | * Only for export and import to whole app's data. 5 | */ 6 | 7 | data class AppDataEntity( 8 | val folders: List = emptyList(), 9 | val timers: List = emptyList(), 10 | val notifier: StepEntity.Step? = null, 11 | val timerStamps: List = emptyList(), 12 | val schedulers: List = emptyList(), 13 | val prefs: Map = emptyMap() 14 | ) 15 | -------------------------------------------------------------------------------- /domain/src/main/java/xyz/aprildown/timer/domain/entities/FolderEntity.kt: -------------------------------------------------------------------------------- 1 | package xyz.aprildown.timer.domain.entities 2 | 3 | data class FolderEntity( 4 | val id: Long, 5 | val name: String 6 | ) { 7 | 8 | val isDefault: Boolean get() = id == FOLDER_DEFAULT 9 | val isTrash: Boolean get() = id == FOLDER_TRASH 10 | 11 | companion object { 12 | const val NEW_ID = 0L 13 | 14 | const val FOLDER_DEFAULT = 1L // Long.MAX_VALUE is the max value of SQLite. 15 | const val FOLDER_TRASH = 2L 16 | } 17 | } 18 | 19 | enum class FolderSortBy { 20 | AddedNewest, AddedOldest, RunNewest, RunOldest, AToZ, ZToA, 21 | } 22 | -------------------------------------------------------------------------------- /domain/src/main/java/xyz/aprildown/timer/domain/entities/StepEntity.kt: -------------------------------------------------------------------------------- 1 | package xyz.aprildown.timer.domain.entities 2 | 3 | enum class StepType { 4 | NORMAL, NOTIFIER, START, END 5 | } 6 | 7 | sealed class StepEntity { 8 | data class Step( 9 | val label: String, 10 | val length: Long, 11 | val behaviour: List = emptyList(), 12 | val type: StepType = StepType.NORMAL 13 | ) : StepEntity() 14 | 15 | data class Group( 16 | val name: String, 17 | val loop: Int, 18 | val steps: List 19 | ) : StepEntity() 20 | } 21 | -------------------------------------------------------------------------------- /domain/src/main/java/xyz/aprildown/timer/domain/entities/TimerStampEntity.kt: -------------------------------------------------------------------------------- 1 | package xyz.aprildown.timer.domain.entities 2 | 3 | import xyz.aprildown.timer.domain.TimeUtils.toEpochMilli 4 | import java.time.LocalDateTime 5 | import java.time.LocalTime 6 | import java.time.Month 7 | import java.time.YearMonth 8 | 9 | data class TimerStampEntity( 10 | val id: Int, 11 | val timerId: Int, 12 | val start: Long, 13 | val end: Long 14 | ) { 15 | 16 | val duration: Long get() = if (start == 0L) 0 else end - start 17 | 18 | constructor(timerId: Int, startTime: Long) : this( 19 | NEW_ID, 20 | timerId, 21 | startTime, 22 | System.currentTimeMillis() 23 | ) 24 | 25 | companion object { 26 | const val NEW_ID = 0 27 | const val NULL_ID = 0 28 | 29 | fun getMinDateMilli(): Long { 30 | return LocalDateTime.of( 31 | YearMonth.of(2018, Month.NOVEMBER).atDay(1), 32 | LocalTime.MIN 33 | ).toEpochMilli() 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /domain/src/main/java/xyz/aprildown/timer/domain/repositories/AppPreferencesProvider.kt: -------------------------------------------------------------------------------- 1 | package xyz.aprildown.timer.domain.repositories 2 | 3 | interface AppPreferencesProvider { 4 | fun getAppPreferences(): Map 5 | fun applyAppPreferences(prefs: Map) 6 | } 7 | -------------------------------------------------------------------------------- /domain/src/main/java/xyz/aprildown/timer/domain/repositories/FolderRepository.kt: -------------------------------------------------------------------------------- 1 | package xyz.aprildown.timer.domain.repositories 2 | 3 | import xyz.aprildown.timer.domain.entities.FolderEntity 4 | 5 | interface FolderRepository { 6 | suspend fun getFolders(): List 7 | 8 | suspend fun addFolder(item: FolderEntity): Long 9 | 10 | suspend fun updateFolder(item: FolderEntity): Boolean 11 | 12 | suspend fun deleteFolder(id: Long) 13 | } 14 | -------------------------------------------------------------------------------- /domain/src/main/java/xyz/aprildown/timer/domain/repositories/NotifierRepository.kt: -------------------------------------------------------------------------------- 1 | package xyz.aprildown.timer.domain.repositories 2 | 3 | import xyz.aprildown.timer.domain.entities.StepEntity 4 | 5 | interface NotifierRepository { 6 | suspend fun get(): StepEntity.Step 7 | suspend fun set(item: StepEntity.Step?): Boolean 8 | 9 | companion object { 10 | const val NAMED_DEFAULT_NOTIFIER_NAME = "default_notifier_name" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /domain/src/main/java/xyz/aprildown/timer/domain/repositories/PreferencesRepository.kt: -------------------------------------------------------------------------------- 1 | package xyz.aprildown.timer.domain.repositories 2 | 3 | interface PreferencesRepository { 4 | suspend fun contains(key: String): Boolean 5 | 6 | suspend fun getBoolean(key: String, default: Boolean): Boolean 7 | suspend fun setBoolean(key: String, value: Boolean) 8 | 9 | suspend fun getInt(key: String, default: Int): Int 10 | suspend fun setInt(key: String, value: Int) 11 | 12 | suspend fun getLong(key: String, default: Long): Long 13 | suspend fun setLong(key: String, value: Long) 14 | 15 | suspend fun getNullableString(key: String, default: String? = null): String? 16 | suspend fun getNonNullString(key: String, default: String): String 17 | suspend fun setString(key: String, value: String?) 18 | } 19 | -------------------------------------------------------------------------------- /domain/src/main/java/xyz/aprildown/timer/domain/repositories/SchedulerExecutor.kt: -------------------------------------------------------------------------------- 1 | package xyz.aprildown.timer.domain.repositories 2 | 3 | import xyz.aprildown.timer.domain.entities.SchedulerEntity 4 | import xyz.aprildown.timer.domain.usecases.scheduler.SetSchedulerEnable 5 | 6 | interface SchedulerExecutor { 7 | /** 8 | * @return scheduled job's trigger time 9 | */ 10 | fun schedule(scheduler: SchedulerEntity): SetSchedulerEnable.Result 11 | 12 | /** 13 | * @return canceled job count 14 | */ 15 | fun cancel(scheduler: SchedulerEntity): SetSchedulerEnable.Result 16 | } 17 | -------------------------------------------------------------------------------- /domain/src/main/java/xyz/aprildown/timer/domain/repositories/SchedulerRepository.kt: -------------------------------------------------------------------------------- 1 | package xyz.aprildown.timer.domain.repositories 2 | 3 | import xyz.aprildown.timer.domain.entities.SchedulerEntity 4 | 5 | interface SchedulerRepository { 6 | suspend fun items(): List 7 | suspend fun item(id: Int): SchedulerEntity? 8 | suspend fun add(item: SchedulerEntity): Int 9 | suspend fun save(item: SchedulerEntity): Boolean 10 | suspend fun delete(id: Int) 11 | suspend fun setSchedulerEnable(id: Int, enable: Int) 12 | suspend fun schedulersWithTimerId(id: Int): List 13 | } 14 | -------------------------------------------------------------------------------- /domain/src/main/java/xyz/aprildown/timer/domain/repositories/TaskerEventTrigger.kt: -------------------------------------------------------------------------------- 1 | package xyz.aprildown.timer.domain.repositories 2 | 3 | interface TaskerEventTrigger { 4 | fun timerStart(id: Int) 5 | fun timerEnd(id: Int) 6 | } 7 | -------------------------------------------------------------------------------- /domain/src/main/java/xyz/aprildown/timer/domain/repositories/TimerRepository.kt: -------------------------------------------------------------------------------- 1 | package xyz.aprildown.timer.domain.repositories 2 | 3 | import kotlinx.coroutines.flow.Flow 4 | import xyz.aprildown.timer.domain.entities.TimerEntity 5 | import xyz.aprildown.timer.domain.entities.TimerInfo 6 | 7 | interface TimerRepository { 8 | suspend fun items(): List 9 | suspend fun item(id: Int): TimerEntity? 10 | suspend fun add(item: TimerEntity): Int 11 | suspend fun save(item: TimerEntity): Boolean 12 | suspend fun delete(id: Int) 13 | suspend fun getTimerInfoByTimerId(timerId: Int): TimerInfo? 14 | fun getTimerInfoFlow(folderId: Long): Flow> 15 | suspend fun getTimerInfo(folderId: Long): List 16 | suspend fun changeTimerFolder(timerId: Int, folderId: Long) 17 | suspend fun moveFolderTimersToAnother(originalFolderId: Long, targetFolderId: Long) 18 | } 19 | -------------------------------------------------------------------------------- /domain/src/main/java/xyz/aprildown/timer/domain/usecases/CoroutinesUseCase.kt: -------------------------------------------------------------------------------- 1 | package xyz.aprildown.timer.domain.usecases 2 | 3 | import kotlinx.coroutines.CoroutineDispatcher 4 | import kotlinx.coroutines.withContext 5 | 6 | abstract class CoroutinesUseCase(protected val dispatcher: CoroutineDispatcher) { 7 | 8 | protected abstract suspend fun create(params: Params): Result 9 | 10 | suspend fun execute(params: Params): Result = withContext(dispatcher) { 11 | create(params) 12 | } 13 | 14 | suspend operator fun invoke(params: Params): Result = execute(params) 15 | } 16 | 17 | suspend operator fun CoroutinesUseCase.invoke(): Result = invoke(Unit) 18 | -------------------------------------------------------------------------------- /domain/src/main/java/xyz/aprildown/timer/domain/usecases/Fruit.kt: -------------------------------------------------------------------------------- 1 | package xyz.aprildown.timer.domain.usecases 2 | 3 | sealed class Fruit { 4 | 5 | data class Ripe(val data: T) : Fruit() 6 | data class Rotten(val exception: Throwable) : Fruit() { 7 | constructor(message: String) : this(IllegalStateException(message)) 8 | } 9 | 10 | override fun toString(): String { 11 | return when (this) { 12 | is Ripe<*> -> "Success[data=$data]" 13 | is Rotten -> "Rotten[exception=$exception]" 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /domain/src/main/java/xyz/aprildown/timer/domain/usecases/data/NotifyDataChanged.kt: -------------------------------------------------------------------------------- 1 | package xyz.aprildown.timer.domain.usecases.data 2 | 3 | import dagger.Reusable 4 | import xyz.aprildown.timer.domain.repositories.AppDataRepository 5 | import xyz.aprildown.timer.domain.utils.fireAndForget 6 | import javax.inject.Inject 7 | 8 | @Reusable 9 | class NotifyDataChanged @Inject constructor( 10 | private val repo: AppDataRepository 11 | ) { 12 | operator fun invoke() { 13 | fireAndForget { 14 | repo.notifyDataChanged() 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /domain/src/main/java/xyz/aprildown/timer/domain/usecases/notifier/GetNotifier.kt: -------------------------------------------------------------------------------- 1 | package xyz.aprildown.timer.domain.usecases.notifier 2 | 3 | import dagger.Reusable 4 | import kotlinx.coroutines.CoroutineDispatcher 5 | import xyz.aprildown.timer.domain.di.IoDispatcher 6 | import xyz.aprildown.timer.domain.entities.StepEntity 7 | import xyz.aprildown.timer.domain.repositories.NotifierRepository 8 | import xyz.aprildown.timer.domain.usecases.CoroutinesUseCase 9 | import javax.inject.Inject 10 | 11 | @Reusable 12 | class GetNotifier @Inject constructor( 13 | @IoDispatcher dispatcher: CoroutineDispatcher, 14 | private val repository: NotifierRepository 15 | ) : CoroutinesUseCase(dispatcher) { 16 | override suspend fun create(params: Unit): StepEntity.Step { 17 | return repository.get() 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /domain/src/main/java/xyz/aprildown/timer/domain/usecases/notifier/SaveNotifier.kt: -------------------------------------------------------------------------------- 1 | package xyz.aprildown.timer.domain.usecases.notifier 2 | 3 | import dagger.Reusable 4 | import kotlinx.coroutines.CoroutineDispatcher 5 | import xyz.aprildown.timer.domain.di.IoDispatcher 6 | import xyz.aprildown.timer.domain.entities.StepEntity 7 | import xyz.aprildown.timer.domain.repositories.AppDataRepository 8 | import xyz.aprildown.timer.domain.repositories.NotifierRepository 9 | import xyz.aprildown.timer.domain.usecases.CoroutinesUseCase 10 | import javax.inject.Inject 11 | 12 | @Reusable 13 | class SaveNotifier @Inject constructor( 14 | @IoDispatcher dispatcher: CoroutineDispatcher, 15 | private val repository: NotifierRepository, 16 | private val appDataRepository: AppDataRepository 17 | ) : CoroutinesUseCase(dispatcher) { 18 | override suspend fun create(params: StepEntity.Step): Boolean { 19 | return repository.set(params).also { 20 | appDataRepository.notifyDataChanged() 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /domain/src/main/java/xyz/aprildown/timer/domain/usecases/scheduler/GetScheduler.kt: -------------------------------------------------------------------------------- 1 | package xyz.aprildown.timer.domain.usecases.scheduler 2 | 3 | import dagger.Reusable 4 | import kotlinx.coroutines.CoroutineDispatcher 5 | import xyz.aprildown.timer.domain.di.IoDispatcher 6 | import xyz.aprildown.timer.domain.entities.SchedulerEntity 7 | import xyz.aprildown.timer.domain.entities.TimerEntity 8 | import xyz.aprildown.timer.domain.repositories.SchedulerRepository 9 | import xyz.aprildown.timer.domain.usecases.CoroutinesUseCase 10 | import javax.inject.Inject 11 | 12 | @Reusable 13 | class GetScheduler @Inject constructor( 14 | @IoDispatcher dispatcher: CoroutineDispatcher, 15 | private val repository: SchedulerRepository 16 | ) : CoroutinesUseCase(dispatcher) { 17 | override suspend fun create(params: Int): SchedulerEntity? { 18 | return if (params == TimerEntity.NULL_ID) { 19 | null 20 | } else { 21 | repository.item(params) 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /domain/src/main/java/xyz/aprildown/timer/domain/usecases/scheduler/GetSchedulers.kt: -------------------------------------------------------------------------------- 1 | package xyz.aprildown.timer.domain.usecases.scheduler 2 | 3 | import dagger.Reusable 4 | import kotlinx.coroutines.CoroutineDispatcher 5 | import xyz.aprildown.timer.domain.di.IoDispatcher 6 | import xyz.aprildown.timer.domain.entities.SchedulerEntity 7 | import xyz.aprildown.timer.domain.repositories.SchedulerRepository 8 | import xyz.aprildown.timer.domain.usecases.CoroutinesUseCase 9 | import javax.inject.Inject 10 | 11 | @Reusable 12 | class GetSchedulers @Inject constructor( 13 | @IoDispatcher dispatcher: CoroutineDispatcher, 14 | private val repository: SchedulerRepository 15 | ) : CoroutinesUseCase>(dispatcher) { 16 | override suspend fun create(params: Unit): List { 17 | return repository.items() 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /domain/src/main/java/xyz/aprildown/timer/domain/usecases/timer/AddTimer.kt: -------------------------------------------------------------------------------- 1 | package xyz.aprildown.timer.domain.usecases.timer 2 | 3 | import dagger.Reusable 4 | import kotlinx.coroutines.CoroutineDispatcher 5 | import xyz.aprildown.timer.domain.di.IoDispatcher 6 | import xyz.aprildown.timer.domain.entities.TimerEntity 7 | import xyz.aprildown.timer.domain.repositories.AppDataRepository 8 | import xyz.aprildown.timer.domain.repositories.TimerRepository 9 | import xyz.aprildown.timer.domain.usecases.CoroutinesUseCase 10 | import javax.inject.Inject 11 | 12 | @Reusable 13 | class AddTimer @Inject constructor( 14 | @IoDispatcher dispatcher: CoroutineDispatcher, 15 | private val repository: TimerRepository, 16 | private val appDataRepository: AppDataRepository 17 | ) : CoroutinesUseCase(dispatcher) { 18 | override suspend fun create(params: TimerEntity): Int { 19 | return repository.add(params).also { 20 | appDataRepository.notifyDataChanged() 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /domain/src/main/java/xyz/aprildown/timer/domain/usecases/timer/FindTimerInfo.kt: -------------------------------------------------------------------------------- 1 | package xyz.aprildown.timer.domain.usecases.timer 2 | 3 | import dagger.Reusable 4 | import kotlinx.coroutines.CoroutineDispatcher 5 | import xyz.aprildown.timer.domain.di.IoDispatcher 6 | import xyz.aprildown.timer.domain.entities.TimerEntity 7 | import xyz.aprildown.timer.domain.entities.TimerInfo 8 | import xyz.aprildown.timer.domain.repositories.TimerRepository 9 | import xyz.aprildown.timer.domain.usecases.CoroutinesUseCase 10 | import javax.inject.Inject 11 | 12 | @Reusable 13 | class FindTimerInfo @Inject constructor( 14 | @IoDispatcher dispatcher: CoroutineDispatcher, 15 | private val repository: TimerRepository 16 | ) : CoroutinesUseCase(dispatcher) { 17 | override suspend fun create(params: Int): TimerInfo? { 18 | if (params == TimerEntity.NULL_ID) return null 19 | return repository.getTimerInfoByTimerId(params) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /domain/src/main/java/xyz/aprildown/timer/domain/usecases/timer/GetTimer.kt: -------------------------------------------------------------------------------- 1 | package xyz.aprildown.timer.domain.usecases.timer 2 | 3 | import dagger.Reusable 4 | import kotlinx.coroutines.CoroutineDispatcher 5 | import xyz.aprildown.timer.domain.di.IoDispatcher 6 | import xyz.aprildown.timer.domain.entities.TimerEntity 7 | import xyz.aprildown.timer.domain.repositories.TimerRepository 8 | import xyz.aprildown.timer.domain.usecases.CoroutinesUseCase 9 | import javax.inject.Inject 10 | 11 | @Reusable 12 | class GetTimer @Inject constructor( 13 | @IoDispatcher dispatcher: CoroutineDispatcher, 14 | private val repository: TimerRepository 15 | ) : CoroutinesUseCase(dispatcher) { 16 | override suspend fun create(params: Int): TimerEntity? { 17 | return if (params == TimerEntity.NULL_ID) { 18 | null 19 | } else { 20 | repository.item(params) 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /domain/src/main/java/xyz/aprildown/timer/domain/utils/AppConfig.kt: -------------------------------------------------------------------------------- 1 | package xyz.aprildown.timer.domain.utils 2 | 3 | import xyz.aprildown.timer.domain.BuildConfig 4 | 5 | object AppConfig { 6 | val showFirstTimeInfo: Boolean = BuildConfig.SHOW_FIRST_TIME 7 | val openDebug: Boolean = BuildConfig.OPEN_DEBUG 8 | } 9 | -------------------------------------------------------------------------------- /domain/src/main/java/xyz/aprildown/timer/domain/utils/AppTracker.kt: -------------------------------------------------------------------------------- 1 | package xyz.aprildown.timer.domain.utils 2 | 3 | import android.content.Context 4 | 5 | interface AppTracker { 6 | fun init(context: Context) 7 | fun trackError(throwable: Throwable, message: String? = null) 8 | suspend fun hasCrashedInLastSession(): Boolean 9 | } 10 | -------------------------------------------------------------------------------- /domain/src/main/java/xyz/aprildown/timer/domain/utils/ApplicationScope.kt: -------------------------------------------------------------------------------- 1 | package xyz.aprildown.timer.domain.utils 2 | 3 | import kotlinx.coroutines.CoroutineScope 4 | import kotlinx.coroutines.Dispatchers 5 | import kotlinx.coroutines.SupervisorJob 6 | import kotlinx.coroutines.launch 7 | import kotlin.coroutines.CoroutineContext 8 | import kotlin.coroutines.EmptyCoroutineContext 9 | 10 | private val applicationScope by lazy { 11 | CoroutineScope(SupervisorJob() + Dispatchers.Default) 12 | } 13 | 14 | fun fireAndForget( 15 | context: CoroutineContext = EmptyCoroutineContext, 16 | block: suspend () -> Unit 17 | ) { 18 | applicationScope.launch(context) { 19 | block.invoke() 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /domain/src/main/java/xyz/aprildown/timer/domain/utils/FileUtils.kt: -------------------------------------------------------------------------------- 1 | package xyz.aprildown.timer.domain.utils 2 | 3 | import java.io.File 4 | 5 | fun File.ensureDirExistence(): File { 6 | if (!exists()) mkdirs() 7 | return this 8 | } 9 | 10 | fun File.ensureNewFile(): File { 11 | parentFile?.ensureDirExistence() 12 | if (exists()) delete() 13 | createNewFile() 14 | return this 15 | } 16 | -------------------------------------------------------------------------------- /domain/src/main/java/xyz/aprildown/tools/helper/SharedPreferencesHelper.kt: -------------------------------------------------------------------------------- 1 | package xyz.aprildown.tools.helper 2 | 3 | import android.content.Context 4 | import android.content.SharedPreferences 5 | import androidx.preference.PreferenceManager 6 | 7 | /** 8 | * This file is just for backward-compatibility. 9 | */ 10 | 11 | val Context.safeSharedPreference: SharedPreferences 12 | get() = PreferenceManager.getDefaultSharedPreferences(this) 13 | -------------------------------------------------------------------------------- /domain/src/test/java/xyz/aprildown/timer/domain/usecases/scheduler/SchedulerRepeatContentTest.kt: -------------------------------------------------------------------------------- 1 | package xyz.aprildown.timer.domain.usecases.scheduler 2 | 3 | import org.junit.Assert.assertEquals 4 | import org.junit.Test 5 | import xyz.aprildown.timer.domain.entities.SchedulerEntity 6 | import java.security.SecureRandom 7 | 8 | class SchedulerRepeatContentTest { 9 | 10 | @Test 11 | fun test() { 12 | val random = SecureRandom() 13 | List(50) { random.nextInt(128) }.forEach { 14 | val days = SchedulerEntity.everyDayToDays(it) 15 | val value = SchedulerEntity.daysToEveryDay(days) 16 | assertEquals(it, value) 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/images/featureGraphic.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timer-machine/timer-machine-android/699caa3dc0065722a4e6f0a76b02009e253dc280/fastlane/metadata/android/en-US/images/featureGraphic.jpg -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/images/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timer-machine/timer-machine-android/699caa3dc0065722a4e6f0a76b02009e253dc280/fastlane/metadata/android/en-US/images/icon.png -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/images/phoneScreenshots/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timer-machine/timer-machine-android/699caa3dc0065722a4e6f0a76b02009e253dc280/fastlane/metadata/android/en-US/images/phoneScreenshots/1.png -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/images/phoneScreenshots/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timer-machine/timer-machine-android/699caa3dc0065722a4e6f0a76b02009e253dc280/fastlane/metadata/android/en-US/images/phoneScreenshots/2.png -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/images/phoneScreenshots/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timer-machine/timer-machine-android/699caa3dc0065722a4e6f0a76b02009e253dc280/fastlane/metadata/android/en-US/images/phoneScreenshots/3.png -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/images/phoneScreenshots/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timer-machine/timer-machine-android/699caa3dc0065722a4e6f0a76b02009e253dc280/fastlane/metadata/android/en-US/images/phoneScreenshots/4.png -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/images/sevenInchScreenshots/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timer-machine/timer-machine-android/699caa3dc0065722a4e6f0a76b02009e253dc280/fastlane/metadata/android/en-US/images/sevenInchScreenshots/1.png -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/images/tenInchScreenshots/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timer-machine/timer-machine-android/699caa3dc0065722a4e6f0a76b02009e253dc280/fastlane/metadata/android/en-US/images/tenInchScreenshots/1.png -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/short_description.txt: -------------------------------------------------------------------------------- 1 | A highly customizable interval timer 2 | -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/title.txt: -------------------------------------------------------------------------------- 1 | TimeR Machine 2 | -------------------------------------------------------------------------------- /flavor-google/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /flavor-google/src/main/java/xyz/aprildown/timer/app/analytics/AnalyticsModule.kt: -------------------------------------------------------------------------------- 1 | package xyz.aprildown.timer.app.analytics 2 | 3 | import dagger.Binds 4 | import dagger.Module 5 | import dagger.hilt.InstallIn 6 | import dagger.hilt.components.SingletonComponent 7 | import xyz.aprildown.timer.domain.utils.AppTracker 8 | 9 | @Module 10 | @InstallIn(SingletonComponent::class) 11 | internal abstract class AnalyticsModule { 12 | @Binds 13 | abstract fun bindAppTracker(impl: AppTrackerImpl): AppTracker 14 | } 15 | -------------------------------------------------------------------------------- /flavor-google/src/main/java/xyz/aprildown/timer/app/analytics/AppTrackerImpl.kt: -------------------------------------------------------------------------------- 1 | package xyz.aprildown.timer.app.analytics 2 | 3 | import android.content.Context 4 | import com.google.firebase.crashlytics.ktx.crashlytics 5 | import com.google.firebase.ktx.Firebase 6 | import dagger.Reusable 7 | import xyz.aprildown.timer.domain.utils.AppTracker 8 | import javax.inject.Inject 9 | 10 | @Reusable 11 | internal class AppTrackerImpl @Inject constructor() : AppTracker { 12 | override fun init(context: Context) = Unit 13 | 14 | override fun trackError(throwable: Throwable, message: String?) { 15 | if (message != null) { 16 | Firebase.crashlytics.recordException(IllegalStateException(message, throwable)) 17 | } else { 18 | Firebase.crashlytics.recordException(throwable) 19 | } 20 | } 21 | 22 | override suspend fun hasCrashedInLastSession(): Boolean { 23 | return Firebase.crashlytics.didCrashOnPreviousExecution() 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /flavor-google/src/main/java/xyz/aprildown/timer/flavor/google/utils/FileUtils.kt: -------------------------------------------------------------------------------- 1 | package xyz.aprildown.timer.flavor.google.utils 2 | 3 | import java.io.File 4 | 5 | internal fun File.ensureNewFile(): File { 6 | if (exists()) { 7 | delete() 8 | } 9 | createNewFile() 10 | return this 11 | } 12 | -------------------------------------------------------------------------------- /flavor-google/src/main/java/xyz/aprildown/timer/flavor/google/utils/FirebaseExceptionUtils.kt: -------------------------------------------------------------------------------- 1 | package xyz.aprildown.timer.flavor.google.utils 2 | 3 | internal fun Throwable.causeFirstMessage(): String { 4 | fun Throwable.getCurrentMessage(): String? { 5 | return localizedMessage ?: message 6 | } 7 | 8 | return cause?.getCurrentMessage() ?: getCurrentMessage().toString() 9 | } 10 | -------------------------------------------------------------------------------- /flavor-google/src/main/java/xyz/aprildown/timer/flavor/google/utils/FirebaseStorageSetUp.kt: -------------------------------------------------------------------------------- 1 | package xyz.aprildown.timer.flavor.google.utils 2 | 3 | import com.google.firebase.ktx.Firebase 4 | import com.google.firebase.storage.ktx.storage 5 | 6 | internal fun setUpFirebaseStorage() { 7 | Firebase.storage.run { 8 | // We don't retry because our upload, download and query operations are synchronized. 9 | maxUploadRetryTimeMillis = 0 10 | maxDownloadRetryTimeMillis = 0 11 | maxOperationRetryTimeMillis = 0 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /flavor-google/src/main/res/drawable/backup_auto_backup.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /flavor-google/src/main/res/drawable/backup_cloud_restore.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /flavor-google/src/main/res/drawable/backup_cloud_state.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /flavor-google/src/main/res/drawable/backup_delete.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /flavor-google/src/main/res/drawable/backup_sign_in.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 11 | 12 | -------------------------------------------------------------------------------- /flavor-google/src/main/res/drawable/backup_sign_out.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 11 | 12 | -------------------------------------------------------------------------------- /flavor-google/src/main/res/layout/dialog_loading.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /flavor-google/src/main/res/layout/list_item_restore.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | -------------------------------------------------------------------------------- /flavor-google/src/main/res/menu/flavor_help.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /flavor-google/src/main/res/navigation/backup_graph.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 12 | 17 | 18 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timer-machine/timer-machine-android/699caa3dc0065722a4e6f0a76b02009e253dc280/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /images/google-play-badge.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timer-machine/timer-machine-android/699caa3dc0065722a4e6f0a76b02009e253dc280/images/google-play-badge.png -------------------------------------------------------------------------------- /images/showcase.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timer-machine/timer-machine-android/699caa3dc0065722a4e6f0a76b02009e253dc280/images/showcase.jpg -------------------------------------------------------------------------------- /presentation/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /presentation/src/main/java/xyz/aprildown/timer/presentation/BaseViewModel.kt: -------------------------------------------------------------------------------- 1 | package xyz.aprildown.timer.presentation 2 | 3 | import androidx.annotation.CallSuper 4 | import androidx.lifecycle.ViewModel 5 | import kotlinx.coroutines.CoroutineDispatcher 6 | import kotlinx.coroutines.CoroutineScope 7 | import kotlinx.coroutines.SupervisorJob 8 | import kotlinx.coroutines.cancel 9 | import kotlin.coroutines.CoroutineContext 10 | 11 | abstract class SimpleViewModel : ViewModel() 12 | 13 | abstract class BaseViewModel( 14 | mainDispatcher: CoroutineDispatcher 15 | ) : SimpleViewModel(), CoroutineScope { 16 | 17 | override val coroutineContext: CoroutineContext = SupervisorJob() + mainDispatcher 18 | 19 | @CallSuper 20 | override fun onCleared() { 21 | cancel() 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /presentation/src/main/java/xyz/aprildown/timer/presentation/StreamMachineIntentProvider.kt: -------------------------------------------------------------------------------- 1 | package xyz.aprildown.timer.presentation 2 | 3 | import android.content.Intent 4 | import xyz.aprildown.timer.presentation.stream.TimerIndex 5 | 6 | interface StreamMachineIntentProvider { 7 | fun bindIntent(): Intent 8 | fun startIntent(id: Int, index: TimerIndex? = null): Intent 9 | fun pauseIntent(id: Int): Intent 10 | fun decreIntent(id: Int): Intent 11 | fun increIntent(id: Int): Intent 12 | fun moveIntent(id: Int, index: TimerIndex): Intent 13 | fun resetIntent(id: Int): Intent 14 | fun adjustTimeIntent(id: Int, amount: Long): Intent 15 | } 16 | -------------------------------------------------------------------------------- /presentation/src/main/java/xyz/aprildown/timer/presentation/di/ViewModelModule.kt: -------------------------------------------------------------------------------- 1 | package xyz.aprildown.timer.presentation.di 2 | 3 | import android.app.Application 4 | import dagger.Module 5 | import dagger.Provides 6 | import dagger.hilt.InstallIn 7 | import dagger.hilt.components.SingletonComponent 8 | import xyz.aprildown.timer.presentation.R 9 | import javax.inject.Named 10 | 11 | @Module 12 | @InstallIn(SingletonComponent::class) 13 | internal object ViewModelModule { 14 | const val DEFAULT_TIMER_NAME = "default_timer_name" 15 | 16 | @Provides 17 | @Named(DEFAULT_TIMER_NAME) 18 | fun provideDefaultTimerName(context: Application): String { 19 | return context.getString(R.string.edit_default_timer_name) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /presentation/src/main/java/xyz/aprildown/timer/presentation/intro/IntroViewModel.kt: -------------------------------------------------------------------------------- 1 | package xyz.aprildown.timer.presentation.intro 2 | 3 | import dagger.hilt.android.lifecycle.HiltViewModel 4 | import kotlinx.coroutines.CoroutineDispatcher 5 | import kotlinx.coroutines.launch 6 | import xyz.aprildown.timer.domain.di.MainDispatcher 7 | import xyz.aprildown.timer.domain.entities.TimerEntity 8 | import xyz.aprildown.timer.domain.usecases.timer.AddTimer 9 | import xyz.aprildown.timer.presentation.BaseViewModel 10 | import javax.inject.Inject 11 | 12 | @HiltViewModel 13 | class IntroViewModel @Inject constructor( 14 | @MainDispatcher mainDispatcher: CoroutineDispatcher, 15 | private val addTimer: AddTimer, 16 | ) : BaseViewModel(mainDispatcher) { 17 | 18 | private val addedTimers = mutableListOf() 19 | 20 | fun addSampleTimer(timer: TimerEntity) = launch { 21 | if (timer in addedTimers) return@launch 22 | 23 | addTimer(timer) 24 | 25 | addedTimers += timer 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /presentation/src/main/java/xyz/aprildown/timer/presentation/stream/StreamState.kt: -------------------------------------------------------------------------------- 1 | package xyz.aprildown.timer.presentation.stream 2 | 3 | enum class StreamState { 4 | RUNNING, PAUSED, RESET; 5 | 6 | val isRunning get() = this == RUNNING 7 | val isPaused get() = this == PAUSED 8 | val isReset get() = this == RESET 9 | } 10 | -------------------------------------------------------------------------------- /presentation/src/main/java/xyz/aprildown/timer/presentation/stream/TimerMachineListener.kt: -------------------------------------------------------------------------------- 1 | package xyz.aprildown.timer.presentation.stream 2 | 3 | /** 4 | * timerId will be 0 if the listener is registered with addListener. 5 | * It will be the real timerId if it's registered with addAllListener. 6 | */ 7 | interface TimerMachineListener { 8 | fun begin(timerId: Int) 9 | fun started(timerId: Int, index: TimerIndex) 10 | fun paused(timerId: Int) 11 | fun updated(timerId: Int, time: Long) 12 | fun finished(timerId: Int) 13 | fun end(timerId: Int, forced: Boolean) 14 | } 15 | -------------------------------------------------------------------------------- /presentation/src/main/java/xyz/aprildown/timer/presentation/stream/task/TickListener.kt: -------------------------------------------------------------------------------- 1 | package xyz.aprildown.timer.presentation.stream.task 2 | 3 | internal interface TickListener { 4 | /** 5 | * Don't use newTime as counter. It may tick twice in one second. 6 | */ 7 | fun onNewTime(newTime: Long) 8 | } 9 | -------------------------------------------------------------------------------- /presentation/src/main/res/values/ids.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | --------------------------------------------------------------------------------