├── app
├── .gitignore
├── src
│ ├── main
│ │ ├── res
│ │ │ ├── xml
│ │ │ │ └── filepaths.xml
│ │ │ ├── mipmap-hdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ ├── ic_launcher_round.png
│ │ │ │ └── ic_launcher_foreground.png
│ │ │ ├── mipmap-mdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ ├── ic_launcher_round.png
│ │ │ │ └── ic_launcher_foreground.png
│ │ │ ├── mipmap-xhdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ ├── ic_launcher_round.png
│ │ │ │ └── ic_launcher_foreground.png
│ │ │ ├── mipmap-xxhdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ ├── ic_launcher_round.png
│ │ │ │ └── ic_launcher_foreground.png
│ │ │ ├── mipmap-xxxhdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ ├── ic_launcher_round.png
│ │ │ │ └── ic_launcher_foreground.png
│ │ │ ├── values-night
│ │ │ │ ├── colors.xml
│ │ │ │ └── styles.xml
│ │ │ ├── drawable-hdpi
│ │ │ │ └── ic_notification.png
│ │ │ ├── drawable-mdpi
│ │ │ │ └── ic_notification.png
│ │ │ ├── drawable-xhdpi
│ │ │ │ └── ic_notification.png
│ │ │ ├── values-land
│ │ │ │ └── dimens.xml
│ │ │ ├── drawable-xxhdpi
│ │ │ │ └── ic_notification.png
│ │ │ ├── values
│ │ │ │ ├── config.xml
│ │ │ │ ├── colors.xml
│ │ │ │ ├── log_colors.xml
│ │ │ │ ├── dimens.xml
│ │ │ │ ├── constants.xml
│ │ │ │ └── styles.xml
│ │ │ ├── mipmap-anydpi-v26
│ │ │ │ ├── ic_launcher.xml
│ │ │ │ └── ic_launcher_round.xml
│ │ │ ├── values-notnight
│ │ │ │ └── styles.xml
│ │ │ ├── layout
│ │ │ │ ├── debug_logs.xml
│ │ │ │ ├── view_settings_card_reminder.xml
│ │ │ │ ├── activity_main.xml
│ │ │ │ ├── view_settings_card_sound.xml
│ │ │ │ ├── fragment_sound.xml
│ │ │ │ ├── view_settings_card_scheduler.xml
│ │ │ │ ├── view_settings_card_vibration.xml
│ │ │ │ ├── fragment_vibration.xml
│ │ │ │ ├── debug_logs_list_item.xml
│ │ │ │ └── item_contribute.xml
│ │ │ └── menu
│ │ │ │ └── menu_settings_fragment.xml
│ │ └── java
│ │ │ └── com
│ │ │ ├── app
│ │ │ └── missednotificationsreminder
│ │ │ │ ├── payment
│ │ │ │ ├── PurchaseInteractor.kt
│ │ │ │ ├── billing
│ │ │ │ │ └── domain
│ │ │ │ │ │ ├── entities
│ │ │ │ │ │ ├── SkuType.kt
│ │ │ │ │ │ ├── PurchaseState.kt
│ │ │ │ │ │ ├── SkuDetails.kt
│ │ │ │ │ │ ├── Purchase.kt
│ │ │ │ │ │ ├── ConsumeResult.kt
│ │ │ │ │ │ └── BillingErrorCodes.kt
│ │ │ │ │ │ └── repository
│ │ │ │ │ │ └── PurchaseRepository.kt
│ │ │ │ ├── model
│ │ │ │ │ └── Purchase.kt
│ │ │ │ ├── di
│ │ │ │ │ ├── qualifiers
│ │ │ │ │ │ └── AvailableSkus.kt
│ │ │ │ │ └── PurchaseDataModule.kt
│ │ │ │ ├── PurchaseItem.kt
│ │ │ │ ├── PurchaseItemViewState.kt
│ │ │ │ ├── PurchaseViewEffect.kt
│ │ │ │ ├── PurchaseItemViewModel.kt
│ │ │ │ ├── PurchaseViewState.kt
│ │ │ │ └── PurchaseViewStatePartialChanges.kt
│ │ │ │ ├── util
│ │ │ │ ├── event
│ │ │ │ │ ├── Event.java
│ │ │ │ │ └── FlowEventBus.kt
│ │ │ │ ├── loadingstate
│ │ │ │ │ ├── LoadingState.kt
│ │ │ │ │ ├── BasicLoadingStateManager.kt
│ │ │ │ │ ├── ManagesLoadingState.kt
│ │ │ │ │ └── HasLoadingStateManager.kt
│ │ │ │ ├── resources
│ │ │ │ │ └── ResourcesUtils.kt
│ │ │ │ ├── livedata
│ │ │ │ │ └── NonNullMutableLiveData.kt
│ │ │ │ ├── flow
│ │ │ │ │ ├── FlowExtensions.kt
│ │ │ │ │ ├── FlowAmb.kt
│ │ │ │ │ └── FlowBufferTimeout.kt
│ │ │ │ ├── coroutines
│ │ │ │ │ └── CoroutinesExtensions.kt
│ │ │ │ ├── ViewUtils.kt
│ │ │ │ └── BatteryUtils.java
│ │ │ │ ├── settings
│ │ │ │ ├── sound
│ │ │ │ │ ├── SoundViewState.kt
│ │ │ │ │ └── SoundViewStatePartialChanges.kt
│ │ │ │ ├── di
│ │ │ │ │ └── qualifiers
│ │ │ │ │ │ ├── Vibrate.kt
│ │ │ │ │ │ ├── ForceWakeLock.kt
│ │ │ │ │ │ ├── SchedulerMode.kt
│ │ │ │ │ │ ├── RateAppClicked.kt
│ │ │ │ │ │ ├── ReminderEnabled.kt
│ │ │ │ │ │ ├── ReminderInterval.kt
│ │ │ │ │ │ ├── ReminderRepeats.kt
│ │ │ │ │ │ ├── ReminderRingtone.kt
│ │ │ │ │ │ ├── SchedulerEnabled.kt
│ │ │ │ │ │ ├── VibrationPattern.kt
│ │ │ │ │ │ ├── ReminderRepeatsMax.kt
│ │ │ │ │ │ ├── ReminderRepeatsMin.kt
│ │ │ │ │ │ ├── RespectPhoneCalls.kt
│ │ │ │ │ │ ├── RespectRingerMode.kt
│ │ │ │ │ │ ├── SchedulerRangeEnd.kt
│ │ │ │ │ │ ├── SchedulerRangeMax.kt
│ │ │ │ │ │ ├── SchedulerRangeMin.kt
│ │ │ │ │ │ ├── LimitReminderRepeats.kt
│ │ │ │ │ │ ├── RemindWhenScreenIsOn.kt
│ │ │ │ │ │ ├── ReminderIntervalMax.kt
│ │ │ │ │ │ ├── ReminderIntervalMin.kt
│ │ │ │ │ │ ├── ReminderSessionsCount.kt
│ │ │ │ │ │ ├── SchedulerRangeBegin.kt
│ │ │ │ │ │ ├── SelectedApplications.kt
│ │ │ │ │ │ ├── ReminderIntervalDefault.kt
│ │ │ │ │ │ ├── ReminderRepeatsDefault.kt
│ │ │ │ │ │ ├── VibrationPatternDefault.kt
│ │ │ │ │ │ ├── CreateDismissNotification.kt
│ │ │ │ │ │ ├── SchedulerRangeDefaultBegin.kt
│ │ │ │ │ │ ├── SchedulerRangeDefaultEnd.kt
│ │ │ │ │ │ ├── IgnorePersistentNotifications.kt
│ │ │ │ │ │ └── CreateDismissNotificationImmediately.kt
│ │ │ │ ├── applicationssettings
│ │ │ │ │ ├── ApplicationsSettingsViewState.kt
│ │ │ │ │ └── ApplicationsSettingsViewStatePartialChanges.kt
│ │ │ │ ├── vibration
│ │ │ │ │ ├── VibrationViewState.kt
│ │ │ │ │ ├── VibrationViewStatePartialChanges.kt
│ │ │ │ │ └── VibrationFragment.kt
│ │ │ │ ├── applicationselection
│ │ │ │ │ ├── ApplicationItemViewStatePartialChanges.kt
│ │ │ │ │ ├── ApplicationItemViewState.kt
│ │ │ │ │ ├── data
│ │ │ │ │ │ └── model
│ │ │ │ │ │ │ └── util
│ │ │ │ │ │ │ └── ApplicationIconHandler.kt
│ │ │ │ │ └── ApplicationItemViewModel.kt
│ │ │ │ ├── SettingsViewState.kt
│ │ │ │ ├── scheduler
│ │ │ │ │ ├── SchedulerViewStatePartialChanges.kt
│ │ │ │ │ └── SchedulerViewState.kt
│ │ │ │ └── reminder
│ │ │ │ │ └── ReminderFragment.kt
│ │ │ │ ├── data
│ │ │ │ ├── source
│ │ │ │ │ ├── ResourceDataSource.kt
│ │ │ │ │ └── DefaultResourceDataSource.kt
│ │ │ │ └── model
│ │ │ │ │ └── NightMode.kt
│ │ │ │ ├── binding
│ │ │ │ ├── model
│ │ │ │ │ ├── ViewStatePartialChanges.kt
│ │ │ │ │ ├── BaseViewModel.java
│ │ │ │ │ ├── BaseViewStateViewEffectModel.kt
│ │ │ │ │ ├── Event.kt
│ │ │ │ │ └── BaseViewStateModel.kt
│ │ │ │ └── util
│ │ │ │ │ └── BindingConversionUtils.java
│ │ │ │ ├── di
│ │ │ │ ├── qualifiers
│ │ │ │ │ ├── ActivityScope.kt
│ │ │ │ │ ├── FragmentScope.kt
│ │ │ │ │ ├── IsInstrumentationTest.kt
│ │ │ │ │ ├── MainThreadScheduler.java
│ │ │ │ │ ├── ForActivity.java
│ │ │ │ │ └── ForApplication.java
│ │ │ │ ├── Injector.kt
│ │ │ │ └── ViewModelFactory.kt
│ │ │ │ ├── ui
│ │ │ │ ├── UiModule.kt
│ │ │ │ ├── fragment
│ │ │ │ │ ├── common
│ │ │ │ │ │ ├── ActivityStateAccessor.java
│ │ │ │ │ │ └── CommonFragmentWithViewBinding.kt
│ │ │ │ │ └── dialog
│ │ │ │ │ │ └── AlertDialogFragment.kt
│ │ │ │ ├── AppContainer.kt
│ │ │ │ ├── widget
│ │ │ │ │ ├── recyclerview
│ │ │ │ │ │ ├── LifecycleAdapterWithViewEffect.kt
│ │ │ │ │ │ ├── LifecycleAdapter.kt
│ │ │ │ │ │ └── LifecycleViewHolder.kt
│ │ │ │ │ ├── misc
│ │ │ │ │ │ └── BetterViewAnimator.java
│ │ │ │ │ └── dialog
│ │ │ │ │ │ └── LifecycleAlertDialog.kt
│ │ │ │ ├── ActivityHierarchyServer.java
│ │ │ │ └── activity
│ │ │ │ │ └── common
│ │ │ │ │ └── CommonActivityLifecycleCallback.kt
│ │ │ │ ├── service
│ │ │ │ ├── event
│ │ │ │ │ ├── NotificationsUpdatedEvent.kt
│ │ │ │ │ └── RemindEvents.java
│ │ │ │ ├── util
│ │ │ │ │ └── PhoneStateUtils.java
│ │ │ │ ├── ReminderNotificationListenerServiceInterface.kt
│ │ │ │ ├── data
│ │ │ │ │ └── model
│ │ │ │ │ │ └── NotificationData.kt
│ │ │ │ └── RemindJob.kt
│ │ │ │ ├── CustomApplication.kt
│ │ │ │ ├── ApplicationComponent.kt
│ │ │ │ ├── ApplicationModule.kt
│ │ │ │ └── CustomApplicationBase.kt
│ │ │ └── jakewharton
│ │ │ └── u2020
│ │ │ └── ui
│ │ │ └── misc
│ │ │ └── BindableAdapter.java
│ ├── debug
│ │ ├── res
│ │ │ ├── values
│ │ │ │ ├── colors.xml
│ │ │ │ ├── strings.xml
│ │ │ │ ├── debug_attrs.xml
│ │ │ │ ├── titles.xml
│ │ │ │ └── debug_strings.xml
│ │ │ └── layout
│ │ │ │ ├── debug_drawer_contextual_action.xml
│ │ │ │ ├── debug_drawer_network_endpoint.xml
│ │ │ │ ├── debug_drawer_network_proxy.xml
│ │ │ │ ├── bugreport_view.xml
│ │ │ │ └── debug_activity_frame.xml
│ │ ├── java
│ │ │ └── com
│ │ │ │ ├── app
│ │ │ │ └── missednotificationsreminder
│ │ │ │ │ ├── data
│ │ │ │ │ └── DebugDataModule.kt
│ │ │ │ │ ├── ui
│ │ │ │ │ ├── DebugUiModule.kt
│ │ │ │ │ └── UiModuleExt.kt
│ │ │ │ │ ├── payment
│ │ │ │ │ └── di
│ │ │ │ │ │ └── PurchaseDataModuleExt.kt
│ │ │ │ │ └── ApplicationModuleExt.kt
│ │ │ │ └── jakewharton
│ │ │ │ └── u2020
│ │ │ │ ├── data
│ │ │ │ ├── AnimationSpeed.java
│ │ │ │ ├── ScalpelEnabled.java
│ │ │ │ ├── PicassoDebugging.java
│ │ │ │ ├── PixelGridEnabled.java
│ │ │ │ ├── SeenDebugDrawer.java
│ │ │ │ ├── PixelRatioEnabled.java
│ │ │ │ └── ScalpelWireframeEnabled.java
│ │ │ │ ├── ui
│ │ │ │ ├── debug
│ │ │ │ │ ├── DebugViewModule.kt
│ │ │ │ │ ├── DrawerLayoutImpl.java
│ │ │ │ │ ├── AnimationSpeedAdapter.java
│ │ │ │ │ └── HierarchyTreeChangeListener.java
│ │ │ │ ├── misc
│ │ │ │ │ └── EmptyTextWatcher.java
│ │ │ │ └── bugreport
│ │ │ │ │ ├── BugReportDialog.java
│ │ │ │ │ └── BugReportView.java
│ │ │ │ └── util
│ │ │ │ ├── Strings.java
│ │ │ │ ├── EmptyActivityLifecycleCallbacks.java
│ │ │ │ └── Intents.java
│ │ └── AndroidManifest.xml
│ ├── release
│ │ ├── java
│ │ │ └── com
│ │ │ │ └── app
│ │ │ │ └── missednotificationsreminder
│ │ │ │ ├── ApplicationModuleExt.kt
│ │ │ │ ├── ui
│ │ │ │ └── UiModuleExt.kt
│ │ │ │ └── payment
│ │ │ │ └── di
│ │ │ │ └── PurchaseDataModuleExt.kt
│ │ └── res
│ │ │ └── values
│ │ │ └── titles.xml
│ ├── notificationListener
│ │ ├── res
│ │ │ ├── values-ru
│ │ │ │ └── strings.xml
│ │ │ ├── values-bg
│ │ │ │ └── strings.xml
│ │ │ ├── values-uk
│ │ │ │ └── strings.xml
│ │ │ ├── values-nl
│ │ │ │ └── strings.xml
│ │ │ ├── values
│ │ │ │ └── strings.xml
│ │ │ └── values-de
│ │ │ │ └── strings.xml
│ │ ├── AndroidManifest.xml
│ │ └── java
│ │ │ └── com
│ │ │ └── app
│ │ │ └── missednotificationsreminder
│ │ │ └── service
│ │ │ └── util
│ │ │ └── ReminderNotificationListenerServiceUtils.java
│ ├── accessibilityV14NoProprietaryDebug
│ │ └── res
│ │ │ └── values
│ │ │ └── titles.xml
│ ├── accessibilityV14ProprietaryDebug
│ │ └── res
│ │ │ └── values
│ │ │ └── titles.xml
│ ├── accessibilityV27NoProprietaryDebug
│ │ └── res
│ │ │ └── values
│ │ │ └── titles.xml
│ ├── accessibilityV27ProprietaryDebug
│ │ └── res
│ │ │ └── values
│ │ │ └── titles.xml
│ ├── notificationListenerV18NoProprietaryDebug
│ │ └── res
│ │ │ └── values
│ │ │ └── titles.xml
│ ├── notificationListenerV18ProprietaryDebug
│ │ └── res
│ │ │ └── values
│ │ │ └── titles.xml
│ ├── notificationListenerV27
│ │ └── AndroidManifest.xml
│ ├── test
│ │ └── java
│ │ │ └── com
│ │ │ └── app
│ │ │ └── missednotificationsreminder
│ │ │ └── ExampleUnitTest.java
│ ├── proprietary
│ │ └── java
│ │ │ └── com
│ │ │ └── app
│ │ │ └── missednotificationsreminder
│ │ │ └── payment
│ │ │ ├── billing
│ │ │ └── data
│ │ │ │ ├── mappers
│ │ │ │ ├── SkuTypeMappers.kt
│ │ │ │ ├── PurchaseMappers.kt
│ │ │ │ └── SkuDetailsMappers.kt
│ │ │ │ ├── utls
│ │ │ │ ├── BillingErrorUtils.kt
│ │ │ │ └── ResultWrapperUtils.kt
│ │ │ │ └── source
│ │ │ │ └── remote
│ │ │ │ └── BillingOperationException.kt
│ │ │ └── di
│ │ │ └── PurchaseRepositoryModule.kt
│ ├── accessibility
│ │ ├── res
│ │ │ ├── xml
│ │ │ │ └── notifications_service_config.xml
│ │ │ ├── values-bg
│ │ │ │ └── strings.xml
│ │ │ ├── values-ru
│ │ │ │ └── strings.xml
│ │ │ ├── values-uk
│ │ │ │ └── strings.xml
│ │ │ ├── values-nl
│ │ │ │ └── strings.xml
│ │ │ ├── values
│ │ │ │ └── strings.xml
│ │ │ ├── values-ja
│ │ │ │ └── strings.xml
│ │ │ └── values-de
│ │ │ │ └── strings.xml
│ │ └── AndroidManifest.xml
│ ├── noProprietary
│ │ └── java
│ │ │ └── com
│ │ │ └── app
│ │ │ └── missednotificationsreminder
│ │ │ └── payment
│ │ │ ├── di
│ │ │ └── PurchaseRepositoryModule.kt
│ │ │ └── billing
│ │ │ └── data
│ │ │ └── PurchaseRepositoryImpl.kt
│ └── androidTest
│ │ └── java
│ │ └── com
│ │ └── app
│ │ └── missednotificationsreminder
│ │ └── ApplicationTestRunner.java
├── lint.xml
└── proguard-rules.pro
├── settings.gradle.kts
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── .gitignore
├── gradle.properties
└── README.md
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/settings.gradle.kts:
--------------------------------------------------------------------------------
1 | includeBuild("buildSrcIncluded")
2 | include(":app")
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/httpdispatch/MissedNotificationsReminder/HEAD/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/app/src/main/res/xml/filepaths.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/app/src/debug/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | #FF5722
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/httpdispatch/MissedNotificationsReminder/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/httpdispatch/MissedNotificationsReminder/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/httpdispatch/MissedNotificationsReminder/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/httpdispatch/MissedNotificationsReminder/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .gradle
2 | local.properties
3 | .idea
4 | *.iml
5 | .DS_Store
6 | build
7 | /shots
8 | /captures
9 | keystore.properties
10 | release.keystore.jks
11 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/httpdispatch/MissedNotificationsReminder/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/values-night/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #33ffffff
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/ic_notification.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/httpdispatch/MissedNotificationsReminder/HEAD/app/src/main/res/drawable-hdpi/ic_notification.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-mdpi/ic_notification.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/httpdispatch/MissedNotificationsReminder/HEAD/app/src/main/res/drawable-mdpi/ic_notification.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/ic_notification.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/httpdispatch/MissedNotificationsReminder/HEAD/app/src/main/res/drawable-xhdpi/ic_notification.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/httpdispatch/MissedNotificationsReminder/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/httpdispatch/MissedNotificationsReminder/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/httpdispatch/MissedNotificationsReminder/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/values-land/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 3
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_notification.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/httpdispatch/MissedNotificationsReminder/HEAD/app/src/main/res/drawable-xxhdpi/ic_notification.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/httpdispatch/MissedNotificationsReminder/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/httpdispatch/MissedNotificationsReminder/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/httpdispatch/MissedNotificationsReminder/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/httpdispatch/MissedNotificationsReminder/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/httpdispatch/MissedNotificationsReminder/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/httpdispatch/MissedNotificationsReminder/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/httpdispatch/MissedNotificationsReminder/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/app/src/debug/java/com/app/missednotificationsreminder/data/DebugDataModule.kt:
--------------------------------------------------------------------------------
1 | package com.app.missednotificationsreminder.data
2 |
3 | import dagger.Module
4 |
5 | @Module
6 | class DebugDataModule
--------------------------------------------------------------------------------
/app/src/main/java/com/app/missednotificationsreminder/payment/PurchaseInteractor.kt:
--------------------------------------------------------------------------------
1 | package com.app.missednotificationsreminder.payment
2 |
3 | interface PurchaseInteractor {
4 | fun purchase()
5 | }
--------------------------------------------------------------------------------
/app/src/main/res/values/config.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/release/java/com/app/missednotificationsreminder/ApplicationModuleExt.kt:
--------------------------------------------------------------------------------
1 | package com.app.missednotificationsreminder
2 |
3 | import dagger.Module
4 |
5 | @Module
6 | class ApplicationModuleExt
--------------------------------------------------------------------------------
/app/src/main/java/com/app/missednotificationsreminder/util/event/Event.java:
--------------------------------------------------------------------------------
1 | package com.app.missednotificationsreminder.util.event;
2 |
3 | /**
4 | * General event interface
5 | */
6 | public interface Event {
7 | }
8 |
--------------------------------------------------------------------------------
/app/src/main/java/com/app/missednotificationsreminder/settings/sound/SoundViewState.kt:
--------------------------------------------------------------------------------
1 | package com.app.missednotificationsreminder.settings.sound
2 |
3 | data class SoundViewState(val ringtone: String, val ringtoneName: String)
--------------------------------------------------------------------------------
/app/src/debug/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | There are no applications available to handle this action.
5 |
6 |
7 |
--------------------------------------------------------------------------------
/app/src/debug/res/values/debug_attrs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/app/src/debug/res/values/titles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Missed Notifications Reminder Leaks
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/java/com/app/missednotificationsreminder/payment/billing/domain/entities/SkuType.kt:
--------------------------------------------------------------------------------
1 | package com.app.missednotificationsreminder.payment.billing.domain.entities
2 |
3 | enum class SkuType {
4 | INAPP,
5 | SUBS,
6 | }
7 |
--------------------------------------------------------------------------------
/app/src/main/java/com/app/missednotificationsreminder/data/source/ResourceDataSource.kt:
--------------------------------------------------------------------------------
1 | package com.app.missednotificationsreminder.data.source
2 |
3 | interface ResourceDataSource {
4 | fun getString(id: Int, vararg args: Any): String
5 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/app/missednotificationsreminder/util/loadingstate/LoadingState.kt:
--------------------------------------------------------------------------------
1 | package com.app.missednotificationsreminder.util.loadingstate
2 |
3 | data class LoadingState(val loading: Boolean = false,
4 | val status: String = "")
--------------------------------------------------------------------------------
/app/src/main/java/com/app/missednotificationsreminder/binding/model/ViewStatePartialChanges.kt:
--------------------------------------------------------------------------------
1 | package com.app.missednotificationsreminder.binding.model
2 |
3 | interface ViewStatePartialChanges {
4 | fun reduce(previousState: VIEW_STATE): VIEW_STATE
5 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/app/missednotificationsreminder/di/qualifiers/ActivityScope.kt:
--------------------------------------------------------------------------------
1 | package com.app.missednotificationsreminder.di.qualifiers
2 |
3 | import javax.inject.Scope
4 |
5 | @Scope
6 | @Retention(AnnotationRetention.RUNTIME)
7 | annotation class ActivityScope
--------------------------------------------------------------------------------
/app/src/main/java/com/app/missednotificationsreminder/di/qualifiers/FragmentScope.kt:
--------------------------------------------------------------------------------
1 | package com.app.missednotificationsreminder.di.qualifiers
2 |
3 | import javax.inject.Scope
4 |
5 | @Scope
6 | @Retention(AnnotationRetention.RUNTIME)
7 | annotation class FragmentScope
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #ffffff
4 | #08332f
5 | #33000000
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/java/com/app/missednotificationsreminder/payment/billing/domain/entities/PurchaseState.kt:
--------------------------------------------------------------------------------
1 | package com.app.missednotificationsreminder.payment.billing.domain.entities
2 |
3 | enum class PurchaseState {
4 | UNSPECIFIED_STATE,
5 | PURCHASED,
6 | PENDING,
7 | }
8 |
--------------------------------------------------------------------------------
/app/src/main/java/com/app/missednotificationsreminder/settings/di/qualifiers/Vibrate.kt:
--------------------------------------------------------------------------------
1 | package com.app.missednotificationsreminder.settings.di.qualifiers
2 |
3 | import javax.inject.Qualifier
4 |
5 | @Qualifier
6 | @Retention(AnnotationRetention.RUNTIME)
7 | annotation class Vibrate
--------------------------------------------------------------------------------
/app/src/main/java/com/app/missednotificationsreminder/payment/model/Purchase.kt:
--------------------------------------------------------------------------------
1 | package com.app.missednotificationsreminder.payment.model
2 |
3 | import com.squareup.moshi.JsonClass
4 |
5 | @JsonClass(generateAdapter = true)
6 | data class Purchase(val sku: String, val price: String)
7 |
--------------------------------------------------------------------------------
/app/src/main/java/com/app/missednotificationsreminder/di/qualifiers/IsInstrumentationTest.kt:
--------------------------------------------------------------------------------
1 | package com.app.missednotificationsreminder.di.qualifiers
2 |
3 | import javax.inject.Qualifier
4 |
5 | @Qualifier
6 | @Retention(AnnotationRetention.RUNTIME)
7 | annotation class IsInstrumentationTest
--------------------------------------------------------------------------------
/app/src/main/java/com/app/missednotificationsreminder/payment/di/qualifiers/AvailableSkus.kt:
--------------------------------------------------------------------------------
1 | package com.app.missednotificationsreminder.payment.di.qualifiers
2 |
3 | import javax.inject.Qualifier
4 |
5 | @Qualifier
6 | @Retention(AnnotationRetention.RUNTIME)
7 | annotation class AvailableSkus
--------------------------------------------------------------------------------
/app/src/main/java/com/app/missednotificationsreminder/settings/di/qualifiers/ForceWakeLock.kt:
--------------------------------------------------------------------------------
1 | package com.app.missednotificationsreminder.settings.di.qualifiers
2 |
3 | import javax.inject.Qualifier
4 |
5 | @Qualifier
6 | @Retention(AnnotationRetention.RUNTIME)
7 | annotation class ForceWakeLock
--------------------------------------------------------------------------------
/app/src/main/java/com/app/missednotificationsreminder/settings/di/qualifiers/SchedulerMode.kt:
--------------------------------------------------------------------------------
1 | package com.app.missednotificationsreminder.settings.di.qualifiers
2 |
3 | import javax.inject.Qualifier
4 |
5 | @Qualifier
6 | @Retention(AnnotationRetention.RUNTIME)
7 | annotation class SchedulerMode
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Thu Apr 15 10:22:03 EEST 2021
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-bin.zip
7 |
--------------------------------------------------------------------------------
/app/src/main/java/com/app/missednotificationsreminder/settings/di/qualifiers/RateAppClicked.kt:
--------------------------------------------------------------------------------
1 | package com.app.missednotificationsreminder.settings.di.qualifiers
2 |
3 | import javax.inject.Qualifier
4 |
5 | @Qualifier
6 | @Retention(AnnotationRetention.RUNTIME)
7 | annotation class RateAppClicked
--------------------------------------------------------------------------------
/app/src/main/java/com/app/missednotificationsreminder/settings/di/qualifiers/ReminderEnabled.kt:
--------------------------------------------------------------------------------
1 | package com.app.missednotificationsreminder.settings.di.qualifiers
2 |
3 | import javax.inject.Qualifier
4 |
5 | @Qualifier
6 | @Retention(AnnotationRetention.RUNTIME)
7 | annotation class ReminderEnabled
--------------------------------------------------------------------------------
/app/src/main/java/com/app/missednotificationsreminder/settings/di/qualifiers/ReminderInterval.kt:
--------------------------------------------------------------------------------
1 | package com.app.missednotificationsreminder.settings.di.qualifiers
2 |
3 | import javax.inject.Qualifier
4 |
5 | @Qualifier
6 | @Retention(AnnotationRetention.RUNTIME)
7 | annotation class ReminderInterval
--------------------------------------------------------------------------------
/app/src/main/java/com/app/missednotificationsreminder/settings/di/qualifiers/ReminderRepeats.kt:
--------------------------------------------------------------------------------
1 | package com.app.missednotificationsreminder.settings.di.qualifiers
2 |
3 | import javax.inject.Qualifier
4 |
5 | @Qualifier
6 | @Retention(AnnotationRetention.RUNTIME)
7 | annotation class ReminderRepeats
--------------------------------------------------------------------------------
/app/src/main/java/com/app/missednotificationsreminder/settings/di/qualifiers/ReminderRingtone.kt:
--------------------------------------------------------------------------------
1 | package com.app.missednotificationsreminder.settings.di.qualifiers
2 |
3 | import javax.inject.Qualifier
4 |
5 | @Qualifier
6 | @Retention(AnnotationRetention.RUNTIME)
7 | annotation class ReminderRingtone
--------------------------------------------------------------------------------
/app/src/main/java/com/app/missednotificationsreminder/settings/di/qualifiers/SchedulerEnabled.kt:
--------------------------------------------------------------------------------
1 | package com.app.missednotificationsreminder.settings.di.qualifiers
2 |
3 | import javax.inject.Qualifier
4 |
5 | @Qualifier
6 | @Retention(AnnotationRetention.RUNTIME)
7 | annotation class SchedulerEnabled
--------------------------------------------------------------------------------
/app/src/main/java/com/app/missednotificationsreminder/settings/di/qualifiers/VibrationPattern.kt:
--------------------------------------------------------------------------------
1 | package com.app.missednotificationsreminder.settings.di.qualifiers
2 |
3 | import javax.inject.Qualifier
4 |
5 | @Qualifier
6 | @Retention(AnnotationRetention.RUNTIME)
7 | annotation class VibrationPattern
--------------------------------------------------------------------------------
/app/src/main/java/com/app/missednotificationsreminder/settings/di/qualifiers/ReminderRepeatsMax.kt:
--------------------------------------------------------------------------------
1 | package com.app.missednotificationsreminder.settings.di.qualifiers
2 |
3 | import javax.inject.Qualifier
4 |
5 | @Qualifier
6 | @Retention(AnnotationRetention.RUNTIME)
7 | annotation class ReminderRepeatsMax
--------------------------------------------------------------------------------
/app/src/main/java/com/app/missednotificationsreminder/settings/di/qualifiers/ReminderRepeatsMin.kt:
--------------------------------------------------------------------------------
1 | package com.app.missednotificationsreminder.settings.di.qualifiers
2 |
3 | import javax.inject.Qualifier
4 |
5 | @Qualifier
6 | @Retention(AnnotationRetention.RUNTIME)
7 | annotation class ReminderRepeatsMin
--------------------------------------------------------------------------------
/app/src/main/java/com/app/missednotificationsreminder/settings/di/qualifiers/RespectPhoneCalls.kt:
--------------------------------------------------------------------------------
1 | package com.app.missednotificationsreminder.settings.di.qualifiers
2 |
3 | import javax.inject.Qualifier
4 |
5 | @Qualifier
6 | @Retention(AnnotationRetention.RUNTIME)
7 | annotation class RespectPhoneCalls
--------------------------------------------------------------------------------
/app/src/main/java/com/app/missednotificationsreminder/settings/di/qualifiers/RespectRingerMode.kt:
--------------------------------------------------------------------------------
1 | package com.app.missednotificationsreminder.settings.di.qualifiers
2 |
3 | import javax.inject.Qualifier
4 |
5 | @Qualifier
6 | @Retention(AnnotationRetention.RUNTIME)
7 | annotation class RespectRingerMode
--------------------------------------------------------------------------------
/app/src/main/java/com/app/missednotificationsreminder/settings/di/qualifiers/SchedulerRangeEnd.kt:
--------------------------------------------------------------------------------
1 | package com.app.missednotificationsreminder.settings.di.qualifiers
2 |
3 | import javax.inject.Qualifier
4 |
5 | @Qualifier
6 | @Retention(AnnotationRetention.RUNTIME)
7 | annotation class SchedulerRangeEnd
--------------------------------------------------------------------------------
/app/src/main/java/com/app/missednotificationsreminder/settings/di/qualifiers/SchedulerRangeMax.kt:
--------------------------------------------------------------------------------
1 | package com.app.missednotificationsreminder.settings.di.qualifiers
2 |
3 | import javax.inject.Qualifier
4 |
5 | @Qualifier
6 | @Retention(AnnotationRetention.RUNTIME)
7 | annotation class SchedulerRangeMax
--------------------------------------------------------------------------------
/app/src/main/java/com/app/missednotificationsreminder/settings/di/qualifiers/SchedulerRangeMin.kt:
--------------------------------------------------------------------------------
1 | package com.app.missednotificationsreminder.settings.di.qualifiers
2 |
3 | import javax.inject.Qualifier
4 |
5 | @Qualifier
6 | @Retention(AnnotationRetention.RUNTIME)
7 | annotation class SchedulerRangeMin
--------------------------------------------------------------------------------
/app/src/release/res/values/titles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Missed Notifications Reminder
5 | Missed Notifications Reminder
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/java/com/app/missednotificationsreminder/settings/di/qualifiers/LimitReminderRepeats.kt:
--------------------------------------------------------------------------------
1 | package com.app.missednotificationsreminder.settings.di.qualifiers
2 |
3 | import javax.inject.Qualifier
4 |
5 | @Qualifier
6 | @Retention(AnnotationRetention.RUNTIME)
7 | annotation class LimitReminderRepeats
--------------------------------------------------------------------------------
/app/src/main/java/com/app/missednotificationsreminder/settings/di/qualifiers/RemindWhenScreenIsOn.kt:
--------------------------------------------------------------------------------
1 | package com.app.missednotificationsreminder.settings.di.qualifiers
2 |
3 | import javax.inject.Qualifier
4 |
5 | @Qualifier
6 | @Retention(AnnotationRetention.RUNTIME)
7 | annotation class RemindWhenScreenIsOn
--------------------------------------------------------------------------------
/app/src/main/java/com/app/missednotificationsreminder/settings/di/qualifiers/ReminderIntervalMax.kt:
--------------------------------------------------------------------------------
1 | package com.app.missednotificationsreminder.settings.di.qualifiers
2 |
3 | import javax.inject.Qualifier
4 |
5 | @Qualifier
6 | @Retention(AnnotationRetention.RUNTIME)
7 | annotation class ReminderIntervalMax
--------------------------------------------------------------------------------
/app/src/main/java/com/app/missednotificationsreminder/settings/di/qualifiers/ReminderIntervalMin.kt:
--------------------------------------------------------------------------------
1 | package com.app.missednotificationsreminder.settings.di.qualifiers
2 |
3 | import javax.inject.Qualifier
4 |
5 | @Qualifier
6 | @Retention(AnnotationRetention.RUNTIME)
7 | annotation class ReminderIntervalMin
--------------------------------------------------------------------------------
/app/src/main/java/com/app/missednotificationsreminder/settings/di/qualifiers/ReminderSessionsCount.kt:
--------------------------------------------------------------------------------
1 | package com.app.missednotificationsreminder.settings.di.qualifiers
2 |
3 | import javax.inject.Qualifier
4 |
5 | @Qualifier
6 | @Retention(AnnotationRetention.RUNTIME)
7 | annotation class ReminderSessionsCount
--------------------------------------------------------------------------------
/app/src/main/java/com/app/missednotificationsreminder/settings/di/qualifiers/SchedulerRangeBegin.kt:
--------------------------------------------------------------------------------
1 | package com.app.missednotificationsreminder.settings.di.qualifiers
2 |
3 | import javax.inject.Qualifier
4 |
5 | @Qualifier
6 | @Retention(AnnotationRetention.RUNTIME)
7 | annotation class SchedulerRangeBegin
--------------------------------------------------------------------------------
/app/src/main/java/com/app/missednotificationsreminder/settings/di/qualifiers/SelectedApplications.kt:
--------------------------------------------------------------------------------
1 | package com.app.missednotificationsreminder.settings.di.qualifiers
2 |
3 | import javax.inject.Qualifier
4 |
5 | @Qualifier
6 | @Retention(AnnotationRetention.RUNTIME)
7 | annotation class SelectedApplications
--------------------------------------------------------------------------------
/app/src/main/java/com/app/missednotificationsreminder/settings/di/qualifiers/ReminderIntervalDefault.kt:
--------------------------------------------------------------------------------
1 | package com.app.missednotificationsreminder.settings.di.qualifiers
2 |
3 | import javax.inject.Qualifier
4 |
5 | @Qualifier
6 | @Retention(AnnotationRetention.RUNTIME)
7 | annotation class ReminderIntervalDefault
--------------------------------------------------------------------------------
/app/src/main/java/com/app/missednotificationsreminder/settings/di/qualifiers/ReminderRepeatsDefault.kt:
--------------------------------------------------------------------------------
1 | package com.app.missednotificationsreminder.settings.di.qualifiers
2 |
3 | import javax.inject.Qualifier
4 |
5 | @Qualifier
6 | @Retention(AnnotationRetention.RUNTIME)
7 | annotation class ReminderRepeatsDefault
--------------------------------------------------------------------------------
/app/src/main/java/com/app/missednotificationsreminder/settings/di/qualifiers/VibrationPatternDefault.kt:
--------------------------------------------------------------------------------
1 | package com.app.missednotificationsreminder.settings.di.qualifiers
2 |
3 | import javax.inject.Qualifier
4 |
5 | @Qualifier
6 | @Retention(AnnotationRetention.RUNTIME)
7 | annotation class VibrationPatternDefault
--------------------------------------------------------------------------------
/app/src/main/java/com/app/missednotificationsreminder/util/loadingstate/BasicLoadingStateManager.kt:
--------------------------------------------------------------------------------
1 | package com.app.missednotificationsreminder.util.loadingstate
2 |
3 | class BasicLoadingStateManager() : LoadingStateManager() {
4 | @Volatile
5 | override var loadingState: LoadingState = LoadingState()
6 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/app/missednotificationsreminder/settings/di/qualifiers/CreateDismissNotification.kt:
--------------------------------------------------------------------------------
1 | package com.app.missednotificationsreminder.settings.di.qualifiers
2 |
3 | import javax.inject.Qualifier
4 |
5 | @Qualifier
6 | @Retention(AnnotationRetention.RUNTIME)
7 | annotation class CreateDismissNotification
--------------------------------------------------------------------------------
/app/src/main/java/com/app/missednotificationsreminder/settings/di/qualifiers/SchedulerRangeDefaultBegin.kt:
--------------------------------------------------------------------------------
1 | package com.app.missednotificationsreminder.settings.di.qualifiers
2 |
3 | import javax.inject.Qualifier
4 |
5 | @Qualifier
6 | @Retention(AnnotationRetention.RUNTIME)
7 | annotation class SchedulerRangeDefaultBegin
--------------------------------------------------------------------------------
/app/src/main/java/com/app/missednotificationsreminder/settings/di/qualifiers/SchedulerRangeDefaultEnd.kt:
--------------------------------------------------------------------------------
1 | package com.app.missednotificationsreminder.settings.di.qualifiers
2 |
3 | import javax.inject.Qualifier
4 |
5 | @Qualifier
6 | @Retention(AnnotationRetention.RUNTIME)
7 | annotation class SchedulerRangeDefaultEnd
--------------------------------------------------------------------------------
/app/src/main/java/com/app/missednotificationsreminder/settings/di/qualifiers/IgnorePersistentNotifications.kt:
--------------------------------------------------------------------------------
1 | package com.app.missednotificationsreminder.settings.di.qualifiers
2 |
3 | import javax.inject.Qualifier
4 |
5 | @Qualifier
6 | @Retention(AnnotationRetention.RUNTIME)
7 | annotation class IgnorePersistentNotifications
--------------------------------------------------------------------------------
/app/src/main/java/com/app/missednotificationsreminder/settings/di/qualifiers/CreateDismissNotificationImmediately.kt:
--------------------------------------------------------------------------------
1 | package com.app.missednotificationsreminder.settings.di.qualifiers
2 |
3 | import javax.inject.Qualifier
4 |
5 | @Qualifier
6 | @Retention(AnnotationRetention.RUNTIME)
7 | annotation class CreateDismissNotificationImmediately
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/notificationListener/res/values-ru/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Служба уведомлений отключена. Нажмите кнопку \"Управление доступом\" и отметьте приложение \"Missed Notification Reminder\". Иначе напоминания не будут работать.
4 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/notificationListener/res/values-bg/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Услугата за известия е изключена. Натиснете на \"Управление на достъпа\" и отметнете приложението \"Missed Notification Reminder\". Иначе известията няма да работят.
4 |
--------------------------------------------------------------------------------
/app/src/notificationListener/res/values-uk/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Служба повідомлень відключена. Натисніть кнопку \"Керування доступом\" и позначте додаток \"Missed Notification Reminder\". Інакше нагадування не будуть працювати.
4 |
--------------------------------------------------------------------------------
/app/src/debug/java/com/jakewharton/u2020/data/AnimationSpeed.java:
--------------------------------------------------------------------------------
1 | package com.jakewharton.u2020.data;
2 |
3 | import java.lang.annotation.Retention;
4 | import javax.inject.Qualifier;
5 |
6 | import static java.lang.annotation.RetentionPolicy.RUNTIME;
7 |
8 | @Qualifier @Retention(RUNTIME)
9 | public @interface AnimationSpeed {
10 | }
11 |
--------------------------------------------------------------------------------
/app/src/debug/java/com/jakewharton/u2020/data/ScalpelEnabled.java:
--------------------------------------------------------------------------------
1 | package com.jakewharton.u2020.data;
2 |
3 | import java.lang.annotation.Retention;
4 | import javax.inject.Qualifier;
5 |
6 | import static java.lang.annotation.RetentionPolicy.RUNTIME;
7 |
8 | @Qualifier @Retention(RUNTIME)
9 | public @interface ScalpelEnabled {
10 | }
11 |
--------------------------------------------------------------------------------
/app/src/main/java/com/app/missednotificationsreminder/payment/billing/domain/entities/SkuDetails.kt:
--------------------------------------------------------------------------------
1 | package com.app.missednotificationsreminder.payment.billing.domain.entities
2 |
3 | data class SkuDetails(
4 | val sku: String,
5 | val price: String,
6 | val priceAmountMicros: Long,
7 | val skuType: SkuType,
8 | )
9 |
--------------------------------------------------------------------------------
/app/src/debug/java/com/app/missednotificationsreminder/ui/DebugUiModule.kt:
--------------------------------------------------------------------------------
1 | package com.app.missednotificationsreminder.ui
2 |
3 | import com.jakewharton.u2020.ui.debug.DebugViewModule
4 | import dagger.Module
5 |
6 | @Module(
7 | includes = [
8 | DebugViewModule::class
9 | ]
10 | )
11 | class DebugUiModule {
12 | }
--------------------------------------------------------------------------------
/app/src/debug/java/com/jakewharton/u2020/data/PicassoDebugging.java:
--------------------------------------------------------------------------------
1 | package com.jakewharton.u2020.data;
2 |
3 | import java.lang.annotation.Retention;
4 | import javax.inject.Qualifier;
5 |
6 | import static java.lang.annotation.RetentionPolicy.RUNTIME;
7 |
8 | @Qualifier @Retention(RUNTIME)
9 | public @interface PicassoDebugging {
10 | }
11 |
--------------------------------------------------------------------------------
/app/src/debug/java/com/jakewharton/u2020/data/PixelGridEnabled.java:
--------------------------------------------------------------------------------
1 | package com.jakewharton.u2020.data;
2 |
3 | import java.lang.annotation.Retention;
4 | import javax.inject.Qualifier;
5 |
6 | import static java.lang.annotation.RetentionPolicy.RUNTIME;
7 |
8 | @Qualifier @Retention(RUNTIME)
9 | public @interface PixelGridEnabled {
10 | }
11 |
--------------------------------------------------------------------------------
/app/src/debug/java/com/jakewharton/u2020/data/SeenDebugDrawer.java:
--------------------------------------------------------------------------------
1 | package com.jakewharton.u2020.data;
2 |
3 | import java.lang.annotation.Retention;
4 | import javax.inject.Qualifier;
5 |
6 | import static java.lang.annotation.RetentionPolicy.RUNTIME;
7 |
8 | @Qualifier @Retention(RUNTIME)
9 | public @interface SeenDebugDrawer {
10 | }
11 |
--------------------------------------------------------------------------------
/app/src/main/java/com/app/missednotificationsreminder/payment/PurchaseItem.kt:
--------------------------------------------------------------------------------
1 | package com.app.missednotificationsreminder.payment
2 |
3 | import com.app.missednotificationsreminder.payment.billing.domain.entities.SkuDetails
4 |
5 | /**
6 | * The class to store purchase item information
7 | */
8 | data class PurchaseItem(val skuDetails: SkuDetails)
9 |
--------------------------------------------------------------------------------
/app/src/accessibilityV14NoProprietaryDebug/res/values/titles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Missed Notifications Reminder Av14NP Debug
5 | Missed Notifications Reminder Av14NP (D)
6 |
7 |
8 |
--------------------------------------------------------------------------------
/app/src/accessibilityV14ProprietaryDebug/res/values/titles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Missed Notifications Reminder Av14P Debug
5 | Missed Notifications Reminder Av14P (D)
6 |
7 |
8 |
--------------------------------------------------------------------------------
/app/src/accessibilityV27NoProprietaryDebug/res/values/titles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Missed Notifications Reminder Av27NP Debug
5 | Missed Notifications Reminder Av27NP (D)
6 |
7 |
8 |
--------------------------------------------------------------------------------
/app/src/accessibilityV27ProprietaryDebug/res/values/titles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Missed Notifications Reminder Av27P Debug
5 | Missed Notifications Reminder Av27P (D)
6 |
7 |
8 |
--------------------------------------------------------------------------------
/app/src/debug/java/com/jakewharton/u2020/data/PixelRatioEnabled.java:
--------------------------------------------------------------------------------
1 | package com.jakewharton.u2020.data;
2 |
3 | import java.lang.annotation.Retention;
4 | import javax.inject.Qualifier;
5 |
6 | import static java.lang.annotation.RetentionPolicy.RUNTIME;
7 |
8 | @Qualifier @Retention(RUNTIME)
9 | public @interface PixelRatioEnabled {
10 | }
11 |
--------------------------------------------------------------------------------
/app/src/main/java/com/app/missednotificationsreminder/payment/billing/domain/entities/Purchase.kt:
--------------------------------------------------------------------------------
1 | package com.app.missednotificationsreminder.payment.billing.domain.entities
2 |
3 | data class Purchase(
4 | val sku: String,
5 | val purchaseToken: String,
6 | val isAcknowledged: Boolean,
7 | val purchaseState: PurchaseState,
8 | )
9 |
--------------------------------------------------------------------------------
/app/src/notificationListener/res/values-nl/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | De meldingsdienst is momenteel uitgeschakeld.
4 | Druk op de knop \"Toegang beheren\" button en schakel \"Missed Notification Reminder\" in.
5 | Zonder dit werkt de app niet.
6 |
7 |
8 |
--------------------------------------------------------------------------------
/app/src/notificationListener/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | The notification service is currently disabled.
4 | Press \"Manage Access\" button and check \"Missed Notification Reminder\" application.
5 | Otherwise the reminder will not work.
6 |
7 |
8 |
--------------------------------------------------------------------------------
/app/src/notificationListenerV18NoProprietaryDebug/res/values/titles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Missed Notifications Reminder v18NP Debug
5 | Missed Notifications Reminder v18NP (D)
6 |
7 |
8 |
--------------------------------------------------------------------------------
/app/src/notificationListenerV18ProprietaryDebug/res/values/titles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Missed Notifications Reminder v18P Debug
5 | Missed Notifications Reminder v18P (D)
6 |
7 |
8 |
--------------------------------------------------------------------------------
/app/src/debug/java/com/jakewharton/u2020/data/ScalpelWireframeEnabled.java:
--------------------------------------------------------------------------------
1 | package com.jakewharton.u2020.data;
2 |
3 | import java.lang.annotation.Retention;
4 | import javax.inject.Qualifier;
5 |
6 | import static java.lang.annotation.RetentionPolicy.RUNTIME;
7 |
8 | @Qualifier @Retention(RUNTIME)
9 | public @interface ScalpelWireframeEnabled {
10 | }
11 |
--------------------------------------------------------------------------------
/app/src/notificationListenerV27/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/java/com/app/missednotificationsreminder/payment/billing/domain/entities/ConsumeResult.kt:
--------------------------------------------------------------------------------
1 | package com.app.missednotificationsreminder.payment.billing.domain.entities
2 |
3 | import com.app.missednotificationsreminder.common.domain.entities.ResultWrapper
4 |
5 | data class ConsumeResult(val skuDetails: List, val operationStatus: ResultWrapper)
6 |
--------------------------------------------------------------------------------
/app/src/notificationListener/res/values-de/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Benachrichtigungszugriff deakiviert.
4 | Klicke auf \"Zugriff verwalten\" und aktiviere die App \"Missed Notification Reminder\".
5 | Andernfalls kann die Benachrichtigung nicht ausgeführt werden.
6 |
7 |
--------------------------------------------------------------------------------
/app/src/debug/res/layout/debug_drawer_contextual_action.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
10 |
--------------------------------------------------------------------------------
/app/src/main/java/com/app/missednotificationsreminder/ui/UiModule.kt:
--------------------------------------------------------------------------------
1 | package com.app.missednotificationsreminder.ui
2 |
3 | import com.app.missednotificationsreminder.di.ViewModelBuilder
4 | import dagger.Module
5 |
6 | /**
7 | * The Dagger dependency injection module for the UI layer
8 | */
9 | @Module(includes = [ViewModelBuilder::class, UiModuleExt::class])
10 | class UiModule
--------------------------------------------------------------------------------
/app/src/main/java/com/app/missednotificationsreminder/service/event/NotificationsUpdatedEvent.kt:
--------------------------------------------------------------------------------
1 | package com.app.missednotificationsreminder.service.event
2 |
3 | import com.app.missednotificationsreminder.service.data.model.NotificationData
4 | import com.app.missednotificationsreminder.util.event.Event
5 |
6 | data class NotificationsUpdatedEvent(val notifications: List) : Event
--------------------------------------------------------------------------------
/app/src/main/java/com/app/missednotificationsreminder/data/model/NightMode.kt:
--------------------------------------------------------------------------------
1 | package com.app.missednotificationsreminder.data.model
2 |
3 | import androidx.appcompat.app.AppCompatDelegate
4 |
5 | enum class NightMode(val nightModeId: Int) {
6 | FOLLOW_SYSTEM(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM),
7 | YES(AppCompatDelegate.MODE_NIGHT_YES),
8 | NO(AppCompatDelegate.MODE_NIGHT_NO)
9 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/app/missednotificationsreminder/di/qualifiers/MainThreadScheduler.java:
--------------------------------------------------------------------------------
1 | package com.app.missednotificationsreminder.di.qualifiers;
2 |
3 | import java.lang.annotation.Retention;
4 |
5 | import javax.inject.Qualifier;
6 |
7 | import static java.lang.annotation.RetentionPolicy.RUNTIME;
8 |
9 | @Qualifier @Retention(RUNTIME)
10 | public @interface MainThreadScheduler {
11 | }
12 |
--------------------------------------------------------------------------------
/app/src/debug/java/com/jakewharton/u2020/ui/debug/DebugViewModule.kt:
--------------------------------------------------------------------------------
1 | package com.jakewharton.u2020.ui.debug
2 |
3 | import dagger.Module
4 | import dagger.android.ContributesAndroidInjector
5 |
6 | /**
7 | * The Dagger dependency injection module for the settings activity
8 | */
9 | @Module
10 | abstract class DebugViewModule {
11 | @ContributesAndroidInjector
12 | abstract fun contribute(): DebugView
13 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/app/missednotificationsreminder/settings/applicationssettings/ApplicationsSettingsViewState.kt:
--------------------------------------------------------------------------------
1 | package com.app.missednotificationsreminder.settings.applicationssettings
2 |
3 | data class ApplicationsSettingsViewState(
4 | val ignorePersistentNotifications: Boolean,
5 | val respectPhoneCalls: Boolean,
6 | val respectRingerMode: Boolean,
7 | val remindWhenScreenIsOn: Boolean)
--------------------------------------------------------------------------------
/app/src/main/java/com/app/missednotificationsreminder/payment/PurchaseItemViewState.kt:
--------------------------------------------------------------------------------
1 | package com.app.missednotificationsreminder.payment
2 |
3 | import com.app.missednotificationsreminder.payment.billing.domain.entities.SkuDetails
4 |
5 | /**
6 | * The class to store purchase item view state information
7 | */
8 | data class PurchaseItemViewState(val skuDetails: SkuDetails) {
9 | val price: String = skuDetails.price
10 | }
11 |
--------------------------------------------------------------------------------
/app/src/main/res/values/log_colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #ff2196f3
4 | #ff4caf50
5 | #ffff9800
6 | #fff44336
7 | #ff9c27b0
8 | #ffffffff
9 |
10 |
--------------------------------------------------------------------------------
/app/src/test/java/com/app/missednotificationsreminder/ExampleUnitTest.java:
--------------------------------------------------------------------------------
1 | package com.app.missednotificationsreminder;
2 |
3 | import org.junit.Test;
4 |
5 | import static org.junit.Assert.assertEquals;
6 |
7 | /**
8 | * To work on unit tests, switch the Test Artifact in the Build Variants view.
9 | */
10 | public class ExampleUnitTest {
11 |
12 | @Test
13 | public void addition_isCorrect() throws Exception {
14 | assertEquals(4, 2 + 2);
15 | }
16 |
17 | }
--------------------------------------------------------------------------------
/app/lint.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/app/src/main/java/com/app/missednotificationsreminder/payment/billing/domain/entities/BillingErrorCodes.kt:
--------------------------------------------------------------------------------
1 | package com.app.missednotificationsreminder.payment.billing.domain.entities
2 |
3 | object BillingErrorCodes {
4 | const val BILLING_UNAVAILABLE = 3
5 | const val SERVICE_UNAVAILABLE = 2
6 | const val ITEM_ALREADY_OWNED = 7
7 | const val USER_CANCELED = 1
8 |
9 | /**
10 | * The purchase payment is pending
11 | */
12 | const val PURCHASE_PENDING = 10
13 | }
14 |
--------------------------------------------------------------------------------
/app/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 116dp
5 | 40dip
6 | 16dip
7 | 8dip
8 | 2
9 |
10 |
--------------------------------------------------------------------------------
/app/src/debug/java/com/jakewharton/u2020/ui/misc/EmptyTextWatcher.java:
--------------------------------------------------------------------------------
1 | package com.jakewharton.u2020.ui.misc;
2 |
3 | import android.text.Editable;
4 | import android.text.TextWatcher;
5 |
6 | public class EmptyTextWatcher implements TextWatcher {
7 | @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) {
8 | }
9 |
10 | @Override public void onTextChanged(CharSequence s, int start, int before, int count) {
11 | }
12 |
13 | @Override public void afterTextChanged(Editable s) {
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/app/src/proprietary/java/com/app/missednotificationsreminder/payment/billing/data/mappers/SkuTypeMappers.kt:
--------------------------------------------------------------------------------
1 | package com.app.missednotificationsreminder.payment.billing.data.mappers
2 |
3 | import com.android.billingclient.api.BillingClient
4 | import com.app.missednotificationsreminder.payment.billing.domain.entities.SkuType
5 |
6 | @BillingClient.SkuType
7 | fun SkuType.toRemote(): String {
8 | return when (this) {
9 | SkuType.INAPP -> BillingClient.SkuType.INAPP
10 | SkuType.SUBS -> BillingClient.SkuType.SUBS
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/app/src/debug/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/app/src/main/java/com/app/missednotificationsreminder/util/loadingstate/ManagesLoadingState.kt:
--------------------------------------------------------------------------------
1 | package com.app.missednotificationsreminder.util.loadingstate
2 |
3 | interface ManagesLoadingState {
4 | /**
5 | * Attach loading state update logic to the [block] operation
6 | */
7 | suspend fun attachLoading(operationName: String, block: suspend () -> T): T
8 |
9 | /**
10 | * Attach loading status update logic to the [block] operation
11 | */
12 | suspend fun attachLoadingStatus(status: String, block: suspend () -> T): T
13 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/app/missednotificationsreminder/payment/PurchaseViewEffect.kt:
--------------------------------------------------------------------------------
1 | package com.app.missednotificationsreminder.payment
2 |
3 | import com.app.missednotificationsreminder.payment.billing.domain.entities.SkuDetails
4 |
5 | sealed class PurchaseViewEffect {
6 | /**
7 | * Purchase the specified [skuDetails]
8 | */
9 | data class Purchase(val skuDetails: SkuDetails) : PurchaseViewEffect()
10 |
11 | /**
12 | * Show the specified [message]
13 | */
14 | data class Message(val message: String) : PurchaseViewEffect()
15 | }
16 |
--------------------------------------------------------------------------------
/app/src/release/java/com/app/missednotificationsreminder/ui/UiModuleExt.kt:
--------------------------------------------------------------------------------
1 | package com.app.missednotificationsreminder.ui
2 |
3 | import dagger.Module
4 | import dagger.Provides
5 | import javax.inject.Singleton
6 |
7 | @Module()
8 | class UiModuleExt {
9 | @Provides
10 | @Singleton
11 | fun provideAppContainer(): AppContainer {
12 | return AppContainer.DEFAULT
13 | }
14 |
15 | @Provides
16 | @Singleton
17 | fun provideActivityHierarchyServer(): ActivityHierarchyServer {
18 | return ActivityHierarchyServer.NONE
19 | }
20 | }
--------------------------------------------------------------------------------
/app/src/debug/res/values/debug_strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Development Settings
5 | Enter the URL of the server to which you want to connect.\n\ne.g., https://bob-loblaw.local/1.0/
6 | Enter the host of the server through which you want to proxy.\n\ne.g., 12.34.56.78:9000
7 | Debug settings are available in this drawer.
8 |
9 |
--------------------------------------------------------------------------------
/app/src/main/java/com/app/missednotificationsreminder/data/source/DefaultResourceDataSource.kt:
--------------------------------------------------------------------------------
1 | package com.app.missednotificationsreminder.data.source
2 |
3 | import android.content.Context
4 | import com.app.missednotificationsreminder.di.qualifiers.ForApplication
5 | import javax.inject.Inject
6 |
7 | class DefaultResourceDataSource @Inject constructor(
8 | @param:ForApplication private val applicationContext: Context
9 | ) : ResourceDataSource {
10 | override fun getString(id: Int, vararg args: Any): String {
11 | return applicationContext.getString(id, *args)
12 | }
13 | }
--------------------------------------------------------------------------------
/app/src/main/res/values-notnight/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
11 |
12 |
14 |
15 |
--------------------------------------------------------------------------------
/app/src/main/java/com/app/missednotificationsreminder/CustomApplication.kt:
--------------------------------------------------------------------------------
1 | package com.app.missednotificationsreminder
2 |
3 | import dagger.android.AndroidInjector
4 | import dagger.android.support.DaggerApplication
5 |
6 | /**
7 | * Custom application implementation to provide additional functionality such as dependency injection, leak inspection
8 | *
9 | * @author Eugene Popovich
10 | */
11 | class CustomApplication : CustomApplicationBase() {
12 | override fun applicationInjector(): AndroidInjector {
13 | return DaggerApplicationComponent.factory().create(applicationContext)
14 | }
15 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/app/missednotificationsreminder/util/resources/ResourcesUtils.kt:
--------------------------------------------------------------------------------
1 | package com.app.missednotificationsreminder.util.resources
2 |
3 | import android.content.Context
4 | import android.content.res.TypedArray
5 | import android.util.TypedValue
6 |
7 | fun Context.resolveBooleanAttribute(attr: Int, defaultValue: Boolean): Boolean {
8 | val typedValue = TypedValue()
9 | theme.resolveAttribute(attr, typedValue, true)
10 | val ta: TypedArray = obtainStyledAttributes(typedValue.resourceId, intArrayOf(attr))
11 | val result = ta.getBoolean(0, defaultValue)
12 | ta.recycle()
13 | return result
14 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/app/missednotificationsreminder/settings/vibration/VibrationViewState.kt:
--------------------------------------------------------------------------------
1 | package com.app.missednotificationsreminder.settings.vibration
2 |
3 | /**
4 | * @property enabled used to handle vibration enabled state
5 | * @property pattern used to handle vibration pattern
6 | * @property lastValidPattern used to store last valid pattern
7 | * @property patternError used to handle pattern error information
8 | */
9 | data class VibrationViewState(
10 | val enabled: Boolean = false,
11 | val pattern: String = "",
12 | val lastValidPattern: String = "",
13 | val patternError: String = "")
--------------------------------------------------------------------------------
/app/src/main/java/com/app/missednotificationsreminder/ui/fragment/common/ActivityStateAccessor.java:
--------------------------------------------------------------------------------
1 | package com.app.missednotificationsreminder.ui.fragment.common;
2 |
3 | /**
4 | * The interface for the activity state accessor to provide various the activity lifecycle
5 | * related information
6 | */
7 | public interface ActivityStateAccessor {
8 | /**
9 | * Is the activity alive
10 | *
11 | * @return
12 | */
13 | public boolean isActivityAlive();
14 |
15 | /**
16 | * Is the activity active and resumed
17 | *
18 | * @return
19 | */
20 | public boolean isActivityResumed();
21 | }
--------------------------------------------------------------------------------
/app/src/accessibility/res/xml/notifications_service_config.xml:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/app/src/main/java/com/app/missednotificationsreminder/binding/model/BaseViewModel.java:
--------------------------------------------------------------------------------
1 | package com.app.missednotificationsreminder.binding.model;
2 |
3 | import androidx.lifecycle.ViewModel;
4 |
5 | /**
6 | * The base view model with the common functionality
7 | */
8 | public class BaseViewModel extends ViewModel {
9 |
10 | /**
11 | * Call this method when the view model is not needed anymore to cancel running background
12 | * operations.
13 | */
14 | public void shutdown() {
15 | }
16 |
17 | @Override protected void onCleared() {
18 | super.onCleared();
19 | shutdown();
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/app/src/main/java/com/app/missednotificationsreminder/payment/PurchaseItemViewModel.kt:
--------------------------------------------------------------------------------
1 | package com.app.missednotificationsreminder.payment
2 |
3 | import com.app.missednotificationsreminder.binding.model.BaseViewStateViewEffectModel
4 | import com.app.missednotificationsreminder.binding.model.ViewStatePartialChanges
5 |
6 | /**
7 | * The view model for the single purchase item
8 | */
9 | class PurchaseItemViewModel(purchaseItem: PurchaseItem) : BaseViewStateViewEffectModel<
10 | PurchaseItemViewState,
11 | PurchaseViewEffect,
12 | ViewStatePartialChanges>(PurchaseItemViewState(purchaseItem.skuDetails))
--------------------------------------------------------------------------------
/app/src/debug/java/com/jakewharton/u2020/util/Strings.java:
--------------------------------------------------------------------------------
1 | package com.jakewharton.u2020.util;
2 |
3 | public final class Strings {
4 | private Strings() {
5 | // No instances.
6 | }
7 |
8 | public static boolean isBlank(CharSequence string){
9 | return (string == null || string.toString().trim().length() == 0);
10 | }
11 |
12 | public static String valueOrDefault(String string, String defaultString) {
13 | return isBlank(string) ? defaultString : string;
14 | }
15 |
16 | public static String truncateAt(String string, int length) {
17 | return string.length() > length ? string.substring(0, length) : string;
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/app/src/main/java/com/app/missednotificationsreminder/service/event/RemindEvents.java:
--------------------------------------------------------------------------------
1 | package com.app.missednotificationsreminder.service.event;
2 |
3 | import com.app.missednotificationsreminder.util.event.Event;
4 |
5 | /**
6 | * Available remind events
7 | */
8 |
9 | public enum RemindEvents implements Event {
10 | /**
11 | * The remind request event
12 | */
13 | REMIND,
14 | /**
15 | * The reminder completed notification event
16 | */
17 | REMINDER_COMPLETED,
18 | /**
19 | * Request current notification data information via {@link NotificationsUpdatedEvent}
20 | */
21 | GET_CURRENT_NOTIFICATIONS_DATA
22 | }
23 |
--------------------------------------------------------------------------------
/app/src/release/java/com/app/missednotificationsreminder/payment/di/PurchaseDataModuleExt.kt:
--------------------------------------------------------------------------------
1 | package com.app.missednotificationsreminder.payment.di
2 |
3 | import com.app.missednotificationsreminder.payment.di.qualifiers.AvailableSkus
4 | import dagger.Module
5 | import dagger.Provides
6 | import javax.inject.Singleton
7 |
8 | @Module
9 | class PurchaseDataModuleExt {
10 |
11 | @Provides
12 | @Singleton
13 | @AvailableSkus
14 | fun provideAvailableSkus() = listOf(
15 | "item_1",
16 | "item_2",
17 | "item_5",
18 | "item_10",
19 | "item_20",
20 | "item_50",
21 | "item_100")
22 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/app/missednotificationsreminder/util/loadingstate/HasLoadingStateManager.kt:
--------------------------------------------------------------------------------
1 | package com.app.missednotificationsreminder.util.loadingstate
2 |
3 | interface HasLoadingStateManager : ManagesLoadingState {
4 | val loadingStateManager: LoadingStateManager
5 |
6 | override suspend fun attachLoading(operationName: String, block: suspend () -> T): T {
7 | return loadingStateManager.attachLoading("$operationName:${this::class.simpleName}", block)
8 | }
9 |
10 | override suspend fun attachLoadingStatus(status: String, block: suspend () -> T): T {
11 | return loadingStateManager.attachLoadingStatus(status, block)
12 | }
13 | }
--------------------------------------------------------------------------------
/app/src/main/res/values-night/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
12 |
13 |
15 |
16 |
--------------------------------------------------------------------------------
/app/src/debug/java/com/app/missednotificationsreminder/payment/di/PurchaseDataModuleExt.kt:
--------------------------------------------------------------------------------
1 | package com.app.missednotificationsreminder.payment.di
2 |
3 | import com.app.missednotificationsreminder.payment.di.qualifiers.AvailableSkus
4 | import dagger.Module
5 | import dagger.Provides
6 | import javax.inject.Singleton
7 |
8 | @Module
9 | class PurchaseDataModuleExt {
10 |
11 | @Provides
12 | @Singleton
13 | @AvailableSkus
14 | fun provideAvailableSkus() = listOf(
15 | "android.test.purchased",
16 | "android.test.canceled",
17 | "android.test.refunded",
18 | "android.test.item_unavailable",
19 | "not_existing")
20 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/app/missednotificationsreminder/settings/applicationselection/ApplicationItemViewStatePartialChanges.kt:
--------------------------------------------------------------------------------
1 | package com.app.missednotificationsreminder.settings.applicationselection
2 |
3 | import com.app.missednotificationsreminder.binding.model.ViewStatePartialChanges
4 |
5 | sealed class ApplicationItemViewStatePartialChanges : ViewStatePartialChanges {
6 | data class CheckedStateChange(private val newValue: Boolean) : ApplicationItemViewStatePartialChanges() {
7 | override fun reduce(previousState: ApplicationItemViewState): ApplicationItemViewState {
8 | return previousState.copy(checked = newValue)
9 | }
10 | }
11 | }
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in /Users/andrzejsliwa/Library/Android/sdk/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 |
--------------------------------------------------------------------------------
/app/src/accessibility/res/values-bg/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Услугата за известия е изключена. Натиснете на \"Управление на достъпа\" и
4 | изберете \"Услуга Missed Notification Reminder\". В следващия прозорец изберете включване.
5 | Иначе известията няма да работят.
6 |
7 | Включете, за да разрешите приложението Missed Notifications Reminder
8 | Приложението НЕ събира никакви Ваши данни. Забележете, че то няма права за достъп до интернет, съответно
9 | не може да прати нищо на никого.
10 | Услуга Missed Notifications Reminder
11 |
--------------------------------------------------------------------------------
/app/src/accessibility/res/values-ru/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Служба уведомлений отключена. Нажмите кнопку \"Управление доступом\",
4 | выберите пункт \"Missed Notification Reminder Служба\". В следующем окне выберите включить.
5 | Иначе напоминания не будут работать.
6 |
7 | Включите, чтобы разрешить приложению Missed Notifications Reminder
8 | Это приложение НЕ собирает никакие ваши данные. Заметьте, что у него нет права доступа к интернету, значит оно
9 | даже не может никуда и ничего послать.
10 | Missed Notifications Reminder Служба
11 |
--------------------------------------------------------------------------------
/app/src/accessibility/res/values-uk/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Служба првідомлень відключена. Натисніть кнопку \"Керування доступом\",
4 | оберіть пункт \"Missed Notification Reminder Служба\". В наступному вікні оберіть увімкнути.
5 | Инакше нагадування не будуть працювати.
6 |
7 | Увімкніть, щоб дозволити додаток Missed Notifications Reminder
8 | Цей додаток НЕ збирає ніяких ваших даних. Зауважте, що у нього нема прав доступу до інтернету, а це означає що він
9 | навіть не може нікуди і нічого послати.
10 | Missed Notifications Reminder Служба
11 |
--------------------------------------------------------------------------------
/app/src/main/java/com/app/missednotificationsreminder/payment/PurchaseViewState.kt:
--------------------------------------------------------------------------------
1 | package com.app.missednotificationsreminder.payment
2 |
3 | import com.app.missednotificationsreminder.common.domain.entities.ResultWrapper
4 | import com.app.missednotificationsreminder.util.loadingstate.LoadingState
5 |
6 | data class PurchaseViewState(
7 | val loadingState: LoadingState = LoadingState(),
8 | val data: ResultWrapper>? = null,
9 | val purchases: String = "",
10 | val contributeOptions: String
11 | ) {
12 | val error = if (data is ResultWrapper.Error) data.messageOrDefault { "Not specified" } else "None"
13 | val errorVisible = data is ResultWrapper.Error
14 |
15 | val purchasesVisible: Boolean
16 | get() = purchases.isNotEmpty()
17 | }
18 |
--------------------------------------------------------------------------------
/app/src/accessibility/res/values-nl/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | De meldingsdienst is momenteel uitgeschakeld.
5 | Druk op de knop \"Toegang beheren\", kies \"Missed Notification Reminder-dienst\" en
6 | schakel dit in. Zonder dit werkt de dienst niet.
7 |
8 | Schakel in om Missed Notifications Reminder
9 | toegang te geven tot inkomende meldingen.
10 | Deze app verzamelt GEEN gegevens. Sterker nog: de app heeft geen internettoegang,
11 | dus gegevens kunnen sowieso niet worden verstuurd.
12 | Missed Notification Reminder-dienst
13 |
14 |
--------------------------------------------------------------------------------
/app/src/noProprietary/java/com/app/missednotificationsreminder/payment/di/PurchaseRepositoryModule.kt:
--------------------------------------------------------------------------------
1 | package com.app.missednotificationsreminder.payment.di
2 |
3 | import com.app.missednotificationsreminder.data.source.ResourceDataSource
4 | import com.app.missednotificationsreminder.payment.billing.data.PurchaseRepositoryImpl
5 | import com.app.missednotificationsreminder.payment.billing.domain.repository.PurchaseRepository
6 | import dagger.Module
7 | import dagger.Provides
8 | import javax.inject.Singleton
9 |
10 | @Module
11 | class PurchaseRepositoryModule {
12 |
13 | @Provides
14 | @Singleton
15 | fun providePurchaseRepository(
16 | resourceDataSource: ResourceDataSource
17 | ): PurchaseRepository {
18 | return PurchaseRepositoryImpl(resourceDataSource)
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/app/src/accessibility/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | The notification service is currently disabled.
5 | Press \"Manage Access\" button, select \"Missed Notification Reminder Service\" item
6 | and enable it. Otherwise the reminder will not work.
7 |
8 | Enable to allow Missed Notifications Reminder
9 | receive all incoming notifications.
10 | This app DOES NOT collect any of your data. Please note that this app doesn\'t have internet
11 | access permission so it actually cannot send the data anywhere.
12 | Missed Notifications Reminder Service
13 |
--------------------------------------------------------------------------------
/app/src/debug/res/layout/debug_drawer_network_endpoint.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
9 |
14 |
21 |
22 |
--------------------------------------------------------------------------------
/app/src/debug/res/layout/debug_drawer_network_proxy.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
9 |
14 |
21 |
22 |
--------------------------------------------------------------------------------
/app/src/accessibility/res/values-ja/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | The notification service is currently disabled.
5 | Press \"Manage Access\" button, select \"Missed Notification Reminder Service\" item
6 | and enable it. Otherwise the reminder will not work.
7 |
8 | Enable to allow Missed Notifications Reminder
9 | receive all incoming notifications.
10 | This app DOES NOT collect any of your data. Please note that this app doesn\'t have internet
11 | access permission so it actually cannot send the data anywhere.
12 | Missed Notifications Reminder Service
13 |
--------------------------------------------------------------------------------
/app/src/main/java/com/app/missednotificationsreminder/ApplicationComponent.kt:
--------------------------------------------------------------------------------
1 | package com.app.missednotificationsreminder
2 |
3 | import android.content.Context
4 | import dagger.BindsInstance
5 | import dagger.Component
6 | import dagger.android.AndroidInjector
7 | import dagger.android.support.AndroidSupportInjectionModule
8 | import javax.inject.Singleton
9 |
10 | /**
11 | * Main component for the application.
12 | */
13 | @Singleton
14 | @Component(
15 | modules = [
16 | AndroidSupportInjectionModule::class,
17 | ApplicationModule::class
18 | ])
19 | interface ApplicationComponent : AndroidInjector {
20 |
21 | @Component.Factory
22 | interface Factory {
23 | fun create(@BindsInstance applicationContext: Context): ApplicationComponent
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/debug_logs.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
13 |
14 |
20 |
21 |
--------------------------------------------------------------------------------
/app/src/main/java/com/app/missednotificationsreminder/util/livedata/NonNullMutableLiveData.kt:
--------------------------------------------------------------------------------
1 | package com.app.missednotificationsreminder.util.livedata
2 |
3 | import androidx.lifecycle.LifecycleOwner
4 | import androidx.lifecycle.MutableLiveData
5 | import androidx.lifecycle.Observer
6 |
7 | class NonNullMutableLiveData(initValue: T) : MutableLiveData() {
8 |
9 | init {
10 | value = initValue
11 | }
12 |
13 | override fun getValue(): T {
14 | return super.getValue()!!
15 | }
16 |
17 | override fun setValue(value: T) {
18 | super.setValue(value)
19 | }
20 |
21 | fun observe(owner: LifecycleOwner, body: (T) -> Unit) {
22 | observe(owner, Observer { t -> body(t!!) })
23 | }
24 |
25 | override fun postValue(value: T) {
26 | super.postValue(value)
27 | }
28 | }
--------------------------------------------------------------------------------
/app/src/debug/java/com/jakewharton/u2020/util/EmptyActivityLifecycleCallbacks.java:
--------------------------------------------------------------------------------
1 | package com.jakewharton.u2020.util;
2 |
3 | import android.app.Activity;
4 | import android.app.Application.ActivityLifecycleCallbacks;
5 | import android.os.Bundle;
6 |
7 | public abstract class EmptyActivityLifecycleCallbacks implements
8 | ActivityLifecycleCallbacks {
9 | @Override public void onActivityCreated(Activity activity, Bundle savedInstanceState) {}
10 | @Override public void onActivityStarted(Activity activity) {}
11 | @Override public void onActivityResumed(Activity activity) {}
12 | @Override public void onActivityPaused(Activity activity) {}
13 | @Override public void onActivityStopped(Activity activity) {}
14 | @Override public void onActivitySaveInstanceState(Activity activity, Bundle outState) {}
15 | @Override public void onActivityDestroyed(Activity activity) {}
16 | }
17 |
--------------------------------------------------------------------------------
/app/src/notificationListener/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
8 |
9 |
10 |
11 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/app/src/main/java/com/app/missednotificationsreminder/binding/model/BaseViewStateViewEffectModel.kt:
--------------------------------------------------------------------------------
1 | package com.app.missednotificationsreminder.binding.model
2 |
3 | import kotlinx.coroutines.ExperimentalCoroutinesApi
4 | import kotlinx.coroutines.flow.*
5 |
6 | @OptIn(ExperimentalCoroutinesApi::class)
7 | open class BaseViewStateViewEffectModel<
8 | VIEW_STATE,
9 | VIEW_EFFECT,
10 | PARTIAL_CHANGES : ViewStatePartialChanges>(
11 | initialViewState: VIEW_STATE) : BaseViewStateModel(initialViewState) {
12 | private val _viewEffect = MutableStateFlow>(Event(null))
13 |
14 | val viewEffect: Flow = _viewEffect
15 | .map{it.getContentIfNotHandled()}
16 | .filterNotNull()
17 |
18 | fun requestViewEffect(effect: VIEW_EFFECT) {
19 | _viewEffect.value = Event(effect)
20 | }
21 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/app/missednotificationsreminder/binding/util/BindingConversionUtils.java:
--------------------------------------------------------------------------------
1 | package com.app.missednotificationsreminder.binding.util;
2 |
3 | import androidx.databinding.BindingConversion;
4 | import android.view.View;
5 |
6 | /**
7 | * Utility class which stores Android Data Binding framework related binding conversion rules
8 | *
9 | * @author Eugene Popovich
10 | */
11 | public class BindingConversionUtils {
12 |
13 | /**
14 | * Rule to convert {@link Boolean} object to int visibility code:
15 | * either {@link View#VISIBLE} or {@link View#GONE}
16 | *
17 | * @param visible the boolean value to convert
18 | * @return the visibility code depend on visible value
19 | */
20 | @BindingConversion public static int convertBooleanToVisibilityState(Boolean visible) {
21 | return visible != null && visible ? View.VISIBLE : View.GONE;
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/app/src/accessibility/res/values-de/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Benachrichtigungszugriff deakiviert.
5 | Klicke auf \"Zugriff verwalten\" und aktiviere die App \"Missed Notification Reminder\".
6 | Andernfalls kann die Benachrichtigung nicht ausgeführt werden.
7 |
8 |
9 | Aktivieren Sie diese Option, um die Erinnerung an verpasste Benachrichtigungen zuzulassen
10 | alle eingehenden Benachrichtigungen erhalten.
11 | Diese App sammelt keine Ihrer Daten. Bitte beachten Sie, dass diese App kein Internet hat
12 | Zugriffsberechtigung, sodass die Daten tatsächlich nirgendwo gesendet werden können.
13 | Missed Notifications Reminder Service
14 |
--------------------------------------------------------------------------------
/app/src/main/java/com/app/missednotificationsreminder/util/flow/FlowExtensions.kt:
--------------------------------------------------------------------------------
1 | package com.app.missednotificationsreminder.util.flow
2 |
3 | import kotlinx.coroutines.FlowPreview
4 | import kotlinx.coroutines.flow.Flow
5 |
6 | /**
7 | * Mirrors the one [Flow] in an [Iterable] of several [Flow]s that first either emits an item or sends
8 | * a termination notification.
9 | */
10 | @FlowPreview
11 | fun Iterable>.amb(): Flow = FlowAmb(this@amb)
12 |
13 | /**
14 | * Mirrors the current [Flow] or the other [Flow] provided of which the first either emits an item or sends a termination
15 | * notification.
16 | */
17 | @FlowPreview
18 | fun Flow.ambWith(other: Flow): Flow = FlowAmb(listOf(this@ambWith, other))
19 |
20 | @FlowPreview
21 | fun Flow.bufferTimeout(duration: Long, size: Int = Int.MAX_VALUE): Flow> =
22 | FlowBufferTimeout(this@bufferTimeout, size, duration)
--------------------------------------------------------------------------------
/app/src/proprietary/java/com/app/missednotificationsreminder/payment/billing/data/mappers/PurchaseMappers.kt:
--------------------------------------------------------------------------------
1 | package com.app.missednotificationsreminder.payment.billing.data.mappers
2 |
3 | import com.android.billingclient.api.Purchase
4 | import com.app.missednotificationsreminder.payment.billing.domain.entities.PurchaseState
5 |
6 | fun Purchase.toDomain() = com.app.missednotificationsreminder.payment.billing.domain.entities.Purchase(
7 | sku = sku,
8 | purchaseToken = purchaseToken,
9 | isAcknowledged = isAcknowledged,
10 | purchaseState = purchaseState.toPurchaseStateDomain(),
11 | )
12 |
13 | private fun Int.toPurchaseStateDomain(): PurchaseState {
14 | return when (this) {
15 | Purchase.PurchaseState.PENDING -> PurchaseState.PENDING
16 | Purchase.PurchaseState.PURCHASED -> PurchaseState.PURCHASED
17 | else -> PurchaseState.UNSPECIFIED_STATE
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/app/src/main/java/com/app/missednotificationsreminder/settings/sound/SoundViewStatePartialChanges.kt:
--------------------------------------------------------------------------------
1 | package com.app.missednotificationsreminder.settings.sound
2 |
3 | import android.net.Uri
4 | import com.app.missednotificationsreminder.binding.model.ViewStatePartialChanges
5 |
6 | sealed class SoundViewStatePartialChanges : ViewStatePartialChanges {
7 |
8 | data class RingtoneChange(private val newValue: Uri) : SoundViewStatePartialChanges() {
9 | override fun reduce(previousState: SoundViewState): SoundViewState {
10 | return previousState.copy(ringtone = newValue.toString())
11 | }
12 | }
13 |
14 | data class RingtoneTitleChange(private val newValue: String) : SoundViewStatePartialChanges() {
15 | override fun reduce(previousState: SoundViewState): SoundViewState {
16 | return previousState.copy(ringtoneName = newValue)
17 | }
18 | }
19 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/app/missednotificationsreminder/binding/model/Event.kt:
--------------------------------------------------------------------------------
1 | package com.app.missednotificationsreminder.binding.model
2 |
3 | /**
4 | * Used as a wrapper for data that is exposed via a StateFlow that represents an event.
5 | * Taken from https://gist.github.com/JoseAlcerreca/5b661f1800e1e654f07cc54fe87441af#file-event-kt
6 | */
7 | open class Event(private val content: T) {
8 | var hasBeenHandled = false
9 | private set // Allow external read but not write
10 |
11 | /**
12 | * Returns the content and prevents its use again.
13 | */
14 | fun getContentIfNotHandled(): T? {
15 | return if (hasBeenHandled) {
16 | null
17 | } else {
18 | hasBeenHandled = true
19 | content
20 | }
21 | }
22 |
23 | /**
24 | * Returns the content, even if it's already been handled.
25 | */
26 | fun peekContent(): T = content
27 | }
--------------------------------------------------------------------------------
/app/src/proprietary/java/com/app/missednotificationsreminder/payment/billing/data/mappers/SkuDetailsMappers.kt:
--------------------------------------------------------------------------------
1 | package com.app.missednotificationsreminder.payment.billing.data.mappers
2 |
3 | import com.android.billingclient.api.BillingClient
4 | import com.android.billingclient.api.SkuDetails
5 | import com.app.missednotificationsreminder.payment.billing.domain.entities.SkuType
6 |
7 | fun SkuDetails.toDomain() = com.app.missednotificationsreminder.payment.billing.domain.entities.SkuDetails(
8 | sku = sku,
9 | skuType = type.toDomainSkuType(),
10 | price = price,
11 | priceAmountMicros = priceAmountMicros,
12 | )
13 |
14 | private fun String.toDomainSkuType(): SkuType {
15 | return when (this) {
16 | BillingClient.SkuType.INAPP -> SkuType.INAPP
17 | BillingClient.SkuType.SUBS -> SkuType.SUBS
18 | else -> throw IllegalArgumentException("Unsupported sku type $this")
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/app/src/main/java/com/app/missednotificationsreminder/service/util/PhoneStateUtils.java:
--------------------------------------------------------------------------------
1 | package com.app.missednotificationsreminder.service.util;
2 |
3 | import android.content.Context;
4 | import android.media.AudioManager;
5 |
6 | /**
7 | * Various phone state related utilities
8 | */
9 | public class PhoneStateUtils {
10 | /**
11 | * Check whether there is an active phone call
12 | * Taken from http://stackoverflow.com/a/17418732/527759
13 | *
14 | * @param context the context instance
15 | * @return true if devices is in phone call, false otherwise
16 | */
17 | public static boolean isCallActive(Context context) {
18 | AudioManager manager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
19 | int mode = manager.getMode();
20 | return mode == AudioManager.MODE_IN_CALL || mode == AudioManager.MODE_IN_COMMUNICATION || mode == AudioManager.MODE_RINGTONE;
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/app/src/main/java/com/app/missednotificationsreminder/ui/AppContainer.kt:
--------------------------------------------------------------------------------
1 | package com.app.missednotificationsreminder.ui
2 |
3 | import android.view.ViewGroup
4 | import androidx.appcompat.app.AppCompatActivity
5 | import com.app.missednotificationsreminder.R
6 |
7 | /**
8 | * An indirection which allows controlling the root container used for each activity.
9 | */
10 | interface AppContainer {
11 | /**
12 | * The root [ViewGroup] into which the activity should place its contents.
13 | */
14 | fun bind(activity: AppCompatActivity): ViewGroup
15 |
16 | companion object {
17 | /**
18 | * An [AppContainer] which returns the normal activity content view.
19 | */
20 | val DEFAULT: AppContainer = object : AppContainer {
21 | override fun bind(activity: AppCompatActivity): ViewGroup {
22 | return activity.findViewById(android.R.id.content)
23 | }
24 | }
25 | }
26 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/app/missednotificationsreminder/di/Injector.kt:
--------------------------------------------------------------------------------
1 | package com.app.missednotificationsreminder.di
2 |
3 | import android.content.Context
4 | import dagger.android.AndroidInjector
5 | import dagger.android.support.DaggerApplication
6 |
7 | /**
8 | * Utility class which allows to obtain [AndroidInjector] related to [Context]
9 | */
10 | class Injector private constructor() {
11 | companion object {
12 | /**
13 | * Obtain the [AndroidInjector] related to the specified context.
14 | *
15 | * @param context
16 | * @return
17 | */
18 | // Explicitly doing a custom service.
19 | @JvmStatic
20 | fun obtain(context: Context): AndroidInjector? {
21 | val app = context.applicationContext;
22 | return if (app is DaggerApplication) app.androidInjector() else null
23 | }
24 | }
25 |
26 | init {
27 | throw AssertionError("No instances.")
28 | }
29 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/app/missednotificationsreminder/ui/widget/recyclerview/LifecycleAdapterWithViewEffect.kt:
--------------------------------------------------------------------------------
1 | package com.app.missednotificationsreminder.ui.widget.recyclerview
2 |
3 | import com.app.missednotificationsreminder.binding.model.Event
4 | import kotlinx.coroutines.flow.Flow
5 | import kotlinx.coroutines.flow.MutableStateFlow
6 | import kotlinx.coroutines.flow.filterNotNull
7 | import kotlinx.coroutines.flow.map
8 | import timber.log.Timber
9 |
10 | abstract class LifecycleAdapterWithViewEffect : LifecycleAdapter() {
11 | private val _viewEffect = MutableStateFlow>(Event(null))
12 |
13 | val viewEffect: Flow = _viewEffect
14 | .map { it.getContentIfNotHandled() }
15 | .filterNotNull()
16 |
17 | fun requestViewEffect(effect: VIEW_EFFECT) {
18 | Timber.d("requestViewEffect() called with: effect = $effect")
19 | _viewEffect.value = Event(effect)
20 | }
21 | }
--------------------------------------------------------------------------------
/app/src/accessibility/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
13 |
14 |
15 |
16 |
17 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/app/src/debug/java/com/app/missednotificationsreminder/ApplicationModuleExt.kt:
--------------------------------------------------------------------------------
1 | package com.app.missednotificationsreminder
2 |
3 | import com.app.missednotificationsreminder.di.qualifiers.IsInstrumentationTest
4 | import com.app.missednotificationsreminder.ui.DebugUiModule
5 | import com.jakewharton.u2020.data.DebugDataModule
6 | import dagger.Module
7 | import dagger.Provides
8 | import javax.inject.Singleton
9 |
10 | @Module(includes = [DebugUiModule::class, DebugDataModule::class])
11 | class ApplicationModuleExt {
12 | @Provides
13 | @Singleton
14 | @IsInstrumentationTest
15 | fun provideIsInstrumentationTest(): Boolean {
16 | return instrumentationTest
17 | }
18 |
19 | companion object {
20 | // Low-tech flag to force certain debug build behaviors when running in an instrumentation test.
21 | // This value is used in the creation of singletons so it must be set before the graph is created.
22 | @JvmField
23 | var instrumentationTest = false
24 | }
25 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/app/missednotificationsreminder/settings/SettingsViewState.kt:
--------------------------------------------------------------------------------
1 | package com.app.missednotificationsreminder.settings
2 |
3 | import com.app.missednotificationsreminder.data.model.NightMode
4 | import com.app.missednotificationsreminder.util.BatteryUtils
5 |
6 | data class SettingsViewState(
7 | val accessInitialized: Boolean = false,
8 | val accessEnabled: Boolean = false,
9 | val batteryOptimizationDisabled: Boolean = false,
10 | val forceWakeLock: Boolean = false,
11 | val advancedSettingsVisible: Boolean = false,
12 | val vibrationSettingsAvailable: Boolean = false,
13 | val missingPermissions: String = "",
14 | val nightMode: NightMode = NightMode.FOLLOW_SYSTEM) {
15 | /**
16 | * Data binding method used to determine whether to display battery optimization settings
17 | */
18 | val isBatteryOptimizationSettingsVisible: Boolean by lazy {
19 | BatteryUtils.isBatteryOptimizationSettingsAvailable()
20 | }
21 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/app/missednotificationsreminder/di/qualifiers/ForActivity.java:
--------------------------------------------------------------------------------
1 |
2 | /*
3 | * Copyright (C) 2013 Square, Inc.
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | */
17 | package com.app.missednotificationsreminder.di.qualifiers;
18 |
19 |
20 | import java.lang.annotation.Retention;
21 |
22 | import javax.inject.Qualifier;
23 |
24 | import static java.lang.annotation.RetentionPolicy.RUNTIME;
25 |
26 | @Qualifier
27 | @Retention(RUNTIME)
28 | public @interface ForActivity {
29 | }
--------------------------------------------------------------------------------
/app/src/debug/java/com/jakewharton/u2020/ui/debug/DrawerLayoutImpl.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2014 The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 |
18 | package com.jakewharton.u2020.ui.debug;
19 |
20 | /**
21 | * Interface used to communicate from the v21-specific code for configuring a DrawerLayout
22 | * to the DrawerLayout itself.
23 | */
24 | interface DrawerLayoutImpl {
25 | void setChildInsets(Object insets, boolean drawStatusBar);
26 | }
27 |
--------------------------------------------------------------------------------
/app/src/main/java/com/app/missednotificationsreminder/di/qualifiers/ForApplication.java:
--------------------------------------------------------------------------------
1 |
2 |
3 | /*
4 | * Copyright (C) 2013 Square, Inc.
5 | *
6 | * Licensed under the Apache License, Version 2.0 (the "License");
7 | * you may not use this file except in compliance with the License.
8 | * You may obtain a copy of the License at
9 | *
10 | * http://www.apache.org/licenses/LICENSE-2.0
11 | *
12 | * Unless required by applicable law or agreed to in writing, software
13 | * distributed under the License is distributed on an "AS IS" BASIS,
14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | * See the License for the specific language governing permissions and
16 | * limitations under the License.
17 | */
18 | package com.app.missednotificationsreminder.di.qualifiers;
19 |
20 | import java.lang.annotation.Retention;
21 |
22 | import javax.inject.Qualifier;
23 |
24 | import static java.lang.annotation.RetentionPolicy.RUNTIME;
25 |
26 | @Qualifier
27 | @Retention(RUNTIME)
28 | public @interface ForApplication {
29 | }
30 |
--------------------------------------------------------------------------------
/app/src/main/res/values/constants.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 5
4 | 3600
5 | 60
6 | 5
7 | 1
8 | 100
9 | 0, 100, 50, 100, 50, 100, 200
10 |
11 | 0
12 |
13 | 1439
14 |
15 | 420
16 |
17 | 1260
18 |
19 |
--------------------------------------------------------------------------------
/app/src/debug/java/com/app/missednotificationsreminder/ui/UiModuleExt.kt:
--------------------------------------------------------------------------------
1 | package com.app.missednotificationsreminder.ui
2 |
3 | import com.app.missednotificationsreminder.di.qualifiers.IsInstrumentationTest
4 | import com.app.missednotificationsreminder.ui.debug.DebugAppContainer
5 | import com.jakewharton.u2020.ui.debug.SocketActivityHierarchyServer
6 | import dagger.Module
7 | import dagger.Provides
8 | import javax.inject.Singleton
9 |
10 | @Module()
11 | class UiModuleExt {
12 | @Provides
13 | @Singleton
14 | fun provideAppContainer(debugAppContainer: DebugAppContainer,
15 | @IsInstrumentationTest isInstrumentationTest: Boolean): AppContainer {
16 | // Do not add the debug controls for when we are running inside of an instrumentation test.
17 | return if (isInstrumentationTest) AppContainer.DEFAULT else debugAppContainer
18 | }
19 |
20 | @Provides
21 | @Singleton
22 | fun provideActivityHierarchyServer(): ActivityHierarchyServer {
23 | return SocketActivityHierarchyServer()
24 | }
25 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/app/missednotificationsreminder/payment/di/PurchaseDataModule.kt:
--------------------------------------------------------------------------------
1 | package com.app.missednotificationsreminder.payment.di
2 |
3 | import com.app.missednotificationsreminder.payment.model.Purchase
4 | import com.app.missednotificationsreminder.util.moshi.MoshiPreferenceWrapper
5 | import com.squareup.moshi.Moshi
6 | import com.squareup.moshi.Types
7 | import com.tfcporciuncula.flow.FlowSharedPreferences
8 | import com.tfcporciuncula.flow.Preference
9 | import dagger.Module
10 | import dagger.Provides
11 | import javax.inject.Singleton
12 |
13 | @Module(includes = [PurchaseDataModuleExt::class, PurchaseRepositoryModule::class])
14 | class PurchaseDataModule {
15 |
16 | @Provides
17 | @Singleton
18 | fun providePurchases(prefs: FlowSharedPreferences, moshi: Moshi): Preference> {
19 | return MoshiPreferenceWrapper(
20 | prefs,
21 | "PURCHASES",
22 | emptyList(),
23 | moshi.adapter(Types.newParameterizedType(List::class.java, Purchase::class.java))
24 | )
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/app/src/proprietary/java/com/app/missednotificationsreminder/payment/di/PurchaseRepositoryModule.kt:
--------------------------------------------------------------------------------
1 | package com.app.missednotificationsreminder.payment.di
2 |
3 | import android.content.Context
4 | import com.app.missednotificationsreminder.data.source.ResourceDataSource
5 | import com.app.missednotificationsreminder.di.qualifiers.ForApplication
6 | import com.app.missednotificationsreminder.payment.billing.data.PurchaseRepositoryImpl
7 | import com.app.missednotificationsreminder.payment.billing.domain.repository.PurchaseRepository
8 | import dagger.Module
9 | import dagger.Provides
10 | import kotlinx.coroutines.CoroutineScope
11 | import kotlinx.coroutines.Dispatchers
12 | import javax.inject.Singleton
13 |
14 | @Module
15 | class PurchaseRepositoryModule {
16 |
17 | @Provides
18 | @Singleton
19 | fun providePurchaseRepository(
20 | @ForApplication context: Context,
21 | resourceDataSource: ResourceDataSource
22 | ): PurchaseRepository {
23 | return PurchaseRepositoryImpl(CoroutineScope(Dispatchers.Main), resourceDataSource, context)
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/app/src/main/java/com/app/missednotificationsreminder/ui/widget/misc/BetterViewAnimator.java:
--------------------------------------------------------------------------------
1 | package com.app.missednotificationsreminder.ui.widget.misc;
2 |
3 | import android.content.Context;
4 | import android.util.AttributeSet;
5 | import android.view.View;
6 | import android.widget.ViewAnimator;
7 |
8 | public class BetterViewAnimator extends ViewAnimator {
9 | public BetterViewAnimator(Context context, AttributeSet attrs) {
10 | super(context, attrs);
11 | }
12 |
13 | public void setDisplayedChild(View v) {
14 | setDisplayedChildId(v.getId());
15 | }
16 | public void setDisplayedChildId(int id) {
17 | if (getDisplayedChildId() == id) {
18 | return;
19 | }
20 | for (int i = 0, count = getChildCount(); i < count; i++) {
21 | if (getChildAt(i).getId() == id) {
22 | setDisplayedChild(i);
23 | return;
24 | }
25 | }
26 | String name = getResources().getResourceEntryName(id);
27 | throw new IllegalArgumentException("No view with ID " + name);
28 | }
29 |
30 | public int getDisplayedChildId() {
31 | return getChildAt(getDisplayedChild()).getId();
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/app/src/main/java/com/app/missednotificationsreminder/binding/model/BaseViewStateModel.kt:
--------------------------------------------------------------------------------
1 | package com.app.missednotificationsreminder.binding.model
2 |
3 | import androidx.lifecycle.viewModelScope
4 | import kotlinx.coroutines.ExperimentalCoroutinesApi
5 | import kotlinx.coroutines.flow.MutableStateFlow
6 | import kotlinx.coroutines.flow.StateFlow
7 | import kotlinx.coroutines.launch
8 | import timber.log.Timber
9 |
10 | @OptIn(ExperimentalCoroutinesApi::class)
11 | open class BaseViewStateModel<
12 | VIEW_STATE,
13 | PARTIAL_CHANGES : ViewStatePartialChanges>(
14 | initialViewState: VIEW_STATE) : BaseViewModel() {
15 | protected val _viewState = MutableStateFlow(initialViewState)
16 |
17 | val viewState: StateFlow = _viewState
18 |
19 | fun process(event: PARTIAL_CHANGES) {
20 | Timber.d("process() called: with event=${event}")
21 | viewModelScope.launch {
22 | processSync(event)
23 | }
24 | }
25 |
26 | protected fun processSync(event: PARTIAL_CHANGES) {
27 | _viewState.apply { value = event.reduce(value) }
28 | }
29 |
30 | override fun toString(): String {
31 | return "${this::class.simpleName}: state = ${_viewState.value}"
32 | }
33 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/app/missednotificationsreminder/util/event/FlowEventBus.kt:
--------------------------------------------------------------------------------
1 | package com.app.missednotificationsreminder.util.event
2 |
3 | import kotlinx.coroutines.ExperimentalCoroutinesApi
4 | import kotlinx.coroutines.FlowPreview
5 | import kotlinx.coroutines.flow.Flow
6 | import kotlinx.coroutines.flow.MutableStateFlow
7 | import kotlinx.coroutines.flow.drop
8 | import timber.log.Timber
9 |
10 | /**
11 | * Implementation of the event bus based on the Kotlin Flow technology.
12 | * courtesy: https://gist.github.com/takahirom/f2dbcc3053adfd87ac7e321d95a23021
13 | */
14 | @OptIn(ExperimentalCoroutinesApi::class, FlowPreview::class)
15 | class FlowEventBus {
16 | // If multiple threads are going to emit events to this
17 | // then it must be made thread-safe like this instead
18 | private val _bus = MutableStateFlow(object : Event {})
19 |
20 | /**
21 | * Send the event to all the subscribers
22 | *
23 | * @param event the event to send
24 | */
25 | fun send(event: Event) {
26 | Timber.d("send() called with: event = %s",
27 | event)
28 | _bus.value = event
29 | }
30 |
31 | /**
32 | * Get the flow from the current event bus object
33 | *
34 | * @return
35 | */
36 | fun toFlow(): Flow = _bus.drop(1)
37 | }
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 |
3 | # IDE (e.g. Android Studio) users:
4 | # Gradle settings configured through the IDE *will override*
5 | # any settings specified in this file.
6 |
7 | # For more details on how to configure your build environment visit
8 | # http://www.gradle.org/docs/current/userguide/build_environment.html
9 |
10 | # Specifies the JVM arguments used for the daemon process.
11 | # The setting is particularly useful for tweaking memory settings.
12 | # Default value: -Xmx10248m -XX:MaxPermSize=256m
13 | org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
14 |
15 | # performance optimization
16 | org.gradle.daemon=true
17 | org.gradle.configureondemand=true
18 |
19 | # When configured, Gradle will run in incubating parallel mode.
20 | # This option should only be used with decoupled projects. More details, visit
21 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
22 | org.gradle.parallel=true
23 |
24 | android.enableBuildCache=true
25 | # Enable V2 compiller
26 | android.databinding.incremental=true
27 | android.useAndroidX=true
28 | android.enableJetifier=true
29 | kapt.incremental.apt=true
30 | kapt.use.worker.api=true
31 | org.gradle.caching=true
32 | org.gradle.unsafe.watch-fs=true
--------------------------------------------------------------------------------
/app/src/main/java/com/app/missednotificationsreminder/util/flow/FlowAmb.kt:
--------------------------------------------------------------------------------
1 | package com.app.missednotificationsreminder.util.flow
2 |
3 | import kotlinx.coroutines.FlowPreview
4 | import kotlinx.coroutines.InternalCoroutinesApi
5 | import kotlinx.coroutines.coroutineScope
6 | import kotlinx.coroutines.flow.*
7 | import kotlinx.coroutines.selects.select
8 |
9 | @FlowPreview
10 | internal class FlowAmb(private val flows: Iterable>) : AbstractFlow() {
11 | @InternalCoroutinesApi
12 | override suspend fun collectSafely(collector: FlowCollector) {
13 | coroutineScope {
14 | val channels = flows.map { it.produceIn(this@coroutineScope) }
15 | var selectedChannelIndex = 0
16 | select {
17 | channels.forEachIndexed { index, channel ->
18 | channel.onReceiveOrClosed { value ->
19 | selectedChannelIndex = index
20 | if (!value.isClosed) {
21 | collector.emit(value.value)
22 | }
23 | }
24 | }
25 | }
26 | channels.forEachIndexed { index, channel ->
27 | if (index != selectedChannelIndex) channel.cancel()
28 | }
29 | collector.emitAll(channels[selectedChannelIndex])
30 | }
31 | }
32 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/app/missednotificationsreminder/ui/ActivityHierarchyServer.java:
--------------------------------------------------------------------------------
1 | package com.app.missednotificationsreminder.ui;
2 |
3 | import android.app.Activity;
4 | import android.app.Application;
5 | import android.os.Bundle;
6 |
7 | /**
8 | * A "view server" adaptation which automatically hooks itself up to all activities.
9 | */
10 | public interface ActivityHierarchyServer extends Application.ActivityLifecycleCallbacks {
11 | /**
12 | * An {@link ActivityHierarchyServer} which does nothing.
13 | */
14 | ActivityHierarchyServer NONE = new ActivityHierarchyServer() {
15 | @Override
16 | public void onActivityCreated(Activity activity, Bundle bundle) {
17 | }
18 |
19 | @Override
20 | public void onActivityStarted(Activity activity) {
21 | }
22 |
23 | @Override
24 | public void onActivityResumed(Activity activity) {
25 | }
26 |
27 | @Override
28 | public void onActivityPaused(Activity activity) {
29 | }
30 |
31 | @Override
32 | public void onActivityStopped(Activity activity) {
33 | }
34 |
35 | @Override
36 | public void onActivitySaveInstanceState(Activity activity, Bundle bundle) {
37 | }
38 |
39 | @Override
40 | public void onActivityDestroyed(Activity activity) {
41 | }
42 | };
43 | }
44 |
--------------------------------------------------------------------------------
/app/src/main/java/com/app/missednotificationsreminder/ui/fragment/common/CommonFragmentWithViewBinding.kt:
--------------------------------------------------------------------------------
1 | @file:JvmName("CommonFragmentWithViewBinding")
2 |
3 | package com.app.missednotificationsreminder.ui.fragment.common
4 |
5 | import android.os.Bundle
6 | import android.view.LayoutInflater
7 | import android.view.View
8 | import android.view.ViewGroup
9 | import androidx.annotation.LayoutRes
10 | import androidx.databinding.DataBindingUtil
11 | import androidx.databinding.ViewDataBinding
12 |
13 | open class CommonFragmentWithViewBinding(
14 | @LayoutRes val layoutId: Int,
15 | private val clearBindingOnViewDestroyed: Boolean = true) : CommonFragment() {
16 | private var _binding: T? = null
17 |
18 | // This property is only valid between onCreateView and
19 | // onDestroyView.
20 | protected val viewDataBinding get() = _binding!!
21 |
22 | override fun onCreateView(
23 | inflater: LayoutInflater,
24 | container: ViewGroup?,
25 | savedInstanceState: Bundle?
26 | ): View? {
27 | if (_binding == null) {
28 | _binding = DataBindingUtil.inflate(inflater, layoutId, container, false)
29 | }
30 | return viewDataBinding.root
31 | }
32 |
33 | override fun onDestroyView() {
34 | super.onDestroyView()
35 | if (clearBindingOnViewDestroyed) {
36 | _binding = null
37 | }
38 | }
39 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/app/missednotificationsreminder/ui/fragment/dialog/AlertDialogFragment.kt:
--------------------------------------------------------------------------------
1 | package com.app.missednotificationsreminder.ui.fragment.dialog
2 |
3 | import android.app.Dialog
4 | import android.os.Bundle
5 | import androidx.appcompat.app.AlertDialog
6 | import androidx.navigation.fragment.navArgs
7 | import com.app.missednotificationsreminder.R
8 | import com.app.missednotificationsreminder.data.source.ResourceDataSource
9 | import com.app.missednotificationsreminder.di.qualifiers.FragmentScope
10 | import dagger.android.ContributesAndroidInjector
11 | import dagger.android.support.DaggerDialogFragment
12 | import javax.inject.Inject
13 |
14 | class AlertDialogFragment : DaggerDialogFragment() {
15 | private val args by navArgs()
16 |
17 | @Inject
18 | lateinit var resourcesDataSource: ResourceDataSource
19 |
20 | override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
21 | return AlertDialog.Builder(requireActivity())
22 | .setTitle(args.title)
23 | .setMessage(args.message)
24 | .setPositiveButton(args.okButton
25 | ?: resourcesDataSource.getString(R.string.common_ok_action)) { _, _ -> }
26 | .setNegativeButton(args.cancelButton) { _, _ -> }
27 | .create()
28 | }
29 |
30 | @dagger.Module
31 | abstract class Module {
32 | @FragmentScope
33 | @ContributesAndroidInjector(modules = [])
34 | abstract fun contribute(): AlertDialogFragment
35 | }
36 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/app/missednotificationsreminder/ui/widget/dialog/LifecycleAlertDialog.kt:
--------------------------------------------------------------------------------
1 | package com.app.missednotificationsreminder.ui.widget.dialog
2 |
3 | import android.content.Context
4 | import android.content.DialogInterface
5 | import androidx.appcompat.app.AlertDialog
6 | import androidx.lifecycle.Lifecycle
7 | import androidx.lifecycle.LifecycleOwner
8 | import androidx.lifecycle.LifecycleRegistry
9 | import timber.log.Timber
10 |
11 | open class LifecycleAlertDialog : AlertDialog, LifecycleOwner {
12 | private var lifecycleRegistry = LifecycleRegistry(this)
13 |
14 | protected constructor(context: Context) : super(context) {}
15 | protected constructor(context: Context, themeResId: Int) : super(context, themeResId) {}
16 | protected constructor(context: Context, cancelable: Boolean, cancelListener: DialogInterface.OnCancelListener?) : super(context, cancelable, cancelListener) {}
17 |
18 | override fun onAttachedToWindow() {
19 | Timber.d("onAttachedToWindow() called")
20 | super.onAttachedToWindow()
21 | lifecycleRegistry.currentState = Lifecycle.State.STARTED
22 | }
23 |
24 | override fun onDetachedFromWindow() {
25 | Timber.d("onDetachedFromWindow() called")
26 | if (lifecycleRegistry.currentState != Lifecycle.State.INITIALIZED) {
27 | lifecycleRegistry.currentState = Lifecycle.State.DESTROYED
28 | lifecycleRegistry = LifecycleRegistry(this)
29 | }
30 | }
31 |
32 | override fun getLifecycle(): Lifecycle {
33 | return lifecycleRegistry
34 | }
35 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Missed Notifications Reminder
2 |
3 | Unfortunately Android doesn't have default integrated functionality to notify user periodically about missing calls/messages/other notifications via sound (only LED is blinking). This opensource tool allows to monitor notifications from any applications which are important for You and perform sound reminder periodically
4 |
5 |
6 |
7 |
8 |
9 |
10 | [](http://play.google.com/store/apps/details?id=com.app.missednotificationsreminder)
11 |
12 | ## LICENSE
13 |
14 | Copyright 2015 Eugene Popovich
15 |
16 | Licensed under the Apache License, Version 2.0 (the "License");
17 | you may not use this file except in compliance with the License.
18 | You may obtain a copy of the License at
19 |
20 |
21 |
22 | Unless required by applicable law or agreed to in writing, software
23 | distributed under the License is distributed on an "AS IS" BASIS,
24 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
25 | See the License for the specific language governing permissions and
26 | limitations under the License.
27 |
--------------------------------------------------------------------------------
/app/src/main/java/com/app/missednotificationsreminder/service/ReminderNotificationListenerServiceInterface.kt:
--------------------------------------------------------------------------------
1 | package com.app.missednotificationsreminder.service
2 |
3 | import androidx.lifecycle.LifecycleOwner
4 | import com.app.missednotificationsreminder.service.data.model.NotificationData
5 |
6 | /**
7 | * The reminder service interface for basic notification handling functionality
8 | */
9 | interface ReminderNotificationListenerServiceInterface : LifecycleOwner{
10 | /**
11 | * The method which should be called when any new notification is posted
12 | *
13 | * @param notificationData the posted notification data
14 | */
15 | suspend fun onNotificationPosted(notificationData: NotificationData)
16 |
17 | /**
18 | * The method which should be called when any notification is removed
19 | *
20 | * @param notificationData the removed notification data
21 | */
22 | suspend fun onNotificationRemoved(notificationData: NotificationData)
23 |
24 | /**
25 | * The method which should be called when a notification listener service is ready
26 | */
27 | fun onReady()
28 |
29 | /**
30 | * Get the currently showing notification data
31 | *
32 | * @return
33 | */
34 | val notificationsData: List
35 |
36 | /**
37 | * Get the currently ignoring notification data
38 | * @return
39 | */
40 | val ignoredNotificationsData: List
41 |
42 | val createDismissNotification: Boolean
43 |
44 | /**
45 | * Actualize the notification date
46 | */
47 | suspend fun actualizeNotificationData()
48 | }
--------------------------------------------------------------------------------
/app/src/notificationListener/java/com/app/missednotificationsreminder/service/util/ReminderNotificationListenerServiceUtils.java:
--------------------------------------------------------------------------------
1 | package com.app.missednotificationsreminder.service.util;
2 |
3 | import android.content.ContentResolver;
4 | import android.content.Context;
5 | import android.content.Intent;
6 | import android.provider.Settings;
7 |
8 | import com.app.missednotificationsreminder.service.ReminderNotificationListenerService;
9 |
10 | /**
11 | * Various utility methods related to the {@link ReminderNotificationListenerService}
12 | *
13 | * @author Eugene Popovich
14 | */
15 | public class ReminderNotificationListenerServiceUtils {
16 | /**
17 | * Get the intent which navigates to the notification listener services settings page
18 | *
19 | * @return
20 | */
21 | public static Intent getServiceEnabledManagementIntent() {
22 | return new Intent("android.settings.ACTION_NOTIFICATION_LISTENER_SETTINGS");
23 | }
24 |
25 | /**
26 | * Check whether notification listener service is enabled for current context
27 | *
28 | * @param context
29 | * @param serviceClass
30 | * @return
31 | */
32 | public static boolean isServiceEnabled(Context context, Class> serviceClass) {
33 | ContentResolver contentResolver = context.getContentResolver();
34 | String enabledNotificationListeners = Settings.Secure.getString(contentResolver, "enabled_notification_listeners");
35 | boolean result = enabledNotificationListeners != null && enabledNotificationListeners
36 | .contains(context.getPackageName());
37 | return result;
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/app/src/main/java/com/app/missednotificationsreminder/settings/scheduler/SchedulerViewStatePartialChanges.kt:
--------------------------------------------------------------------------------
1 | package com.app.missednotificationsreminder.settings.scheduler
2 |
3 | import com.app.missednotificationsreminder.binding.model.ViewStatePartialChanges
4 |
5 | sealed class SchedulerViewStatePartialChanges : ViewStatePartialChanges {
6 |
7 | data class EnabledChange(private val newValue: Boolean) : SchedulerViewStatePartialChanges() {
8 | override fun reduce(previousState: SchedulerViewState): SchedulerViewState {
9 | return previousState.copy(enabled = newValue)
10 | }
11 | }
12 |
13 | data class ModeChange(private val newValue: Boolean) : SchedulerViewStatePartialChanges() {
14 | override fun reduce(previousState: SchedulerViewState): SchedulerViewState {
15 | return previousState.copy(mode = newValue)
16 | }
17 | }
18 |
19 | data class BeginChange(private val newValue: Int) : SchedulerViewStatePartialChanges() {
20 | override fun reduce(previousState: SchedulerViewState): SchedulerViewState {
21 | if (newValue == previousState.begin) {
22 | return previousState
23 | }
24 | return previousState.copy(begin = newValue)
25 | }
26 | }
27 |
28 | data class EndChange(private val newValue: Int) : SchedulerViewStatePartialChanges() {
29 | override fun reduce(previousState: SchedulerViewState): SchedulerViewState {
30 | if (newValue == previousState.end) {
31 | return previousState
32 | }
33 | return previousState.copy(end = newValue)
34 | }
35 | }
36 |
37 | }
--------------------------------------------------------------------------------
/app/src/debug/java/com/jakewharton/u2020/ui/bugreport/BugReportDialog.java:
--------------------------------------------------------------------------------
1 | package com.jakewharton.u2020.ui.bugreport;
2 |
3 |
4 | import android.app.Dialog;
5 | import android.content.Context;
6 | import androidx.appcompat.app.AlertDialog;
7 | import android.view.LayoutInflater;
8 | import com.app.missednotificationsreminder.R;
9 |
10 | import static com.jakewharton.u2020.ui.bugreport.BugReportView.Report;
11 | import static com.jakewharton.u2020.ui.bugreport.BugReportView.ReportDetailsListener;
12 |
13 | public final class BugReportDialog extends AlertDialog implements ReportDetailsListener {
14 | public interface ReportListener {
15 | void onBugReportSubmit(Report report);
16 | }
17 |
18 | private ReportListener listener;
19 |
20 | public BugReportDialog(Context context) {
21 | super(context);
22 |
23 | final BugReportView view =
24 | (BugReportView) LayoutInflater.from(context).inflate(R.layout.bugreport_view, null);
25 | view.setBugReportListener(this);
26 |
27 | setTitle("Report a bug");
28 | setView(view);
29 | setButton(Dialog.BUTTON_NEGATIVE, "Cancel", (OnClickListener) null);
30 | setButton(Dialog.BUTTON_POSITIVE, "Submit", (dialog, which) -> {
31 | if (listener != null) {
32 | listener.onBugReportSubmit(view.getReport());
33 | }
34 | });
35 | }
36 |
37 | public void setReportListener(ReportListener listener) {
38 | this.listener = listener;
39 | }
40 |
41 | @Override protected void onStart() {
42 | getButton(Dialog.BUTTON_POSITIVE).setEnabled(false);
43 | }
44 |
45 | @Override public void onStateChanged(boolean valid) {
46 | getButton(Dialog.BUTTON_POSITIVE).setEnabled(valid);
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/app/src/main/java/com/app/missednotificationsreminder/settings/applicationselection/ApplicationItemViewState.kt:
--------------------------------------------------------------------------------
1 | package com.app.missednotificationsreminder.settings.applicationselection
2 |
3 | import android.net.Uri
4 | import timber.log.Timber
5 |
6 | /**
7 | * The class to store application item information used in the [ApplicationsSelectionViewModel]
8 | * @property checked Whether the application is already checked by user
9 | * @property applicationName The application name
10 | * @property packageName The application package name
11 | * @property iconUri The application icon URI
12 | * @property activeNotifications The number of active notifications
13 | */
14 | data class ApplicationItemViewState(
15 | val checked: Boolean,
16 | val applicationName: CharSequence,
17 | val packageName: String,
18 | val iconUri: Uri,
19 | val activeNotifications: Int) {
20 | /**
21 | * Get the application name
22 | *
23 | * @return
24 | */
25 | val name: CharSequence
26 | get() {
27 | Timber.d("getName for %1\$s", toString())
28 | return applicationName
29 | }
30 |
31 | /**
32 | * Get the application description
33 | *
34 | * @return
35 | */
36 | val description: String
37 | get() {
38 | Timber.d("getDescription for %1\$s", toString())
39 | return packageName
40 | }
41 |
42 | val hasActiveNotifications: Boolean
43 | get() {
44 | return activeNotifications > 0
45 | }
46 |
47 | val activeNotificationsInfo: String
48 | get() {
49 | return activeNotifications.toString()
50 | }
51 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/app/missednotificationsreminder/settings/applicationselection/data/model/util/ApplicationIconHandler.kt:
--------------------------------------------------------------------------------
1 | package com.app.missednotificationsreminder.settings.applicationselection.data.model.util
2 |
3 | import android.content.pm.PackageManager
4 | import android.graphics.Bitmap
5 | import android.graphics.Canvas
6 | import com.squareup.picasso.Picasso
7 | import com.squareup.picasso.Request
8 | import com.squareup.picasso.RequestHandler
9 | import timber.log.Timber
10 |
11 | /**
12 | * The application icon picasso handler
13 | */
14 | class ApplicationIconHandler(private val mPackageManager: PackageManager) : RequestHandler() {
15 | override fun canHandleRequest(data: Request): Boolean {
16 | return SCHEME == data.uri.scheme
17 | }
18 |
19 | override fun load(request: Request, networkPolicy: Int): Result? {
20 | return getAppIcon(request.uri.host)?.let {
21 | Result(it, Picasso.LoadedFrom.DISK)
22 | }
23 | }
24 |
25 | private fun getAppIcon(packageName: String?): Bitmap? {
26 | try {
27 | return mPackageManager.getApplicationIcon(packageName)?.let { drawable ->
28 | val bitmap = Bitmap.createBitmap(drawable.intrinsicWidth, drawable.intrinsicHeight, Bitmap.Config.ARGB_8888)
29 | val canvas = Canvas(bitmap)
30 | drawable.setBounds(0, 0, canvas.width, canvas.height)
31 | drawable.draw(canvas)
32 | bitmap
33 | }
34 | } catch (e: Throwable) {
35 | Timber.e(e)
36 | }
37 | return null
38 | }
39 |
40 | companion object {
41 | const val SCHEME = "appicon"
42 | }
43 |
44 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/app/missednotificationsreminder/settings/vibration/VibrationViewStatePartialChanges.kt:
--------------------------------------------------------------------------------
1 | package com.app.missednotificationsreminder.settings.vibration
2 |
3 | import com.app.missednotificationsreminder.binding.model.ViewStatePartialChanges
4 |
5 | sealed class VibrationViewStatePartialChanges : ViewStatePartialChanges {
6 |
7 | data class EnabledChange(private val newValue: Boolean) : VibrationViewStatePartialChanges() {
8 | override fun reduce(previousState: VibrationViewState): VibrationViewState {
9 | return previousState.copy(enabled = newValue)
10 | }
11 | }
12 |
13 | data class PatternChange(private val newValue: String, private val vibrationPatternError: String) : VibrationViewStatePartialChanges() {
14 |
15 | private val vibrationRegexpPattern = "\\s*\\d+(\\s*,\\s*\\d+)*\\s*".toRegex()
16 |
17 | override fun reduce(previousState: VibrationViewState): VibrationViewState {
18 | val validVibrationPattern = newValue.matches(vibrationRegexpPattern)
19 | val filteredPattern = newValue
20 | .takeIf { validVibrationPattern }
21 | ?.replaceFirst("^\\s+".toRegex(), "")?.replaceFirst("\\s+$".toRegex(), "")
22 | ?: newValue
23 | return previousState.copy(
24 | pattern = filteredPattern,
25 | lastValidPattern = if (validVibrationPattern) filteredPattern else previousState.lastValidPattern,
26 | patternError = if (validVibrationPattern)
27 | ""
28 | else
29 | vibrationPatternError)
30 | }
31 | }
32 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/app/missednotificationsreminder/settings/scheduler/SchedulerViewState.kt:
--------------------------------------------------------------------------------
1 | package com.app.missednotificationsreminder.settings.scheduler
2 |
3 | import com.app.missednotificationsreminder.util.TimeUtils
4 |
5 | /**
6 | * @property enabled used to handle scheduler enabled state
7 | * @property mode used to handle scheduler mode
8 | * @property begin used to handle scheduler range begin information
9 | * @property end used to handle scheduler range end information
10 | * @property rangeBegin used to mirror [.begin] field for the RangeBar with the value
11 | * transformation such as RangeBar has 5 minutes interval specified
12 | * @property rangeEnd used to mirror [.end] field for the RangeBar with the value
13 | * transformation such as RangeBar has 5 minutes interval specified
14 | * @property maximum used to provide maximum possible value information to the RangeBar
15 | * @property minimum used to provide minimum possible value information to the RangeBar
16 | */
17 | data class SchedulerViewState(
18 | val enabled: Boolean = false,
19 | val mode: Boolean = true,
20 | val begin: Int = 0,
21 | val end: Int = 0,
22 | val rangeBegin: Int = 0,
23 | val rangeEnd: Int = 0,
24 | val maximum: Int,
25 | val minimum: Int) {
26 | /**
27 | * used to return scheduler range begin information represented as human readable string
28 | */
29 | val beginTime: String
30 | get() = TimeUtils.minutesToTime(begin)
31 |
32 | /**
33 | * used to return scheduler range end information represented as human readable string
34 | */
35 | val endTime: String
36 | get() = TimeUtils.minutesToTime(end)
37 | }
38 |
39 |
--------------------------------------------------------------------------------
/app/src/main/java/com/app/missednotificationsreminder/settings/applicationselection/ApplicationItemViewModel.kt:
--------------------------------------------------------------------------------
1 | package com.app.missednotificationsreminder.settings.applicationselection
2 |
3 | import com.app.missednotificationsreminder.binding.model.BaseViewStateModel
4 | import com.squareup.picasso.Picasso
5 | import com.squareup.picasso.RequestCreator
6 | import kotlinx.coroutines.ExperimentalCoroutinesApi
7 | import timber.log.Timber
8 |
9 | /**
10 | * The view model for the single application item
11 | *
12 | * @property applicationItem the current application item
13 | * @property picasso
14 | * state changed event
15 | */
16 | @ExperimentalCoroutinesApi
17 | class ApplicationItemViewModel(
18 | private val applicationItem: ApplicationItemViewState,
19 | private val picasso: Picasso) : BaseViewStateModel(applicationItem) {
20 |
21 | /**
22 | * Get the application icon request
23 | *
24 | * @return
25 | */
26 | val icon: RequestCreator by lazy {
27 | Timber.d("getIcon for %1\$s", toString())
28 | picasso.load(applicationItem.iconUri)
29 | .fit()
30 | }
31 |
32 | /**
33 | * Reverse checked state. Called when the application item clicked. Method binded directly in
34 | * the layout xml
35 | */
36 | fun onItemClicked() {
37 | Timber.d("onItemClicked for %1\$s", toString())
38 | process(ApplicationItemViewStatePartialChanges.CheckedStateChange(!_viewState.value.checked))
39 | }
40 |
41 | override fun toString(): String {
42 | return "ApplicationItemViewModel: state = ${_viewState.value}"
43 | }
44 | }
--------------------------------------------------------------------------------
/app/src/androidTest/java/com/app/missednotificationsreminder/ApplicationTestRunner.java:
--------------------------------------------------------------------------------
1 | package com.app.missednotificationsreminder;
2 |
3 | import android.app.KeyguardManager;
4 | import android.content.Context;
5 | import android.os.PowerManager;
6 | import androidx.test.runner.AndroidJUnitRunner;
7 |
8 | import static android.content.Context.KEYGUARD_SERVICE;
9 | import static android.content.Context.POWER_SERVICE;
10 | import static android.os.PowerManager.ACQUIRE_CAUSES_WAKEUP;
11 | import static android.os.PowerManager.FULL_WAKE_LOCK;
12 | import static android.os.PowerManager.ON_AFTER_RELEASE;
13 |
14 | public final class ApplicationTestRunner extends AndroidJUnitRunner {
15 | private PowerManager.WakeLock wakeLock;
16 |
17 | @Override
18 | public void onStart() {
19 | // Inform the app we are an instrumentation test before the object graph is initialized.
20 | ApplicationModuleExt.instrumentationTest = true;
21 |
22 | Context app = getTargetContext().getApplicationContext();
23 |
24 | String name = ApplicationTestRunner.class.getSimpleName();
25 | // Unlock the device so that the tests can input keystrokes.
26 | KeyguardManager keyguard = (KeyguardManager) app.getSystemService(KEYGUARD_SERVICE);
27 | keyguard.newKeyguardLock(name).disableKeyguard();
28 | // Wake up the screen.
29 | PowerManager power = (PowerManager) app.getSystemService(POWER_SERVICE);
30 | wakeLock = power.newWakeLock(FULL_WAKE_LOCK | ACQUIRE_CAUSES_WAKEUP | ON_AFTER_RELEASE, name);
31 | wakeLock.acquire();
32 |
33 | super.onStart();
34 | }
35 |
36 | @Override
37 | public void onDestroy() {
38 | super.onDestroy();
39 |
40 | wakeLock.release();
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/app/src/main/java/com/app/missednotificationsreminder/settings/applicationssettings/ApplicationsSettingsViewStatePartialChanges.kt:
--------------------------------------------------------------------------------
1 | package com.app.missednotificationsreminder.settings.applicationssettings
2 |
3 | import com.app.missednotificationsreminder.binding.model.ViewStatePartialChanges
4 |
5 | sealed class ApplicationsSettingsViewStatePartialChanges :
6 | ViewStatePartialChanges {
7 |
8 | data class IgnorePersistentNotificationsChange(private val newValue: Boolean) : ApplicationsSettingsViewStatePartialChanges() {
9 | override fun reduce(previousState: ApplicationsSettingsViewState): ApplicationsSettingsViewState {
10 | return previousState.copy(ignorePersistentNotifications = newValue)
11 | }
12 | }
13 |
14 | data class RespectPhoneCallsChange(private val newValue: Boolean) : ApplicationsSettingsViewStatePartialChanges() {
15 | override fun reduce(previousState: ApplicationsSettingsViewState): ApplicationsSettingsViewState {
16 | return previousState.copy(respectPhoneCalls = newValue)
17 | }
18 | }
19 |
20 | data class RespectRingerModeChange(private val newValue: Boolean) : ApplicationsSettingsViewStatePartialChanges() {
21 | override fun reduce(previousState: ApplicationsSettingsViewState): ApplicationsSettingsViewState {
22 | return previousState.copy(respectRingerMode = newValue)
23 | }
24 | }
25 |
26 | data class RemindWhenScreenIsOnChange(private val newValue: Boolean) : ApplicationsSettingsViewStatePartialChanges() {
27 | override fun reduce(previousState: ApplicationsSettingsViewState): ApplicationsSettingsViewState {
28 | return previousState.copy(remindWhenScreenIsOn = newValue)
29 | }
30 | }
31 |
32 | }
--------------------------------------------------------------------------------
/app/src/main/res/layout/view_settings_card_reminder.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
8 |
9 |
10 |
14 |
15 |
19 |
20 |
25 |
26 |
30 |
31 |
35 |
36 |
42 |
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/app/src/main/java/com/app/missednotificationsreminder/payment/PurchaseViewStatePartialChanges.kt:
--------------------------------------------------------------------------------
1 | package com.app.missednotificationsreminder.payment
2 |
3 | import com.app.missednotificationsreminder.R
4 | import com.app.missednotificationsreminder.binding.model.ViewStatePartialChanges
5 | import com.app.missednotificationsreminder.common.domain.entities.ResultWrapper
6 | import com.app.missednotificationsreminder.data.source.ResourceDataSource
7 | import com.app.missednotificationsreminder.payment.model.Purchase
8 | import com.app.missednotificationsreminder.util.loadingstate.LoadingState
9 |
10 | sealed class PurchaseViewStatePartialChanges : ViewStatePartialChanges {
11 | data class LoadingStateChange(
12 | private val newValue: LoadingState) : PurchaseViewStatePartialChanges() {
13 | override fun reduce(previousState: PurchaseViewState): PurchaseViewState {
14 | return previousState.copy(loadingState = newValue)
15 | }
16 | }
17 |
18 | data class DataStateChange(
19 | private val newValue: ResultWrapper>
20 | ) : PurchaseViewStatePartialChanges() {
21 | override fun reduce(previousState: PurchaseViewState): PurchaseViewState {
22 | return previousState.copy(data = newValue)
23 | }
24 | }
25 |
26 | data class PurchasesUpdated(
27 | private val newValue: List, private val resourceDataSource: ResourceDataSource) : PurchaseViewStatePartialChanges() {
28 | override fun reduce(previousState: PurchaseViewState): PurchaseViewState {
29 | return previousState.copy(purchases = resourceDataSource.getString(
30 | R.string.payment_contributions,
31 | newValue.joinToString(", ") { it.price }))
32 | }
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/app/src/debug/java/com/jakewharton/u2020/ui/debug/AnimationSpeedAdapter.java:
--------------------------------------------------------------------------------
1 | package com.jakewharton.u2020.ui.debug;
2 |
3 | import android.content.Context;
4 | import android.view.LayoutInflater;
5 | import android.view.View;
6 | import android.view.ViewGroup;
7 | import android.widget.TextView;
8 | import com.jakewharton.u2020.ui.misc.BindableAdapter;
9 |
10 |
11 | class AnimationSpeedAdapter extends BindableAdapter {
12 | private static final int[] VALUES = {
13 | 1, 2, 3, 5, 10
14 | };
15 |
16 | public static int getPositionForValue(int value) {
17 | for (int i = 0; i < VALUES.length; i++) {
18 | if (VALUES[i] == value) {
19 | return i;
20 | }
21 | }
22 | return 0; // Default to 1x if something changes.
23 | }
24 |
25 | AnimationSpeedAdapter(Context context) {
26 | super(context);
27 | }
28 |
29 | @Override public int getCount() {
30 | return VALUES.length;
31 | }
32 |
33 | @Override public Integer getItem(int position) {
34 | return VALUES[position];
35 | }
36 |
37 | @Override public long getItemId(int position) {
38 | return position;
39 | }
40 |
41 | @Override public View newView(LayoutInflater inflater, int position, ViewGroup container) {
42 | return inflater.inflate(android.R.layout.simple_spinner_item, container, false);
43 | }
44 |
45 | @Override public void bindView(Integer item, int position, View view) {
46 | TextView tv = view.findViewById(android.R.id.text1);
47 | if (item == 1) {
48 | tv.setText("Normal");
49 | } else {
50 | tv.setText(item + "x slower");
51 | }
52 | }
53 |
54 | @Override
55 | public View newDropDownView(LayoutInflater inflater, int position, ViewGroup container) {
56 | return inflater.inflate(android.R.layout.simple_spinner_dropdown_item, container, false);
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/menu_settings_fragment.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/app/src/debug/res/layout/bugreport_view.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
19 |
27 |
37 |
47 |
48 |
--------------------------------------------------------------------------------
/app/src/main/java/com/app/missednotificationsreminder/util/coroutines/CoroutinesExtensions.kt:
--------------------------------------------------------------------------------
1 | package com.app.missednotificationsreminder.util.coroutines
2 |
3 | import com.app.missednotificationsreminder.common.domain.entities.ResultWrapper
4 | import com.app.missednotificationsreminder.common.domain.entities.asResultWrapper
5 | import kotlinx.coroutines.CoroutineScope
6 | import kotlinx.coroutines.Job
7 | import kotlinx.coroutines.delay
8 | import kotlinx.coroutines.launch
9 | import timber.log.Timber
10 |
11 | fun debounce(
12 | waitMs: Long,
13 | coroutineScope: CoroutineScope,
14 | destinationFunction: (T) -> Unit
15 | ): (T) -> Unit {
16 | var debounceJob: Job? = null
17 | return { param: T ->
18 | debounceJob?.cancel()
19 | debounceJob = coroutineScope.launch {
20 | delay(waitMs)
21 | destinationFunction(param)
22 | }
23 | }
24 | }
25 |
26 | /**
27 | * Retry call in case of error until [maxRetryCount] is reached. In addition log some verbose information
28 | */
29 | suspend fun retryCallOnError(maxRetryCount: Int, block: suspend () -> T): ResultWrapper {
30 | require(maxRetryCount >= 0)
31 | var result: Result? = null
32 | for (count in 0..maxRetryCount) {
33 | if (count > 0) {
34 | Timber.d("retryCallOnError: retry")
35 | }
36 | result = runCatching { block() }
37 | if (result.isFailure) {
38 | Timber.e(result.exceptionOrNull(), "retryCallOnError")
39 | Timber.d("retryCallOnError: count=%d; maxRetryCount=%d", count, maxRetryCount)
40 | } else {
41 | break
42 | }
43 | }
44 | require(result != null)
45 | if (result.isFailure) {
46 | Timber.d("retryCallOnError: don't retry, max retry count reached")
47 | }
48 | return result.asResultWrapper()
49 | }
50 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
16 |
17 |
22 |
23 |
31 |
32 |
33 |
40 |
41 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/view_settings_card_sound.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
8 |
9 |
10 |
13 |
14 |
15 |
20 |
21 |
25 |
26 |
31 |
32 |
36 |
37 |
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/app/src/main/java/com/app/missednotificationsreminder/service/data/model/NotificationData.kt:
--------------------------------------------------------------------------------
1 | package com.app.missednotificationsreminder.service.data.model
2 |
3 | /**
4 | * The class to store notification information
5 | */
6 | open class NotificationData(
7 | /**
8 | * The notification id
9 | */
10 | val id: String,
11 | /**
12 | * The notification related application package name
13 | */
14 | val packageName: String,
15 | /**
16 | * The time when notification has been found
17 | */
18 | val foundAtTime: Long,
19 | /**
20 | * Notification specific flags
21 | */
22 | val flags: Int) {
23 |
24 | override fun equals(other: Any?): Boolean {
25 | if (this === other) return true
26 | if (javaClass != other?.javaClass) return false
27 |
28 | other as NotificationData
29 |
30 | if (id != other.id) return false
31 | if (packageName != other.packageName) return false
32 | if (foundAtTime != other.foundAtTime) return false
33 | if (flags != other.flags) return false
34 |
35 | return true
36 | }
37 |
38 |
39 | override fun toString(): String {
40 | return StringBuilder()
41 | .append(className).append("{")
42 | .append(fieldsAsString())
43 | .append("}")
44 | .toString()
45 | }
46 |
47 | protected open val className: String
48 | get() = "NotificationData"
49 |
50 | protected open fun fieldsAsString(): String {
51 | return StringBuilder()
52 | .append("id=").append(id)
53 | .append(", packageName='").append(packageName).append('\'')
54 | .append(", foundAtTime=").append(foundAtTime)
55 | .append(", flags=").append(flags)
56 | .toString()
57 | }
58 | }
--------------------------------------------------------------------------------
/app/src/debug/res/layout/debug_activity_frame.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
11 |
17 |
22 |
27 |
28 |
29 |
37 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
13 |
14 |
19 |
20 |
21 |
22 |
27 |
28 |
31 |
32 |
35 |
36 |
37 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/app/src/main/java/com/app/missednotificationsreminder/service/RemindJob.kt:
--------------------------------------------------------------------------------
1 | package com.app.missednotificationsreminder.service
2 |
3 | import android.content.Context
4 | import androidx.work.CoroutineWorker
5 | import androidx.work.WorkerParameters
6 | import com.app.missednotificationsreminder.di.Injector.Companion.obtain
7 | import com.app.missednotificationsreminder.service.event.RemindEvents
8 | import com.app.missednotificationsreminder.util.event.FlowEventBus
9 | import dagger.android.AndroidInjector
10 | import dagger.android.ContributesAndroidInjector
11 | import kotlinx.coroutines.ExperimentalCoroutinesApi
12 | import kotlinx.coroutines.FlowPreview
13 | import kotlinx.coroutines.coroutineScope
14 | import kotlinx.coroutines.flow.*
15 | import timber.log.Timber
16 | import javax.inject.Inject
17 |
18 | /**
19 | * The remind job
20 | */
21 | @OptIn(FlowPreview::class, ExperimentalCoroutinesApi::class)
22 | class RemindJob(context: Context, params: WorkerParameters)
23 | : CoroutineWorker(context, params) {
24 | init {
25 | Timber.d("RemindJob() called with: params = %s",
26 | params)
27 | // inject dependencies
28 | val appGraph: AndroidInjector? = obtain(context.applicationContext)
29 | appGraph!!.inject(this)
30 | }
31 |
32 | @Inject
33 | lateinit var mEventBus: FlowEventBus
34 |
35 | override suspend fun doWork(): Result = coroutineScope {
36 | Timber.d("doWork() called")
37 | mEventBus.toFlow()
38 | .filter { event -> event === RemindEvents.REMINDER_COMPLETED }
39 | .onStart { mEventBus.send(RemindEvents.REMIND) }
40 | .first()
41 | Timber.d("doWork() done")
42 | Result.success()
43 | }
44 |
45 | @dagger.Module
46 | abstract class Module {
47 | @ContributesAndroidInjector
48 | abstract fun contribute(): RemindJob
49 | }
50 |
51 | companion object {
52 | const val TAG = "REMIND_JOB"
53 | }
54 | }
--------------------------------------------------------------------------------
/app/src/debug/java/com/jakewharton/u2020/ui/debug/HierarchyTreeChangeListener.java:
--------------------------------------------------------------------------------
1 | package com.jakewharton.u2020.ui.debug;
2 |
3 | import android.view.View;
4 | import android.view.ViewGroup;
5 |
6 | /**
7 | * A {@link ViewGroup.OnHierarchyChangeListener hierarchy change listener} which recursively
8 | * monitors an entire tree of views.
9 | */
10 | public final class HierarchyTreeChangeListener implements ViewGroup.OnHierarchyChangeListener {
11 | /**
12 | * Wrap a regular {@link ViewGroup.OnHierarchyChangeListener hierarchy change listener} with one
13 | * that monitors an entire tree of views.
14 | */
15 | public static HierarchyTreeChangeListener wrap(ViewGroup.OnHierarchyChangeListener delegate) {
16 | return new HierarchyTreeChangeListener(delegate);
17 | }
18 |
19 | private final ViewGroup.OnHierarchyChangeListener delegate;
20 |
21 | private HierarchyTreeChangeListener(ViewGroup.OnHierarchyChangeListener delegate) {
22 | if (delegate == null) {
23 | throw new NullPointerException("Delegate must not be null.");
24 | }
25 | this.delegate = delegate;
26 | }
27 |
28 | @Override public void onChildViewAdded(View parent, View child) {
29 | delegate.onChildViewAdded(parent, child);
30 |
31 | if (child instanceof ViewGroup) {
32 | ViewGroup childGroup = (ViewGroup) child;
33 | childGroup.setOnHierarchyChangeListener(this);
34 | for (int i = 0; i < childGroup.getChildCount(); i++) {
35 | onChildViewAdded(childGroup, childGroup.getChildAt(i));
36 | }
37 | }
38 | }
39 |
40 | @Override public void onChildViewRemoved(View parent, View child) {
41 | if (child instanceof ViewGroup) {
42 | ViewGroup childGroup = (ViewGroup) child;
43 | for (int i = 0; i < childGroup.getChildCount(); i++) {
44 | onChildViewRemoved(childGroup, childGroup.getChildAt(i));
45 | }
46 | childGroup.setOnHierarchyChangeListener(null);
47 | }
48 |
49 | delegate.onChildViewRemoved(parent, child);
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/app/src/debug/java/com/jakewharton/u2020/util/Intents.java:
--------------------------------------------------------------------------------
1 | package com.jakewharton.u2020.util;
2 |
3 | import android.content.Context;
4 | import android.content.Intent;
5 | import android.content.pm.ResolveInfo;
6 | import android.widget.Toast;
7 |
8 | import com.app.missednotificationsreminder.R;
9 |
10 | import java.util.List;
11 |
12 | import static android.widget.Toast.LENGTH_LONG;
13 |
14 | public final class Intents {
15 | /**
16 | * Attempt to launch the supplied {@link Intent}. Queries on-device packages before launching and
17 | * will display a simple message if none are available to handle it.
18 | */
19 | public static boolean maybeStartActivity(Context context, Intent intent) {
20 | return maybeStartActivity(context, intent, false);
21 | }
22 |
23 | /**
24 | * Attempt to launch Android's chooser for the supplied {@link Intent}. Queries on-device
25 | * packages before launching and will display a simple message if none are available to handle
26 | * it.
27 | */
28 | public static boolean maybeStartChooser(Context context, Intent intent) {
29 | return maybeStartActivity(context, intent, true);
30 | }
31 |
32 | private static boolean maybeStartActivity(Context context, Intent intent, boolean chooser) {
33 | if (hasHandler(context, intent)) {
34 | if (chooser) {
35 | intent = Intent.createChooser(intent, null);
36 | }
37 | context.startActivity(intent);
38 | return true;
39 | } else {
40 | Toast.makeText(context, R.string.no_intent_handler, LENGTH_LONG).show();
41 | return false;
42 | }
43 | }
44 |
45 | /**
46 | * Queries on-device packages for a handler for the supplied {@link Intent}.
47 | */
48 | private static boolean hasHandler(Context context, Intent intent) {
49 | List handlers = context.getPackageManager().queryIntentActivities(intent, 0);
50 | return !handlers.isEmpty();
51 | }
52 |
53 | private Intents() {
54 | throw new AssertionError("No instances.");
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/app/src/main/java/com/app/missednotificationsreminder/payment/billing/domain/repository/PurchaseRepository.kt:
--------------------------------------------------------------------------------
1 | package com.app.missednotificationsreminder.payment.billing.domain.repository
2 |
3 | import android.app.Activity
4 | import com.app.missednotificationsreminder.common.domain.entities.ResultWrapper
5 | import com.app.missednotificationsreminder.payment.billing.domain.entities.ConsumeResult
6 | import com.app.missednotificationsreminder.payment.billing.domain.entities.Purchase
7 | import com.app.missednotificationsreminder.payment.billing.domain.entities.SkuDetails
8 | import com.app.missednotificationsreminder.payment.billing.domain.entities.SkuType
9 |
10 | interface PurchaseRepository {
11 | /**
12 | * Get the sku details for the specified [skuList] of the [skuType] product type
13 | */
14 | suspend fun getSkuDetails(
15 | skuList: List,
16 | skuType: SkuType
17 | ): ResultWrapper>
18 |
19 | /**
20 | * Launch the purchase flow for the specified product details
21 | */
22 | suspend fun purchase(
23 | skuDetails: SkuDetails,
24 | oldSku: String? = null,
25 | oldPurchaseToken: String? = null,
26 | userId: String? = null,
27 | activity: Activity
28 | ): ResultWrapper>
29 |
30 | /**
31 | * Query purchases for the specified [skuType]
32 | */
33 | suspend fun queryPurchases(skuType: SkuType): ResultWrapper>
34 |
35 | /**
36 | * Acknowledge [purchase] to avoid purchase transaction rollback
37 | */
38 | suspend fun acknowledgePurchase(purchase: Purchase): ResultWrapper
39 |
40 | /**
41 | * Consume the [purchase] so user may but it again
42 | */
43 | suspend fun consumePurchase(purchase: Purchase): ResultWrapper
44 |
45 | /**
46 | * Check whether there are any unhandled purchases and try to handle them
47 | */
48 | suspend fun verifyAndConsumePendingPurchases(): ConsumeResult
49 | }
50 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_sound.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
9 |
12 |
13 |
14 |
18 |
19 |
29 |
30 |
37 |
38 |
45 |
46 |
47 |
48 |
49 |
--------------------------------------------------------------------------------
/app/src/proprietary/java/com/app/missednotificationsreminder/payment/billing/data/utls/BillingErrorUtils.kt:
--------------------------------------------------------------------------------
1 | package com.app.missednotificationsreminder.payment.billing.data.utls
2 |
3 | import com.android.billingclient.api.BillingClient
4 | import com.app.missednotificationsreminder.R
5 | import com.app.missednotificationsreminder.common.domain.entities.ResultWrapper
6 | import com.app.missednotificationsreminder.data.source.ResourceDataSource
7 | import com.app.missednotificationsreminder.payment.billing.data.source.remote.BillingOperationException
8 | import com.app.missednotificationsreminder.payment.billing.domain.entities.BillingErrorCodes
9 |
10 | /**
11 | * Handle billing error when detected
12 | **/
13 | fun Throwable?.handleBillingError(resourceDataSource: ResourceDataSource): ResultWrapper {
14 | val error = this
15 | if (error is BillingOperationException) {
16 | when (error.code) {
17 | BillingClient.BillingResponseCode.BILLING_UNAVAILABLE ->
18 | return ResultWrapper.Error(error, BillingErrorCodes.BILLING_UNAVAILABLE, resourceDataSource.getString(R.string.payment_error_billing_unavailable))
19 | BillingClient.BillingResponseCode.SERVICE_UNAVAILABLE ->
20 | return ResultWrapper.Error(error, BillingErrorCodes.SERVICE_UNAVAILABLE, resourceDataSource.getString(R.string.payment_error_service_unavailable))
21 | BillingClient.BillingResponseCode.USER_CANCELED ->
22 | return ResultWrapper.Error(error, BillingErrorCodes.USER_CANCELED, resourceDataSource.getString(R.string.payment_error_user_canceled))
23 | BillingClient.BillingResponseCode.ITEM_ALREADY_OWNED ->
24 | return ResultWrapper.Error(error, BillingErrorCodes.ITEM_ALREADY_OWNED, resourceDataSource.getString(R.string.payment_error_purchase_pending))
25 | }
26 | }
27 | return ResultWrapper.Error(error)
28 | }
29 |
30 | fun ResultWrapper.Error.handleBillingError(resourceDataSource: ResourceDataSource): ResultWrapper {
31 | return throwable.handleBillingError(resourceDataSource)
32 | }
33 |
--------------------------------------------------------------------------------
/app/src/noProprietary/java/com/app/missednotificationsreminder/payment/billing/data/PurchaseRepositoryImpl.kt:
--------------------------------------------------------------------------------
1 | package com.app.missednotificationsreminder.payment.billing.data
2 |
3 | import android.app.Activity
4 | import com.app.missednotificationsreminder.R
5 | import com.app.missednotificationsreminder.common.domain.entities.ResultWrapper
6 | import com.app.missednotificationsreminder.data.source.ResourceDataSource
7 | import com.app.missednotificationsreminder.payment.billing.domain.entities.*
8 | import com.app.missednotificationsreminder.payment.billing.domain.repository.PurchaseRepository
9 |
10 | class PurchaseRepositoryImpl(
11 | resourceDataSource: ResourceDataSource
12 | ) : PurchaseRepository {
13 |
14 | private val defaultAnswer = ResultWrapper.Error(
15 | throwable = null,
16 | code = BillingErrorCodes.BILLING_UNAVAILABLE,
17 | message = resourceDataSource.getString(R.string.payment_error_billing_unavailable)
18 | )
19 |
20 | override suspend fun getSkuDetails(
21 | skuList: List,
22 | skuType: SkuType
23 | ): ResultWrapper> {
24 | return defaultAnswer
25 | }
26 |
27 | override suspend fun purchase(
28 | skuDetails: SkuDetails,
29 | oldSku: String?,
30 | oldPurchaseToken: String?,
31 | userId: String?,
32 | activity: Activity
33 | ): ResultWrapper> {
34 | return defaultAnswer
35 | }
36 |
37 | override suspend fun queryPurchases(skuType: SkuType): ResultWrapper> {
38 | return defaultAnswer
39 | }
40 |
41 | override suspend fun acknowledgePurchase(purchase: Purchase): ResultWrapper {
42 | return defaultAnswer
43 | }
44 |
45 | override suspend fun consumePurchase(purchase: Purchase): ResultWrapper {
46 | return defaultAnswer
47 | }
48 |
49 | override suspend fun verifyAndConsumePendingPurchases(): ConsumeResult {
50 | return ConsumeResult(skuDetails = emptyList(), operationStatus = defaultAnswer)
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/app/src/main/java/com/app/missednotificationsreminder/util/ViewUtils.kt:
--------------------------------------------------------------------------------
1 | package com.app.missednotificationsreminder.util
2 |
3 | import android.view.View
4 | import android.view.ViewGroup
5 | import androidx.core.view.ViewCompat
6 | import androidx.core.view.WindowInsetsCompat
7 |
8 | fun View.doOnApplyWindowInsets(f: (View, WindowInsetsCompat, InitialPadding, InitialMargins) -> Unit) {
9 | // Create a snapshot of the view's padding state
10 | val initialPadding = recordInitialPadding()
11 | val initialMargins = recordInitialMargins()
12 | // Set an actual OnApplyWindowInsetsListener which proxies to the given
13 | // lambda, also passing in the original padding state
14 | ViewCompat.setOnApplyWindowInsetsListener(this) { v, insets ->
15 | f(v, insets, initialPadding, initialMargins)
16 | // Always return the insets, so that children can also use them
17 | insets
18 | }
19 | // request some insets
20 | requestApplyInsetsWhenAttached()
21 | }
22 |
23 | data class InitialPadding(val left: Int, val top: Int,
24 | val right: Int, val bottom: Int)
25 |
26 | data class InitialMargins(val left: Int, val top: Int,
27 | val right: Int, val bottom: Int)
28 |
29 | fun View.recordInitialPadding() = InitialPadding(
30 | paddingLeft, paddingTop, paddingRight, paddingBottom)
31 |
32 | fun View.recordInitialMargins(): InitialMargins =
33 | with(layoutParams as ViewGroup.MarginLayoutParams) {
34 | return InitialMargins(leftMargin, topMargin, rightMargin, bottomMargin)
35 | }
36 |
37 | fun View.requestApplyInsetsWhenAttached() {
38 | if (ViewCompat.isAttachedToWindow(this)) {
39 | ViewCompat.requestApplyInsets(this)
40 | } else {
41 | addOnAttachStateChangeListener(object : View.OnAttachStateChangeListener {
42 | override fun onViewAttachedToWindow(v: View) {
43 | v.removeOnAttachStateChangeListener(this)
44 | ViewCompat.requestApplyInsets(v)
45 | }
46 |
47 | override fun onViewDetachedFromWindow(v: View) {}
48 | })
49 | }
50 | }
--------------------------------------------------------------------------------
/app/src/proprietary/java/com/app/missednotificationsreminder/payment/billing/data/source/remote/BillingOperationException.kt:
--------------------------------------------------------------------------------
1 | package com.app.missednotificationsreminder.payment.billing.data.source.remote
2 |
3 | import com.android.billingclient.api.BillingResult
4 |
5 | sealed class BillingOperationException(val code: Int, message: String) : Exception(message) {
6 |
7 | constructor(billingResult: BillingResult) : this(billingResult.responseCode, billingResult.debugMessage)
8 |
9 | override fun toString(): String {
10 | return StringBuilder()
11 | .append(getClassName() + "{")
12 | .append("code=").append(code)
13 | .append("}")
14 | .toString()
15 | }
16 |
17 | private fun getClassName() = this::class.simpleName
18 | }
19 |
20 | class AcknowledgePurchaseFailureException : BillingOperationException {
21 | constructor(billingResult: BillingResult) : super(billingResult)
22 | constructor(code: Int, message: String) : super(code, message)
23 | }
24 |
25 | class ConnectionFailureException : BillingOperationException {
26 | constructor(billingResult: BillingResult) : super(billingResult)
27 | constructor(code: Int, message: String) : super(code, message)
28 | }
29 |
30 | class ConsumePurchaseFailureException : BillingOperationException {
31 | constructor(billingResult: BillingResult) : super(billingResult)
32 | constructor(code: Int, message: String) : super(code, message)
33 | }
34 |
35 | class PurchaseFailureException : BillingOperationException {
36 | constructor(billingResult: BillingResult) : super(billingResult)
37 | constructor(code: Int, message: String) : super(code, message)
38 | }
39 |
40 | class QueryPurchaseFailureException : BillingOperationException {
41 | constructor(billingResult: BillingResult) : super(billingResult)
42 | constructor(code: Int, message: String) : super(code, message)
43 | }
44 |
45 | class SkuDetailsFailureException : BillingOperationException {
46 | constructor(billingResult: BillingResult) : super(billingResult)
47 | constructor(code: Int, message: String) : super(code, message)
48 | }
49 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/view_settings_card_scheduler.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
8 |
9 |
10 |
13 |
14 |
15 |
20 |
21 |
25 |
26 |
31 |
32 |
36 |
37 |
41 |
42 |
48 |
49 |
50 |
51 |
52 |
--------------------------------------------------------------------------------
/app/src/proprietary/java/com/app/missednotificationsreminder/payment/billing/data/utls/ResultWrapperUtils.kt:
--------------------------------------------------------------------------------
1 | package com.app.missednotificationsreminder.payment.billing.data.utls
2 |
3 | import com.app.missednotificationsreminder.common.domain.entities.ResultWrapper
4 | import com.app.missednotificationsreminder.common.domain.entities.ResultWrapper.Error
5 | import com.app.missednotificationsreminder.common.domain.entities.map
6 | import com.app.missednotificationsreminder.common.domain.entities.succeeded
7 | import kotlinx.coroutines.flow.Flow
8 | import kotlinx.coroutines.flow.fold
9 | import kotlinx.coroutines.flow.transformWhile
10 |
11 |
12 | /**
13 | * Collect the `Flow` results until last or [Error] value is emitted. Collect
14 | * data using [collector].
15 | *
16 | * Note, that this function rethrows any [Throwable] exception thrown by [collector] function.
17 | */
18 | /**
19 | * Collect the `Flow` results until last or [Error] value is emitted. Collect
20 | * data using [collector].
21 | *
22 | * Note, that this function rethrows any [Throwable] exception thrown by [collector] function.
23 | */
24 | suspend fun Flow>.collectWithLastErrorOrSuccessStatusSimple(
25 | defaultValue: ResultWrapper,
26 | collector: (R, T) -> R
27 | ): ResultWrapper {
28 | return collectWithLastErrorOrSuccessStatus(
29 | defaultValue,
30 | { it.succeeded })
31 | { mergedValue, value ->
32 | value.map {
33 | val mergedData = (mergedValue as ResultWrapper.Success).data
34 | collector(mergedData, it)
35 | }
36 | }
37 | }
38 |
39 | /**
40 | * Collect the `Flow` results until last or not succeeded value is emitted. Collect
41 | * data using [collector].
42 | *
43 | * Note, that this function rethrows any [Throwable] exception thrown by [collector] function.
44 | */
45 | suspend fun Flow.collectWithLastErrorOrSuccessStatus(
46 | defaultValue: R,
47 | succeededTest: (T) -> Boolean,
48 | collector: suspend (R, T) -> R
49 | ): R {
50 | return transformWhile {
51 | emit(it)
52 | succeededTest(it)
53 | }
54 | .fold(defaultValue, collector)
55 | }
56 |
--------------------------------------------------------------------------------
/app/src/main/java/com/app/missednotificationsreminder/settings/vibration/VibrationFragment.kt:
--------------------------------------------------------------------------------
1 | package com.app.missednotificationsreminder.settings.vibration
2 |
3 | import android.os.Bundle
4 | import android.view.View
5 | import androidx.fragment.app.viewModels
6 | import androidx.lifecycle.ViewModel
7 | import androidx.lifecycle.ViewModelProvider
8 | import androidx.lifecycle.asLiveData
9 | import com.app.missednotificationsreminder.R
10 | import com.app.missednotificationsreminder.databinding.FragmentVibrationBinding
11 | import com.app.missednotificationsreminder.di.ViewModelKey
12 | import com.app.missednotificationsreminder.ui.fragment.common.CommonFragmentWithViewBinding
13 | import dagger.Binds
14 | import dagger.android.ContributesAndroidInjector
15 | import dagger.multibindings.IntoMap
16 | import kotlinx.coroutines.ExperimentalCoroutinesApi
17 | import kotlinx.coroutines.FlowPreview
18 | import javax.inject.Inject
19 |
20 | /**
21 | * Fragment which displays vibration settings view
22 | */
23 | @FlowPreview
24 | @ExperimentalCoroutinesApi
25 | class VibrationFragment : CommonFragmentWithViewBinding(R.layout.fragment_vibration) {
26 | @Inject
27 | lateinit var viewModelFactory: ViewModelProvider.Factory
28 |
29 | private val viewModel by viewModels { viewModelFactory }
30 |
31 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
32 | super.onViewCreated(view, savedInstanceState)
33 | init()
34 | }
35 |
36 | private fun init() {
37 | viewDataBinding.apply {
38 | // Set the lifecycle owner to the lifecycle of the view
39 | lifecycleOwner = viewLifecycleOwner
40 | viewModel = this@VibrationFragment.viewModel
41 | viewState = this@VibrationFragment.viewModel.viewState.asLiveData()
42 | }
43 | }
44 |
45 | @dagger.Module
46 | abstract class Module {
47 | @ContributesAndroidInjector
48 | abstract fun contribute(): VibrationFragment
49 |
50 | @Binds
51 | @IntoMap
52 | @ViewModelKey(VibrationViewModel::class)
53 | internal abstract fun bindViewModel(viewModel: VibrationViewModel): ViewModel
54 | }
55 | }
--------------------------------------------------------------------------------
/app/src/main/res/layout/view_settings_card_vibration.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
8 |
9 |
12 |
13 |
14 |
19 |
20 |
24 |
25 |
30 |
31 |
35 |
36 |
40 |
41 |
47 |
48 |
49 |
50 |
51 |
--------------------------------------------------------------------------------
/app/src/main/java/com/app/missednotificationsreminder/ApplicationModule.kt:
--------------------------------------------------------------------------------
1 | package com.app.missednotificationsreminder
2 |
3 | import android.app.Application
4 | import android.content.Context
5 | import android.media.AudioManager
6 | import android.os.Vibrator
7 | import com.app.missednotificationsreminder.data.DataModule
8 | import com.app.missednotificationsreminder.di.ViewModelBuilder
9 | import com.app.missednotificationsreminder.di.qualifiers.ForApplication
10 | import com.app.missednotificationsreminder.payment.di.PurchaseDataModule
11 | import com.app.missednotificationsreminder.service.RemindJob
12 | import com.app.missednotificationsreminder.service.ReminderNotificationListenerService
13 | import com.app.missednotificationsreminder.settings.MainActivity
14 | import com.app.missednotificationsreminder.ui.UiModule
15 | import dagger.Module
16 | import dagger.Provides
17 | import javax.inject.Singleton
18 |
19 | /**
20 | * The Dagger dependency injection module for the application
21 | */
22 | @Module(includes = [
23 | UiModule::class,
24 | DataModule::class,
25 | MainActivity.Module::class,
26 | ReminderNotificationListenerService.Module::class,
27 | RemindJob.Module::class,
28 | ApplicationModuleExt::class])
29 | class ApplicationModule {
30 | @Provides
31 | @Singleton
32 | fun provideApplication(@ForApplication context: Context): Application {
33 | return context as Application
34 | }
35 |
36 | /**
37 | * Allow the application context to be injected but require that it be annotated with
38 | * [@ForApplication][ForApplication] to explicitly differentiate it from an activity context.
39 | */
40 | @Provides
41 | @Singleton
42 | @ForApplication
43 | fun provideApplicationContext(context: Context): Context {
44 | return context.applicationContext
45 | }
46 |
47 | @Provides
48 | @Singleton
49 | fun provideVibrator(@ForApplication context: Context): Vibrator {
50 | return context.getSystemService(Context.VIBRATOR_SERVICE) as Vibrator
51 | }
52 |
53 | @Provides
54 | @Singleton
55 | fun provideAudioManager(@ForApplication context: Context): AudioManager {
56 | return context.getSystemService(Context.AUDIO_SERVICE) as AudioManager
57 | }
58 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/app/missednotificationsreminder/ui/activity/common/CommonActivityLifecycleCallback.kt:
--------------------------------------------------------------------------------
1 | package com.app.missednotificationsreminder.ui.activity.common
2 |
3 | import android.app.Activity
4 | import android.app.Application
5 | import android.os.Build
6 | import android.os.Bundle
7 | import android.view.View
8 | import com.app.missednotificationsreminder.util.resources.resolveBooleanAttribute
9 | import timber.log.Timber
10 |
11 | class CommonActivityLifecycleCallback : Application.ActivityLifecycleCallbacks {
12 | override fun onActivityPaused(activity: Activity) {
13 | }
14 |
15 | override fun onActivityStarted(activity: Activity) {
16 | }
17 |
18 | override fun onActivityDestroyed(activity: Activity) {
19 | }
20 |
21 | override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) {
22 | }
23 |
24 | override fun onActivityStopped(activity: Activity) {
25 | }
26 |
27 | override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {
28 | Timber.d("onActivityCreated() called")
29 | with(activity) {
30 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
31 | val decorView: View = window.decorView
32 | decorView.systemUiVisibility = decorView.systemUiVisibility or View.SYSTEM_UI_FLAG_LAYOUT_STABLE or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
33 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
34 | val lightNavigationBar: Boolean = resolveBooleanAttribute(android.R.attr.windowLightNavigationBar, false)
35 | if (!lightNavigationBar) {
36 | decorView.systemUiVisibility = decorView.systemUiVisibility and View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR.inv()
37 | }
38 | val lightStatusBar: Boolean = resolveBooleanAttribute(android.R.attr.windowLightNavigationBar, false)
39 | if (!lightStatusBar) {
40 | decorView.systemUiVisibility = decorView.systemUiVisibility and View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR.inv()
41 | }
42 | }
43 | }
44 | }
45 | }
46 |
47 | override fun onActivityResumed(activity: Activity) {
48 | }
49 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/app/missednotificationsreminder/util/flow/FlowBufferTimeout.kt:
--------------------------------------------------------------------------------
1 | package com.app.missednotificationsreminder.util.flow
2 |
3 | import kotlinx.coroutines.*
4 | import kotlinx.coroutines.channels.ClosedReceiveChannelException
5 | import kotlinx.coroutines.channels.ReceiveChannel
6 | import kotlinx.coroutines.channels.produce
7 | import kotlinx.coroutines.channels.ticker
8 | import kotlinx.coroutines.flow.AbstractFlow
9 | import kotlinx.coroutines.flow.Flow
10 | import kotlinx.coroutines.flow.FlowCollector
11 | import kotlinx.coroutines.flow.collect
12 | import kotlinx.coroutines.selects.select
13 |
14 | /**
15 | * Implementation is taken from
16 | * https://dev.to/psfeng/a-story-of-building-a-custom-flow-operator-buffertimeout-4d95
17 | */
18 | @FlowPreview
19 | internal class FlowBufferTimeout constructor(
20 | private val source: Flow,
21 | private val size: Int,
22 | private val duration: Long) : AbstractFlow>() {
23 | @ExperimentalCoroutinesApi
24 | @ObsoleteCoroutinesApi
25 | override suspend fun collectSafely(collector: FlowCollector>) {
26 | coroutineScope {
27 | val events = mutableListOf()
28 | val tickerChannel = ticker(duration)
29 | try {
30 | val upstreamValues: ReceiveChannel = produce { source.collect { value -> send(value) } }
31 |
32 | while (isActive) {
33 | var hasTimedOut = false
34 |
35 | select {
36 | upstreamValues.onReceive { value ->
37 | events.add(value)
38 | }
39 |
40 | tickerChannel.onReceive {
41 | hasTimedOut = true
42 | }
43 | }
44 |
45 | if (events.size == size || (hasTimedOut && events.isNotEmpty())) {
46 | collector.emit(events)
47 | events.clear()
48 | }
49 | }
50 | } catch (e: ClosedReceiveChannelException) {
51 | // drain remaining events
52 | if (events.isNotEmpty()) collector.emit(events)
53 | } finally {
54 | tickerChannel.cancel()
55 | }
56 | }
57 | }
58 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/app/missednotificationsreminder/ui/widget/recyclerview/LifecycleAdapter.kt:
--------------------------------------------------------------------------------
1 | package com.app.missednotificationsreminder.ui.widget.recyclerview
2 |
3 | import android.view.View
4 | import androidx.lifecycle.Lifecycle
5 | import androidx.lifecycle.LifecycleOwner
6 | import androidx.lifecycle.LifecycleRegistry
7 | import androidx.recyclerview.widget.RecyclerView
8 | import timber.log.Timber
9 |
10 | abstract class LifecycleAdapter : RecyclerView.Adapter(), LifecycleOwner {
11 | private val lifecycleRegistry = LifecycleRegistry(this)
12 |
13 | private val attachListener = object : View.OnAttachStateChangeListener {
14 | override fun onViewAttachedToWindow(v: View?) {
15 | Timber.d("onViewAttachedToWindow: $v")
16 | lifecycleRegistry.currentState = Lifecycle.State.STARTED
17 | }
18 |
19 | override fun onViewDetachedFromWindow(v: View?) {
20 | Timber.d("onViewDetachedFromWindow: $v")
21 | lifecycleRegistry.currentState = Lifecycle.State.DESTROYED
22 | }
23 | }
24 |
25 | init {
26 | lifecycleRegistry.currentState = Lifecycle.State.INITIALIZED
27 | }
28 |
29 | override fun onAttachedToRecyclerView(recyclerView: RecyclerView) {
30 | Timber.d("onAttachedToRecyclerView")
31 | if (recyclerView.isAttachedToWindow) {
32 | lifecycleRegistry.currentState = Lifecycle.State.STARTED
33 | }
34 | recyclerView.addOnAttachStateChangeListener(attachListener)
35 | super.onAttachedToRecyclerView(recyclerView)
36 | }
37 |
38 | override fun onDetachedFromRecyclerView(recyclerView: RecyclerView) {
39 | Timber.d("onDetachedFromRecyclerView")
40 | recyclerView.removeOnAttachStateChangeListener(attachListener)
41 | lifecycleRegistry.currentState = Lifecycle.State.DESTROYED
42 | super.onDetachedFromRecyclerView(recyclerView)
43 | }
44 |
45 | override fun onViewAttachedToWindow(holder: VH) {
46 | super.onViewAttachedToWindow(holder)
47 | holder.onAttached()
48 | }
49 |
50 | override fun onViewDetachedFromWindow(holder: VH) {
51 | super.onViewDetachedFromWindow(holder)
52 | holder.onDetached()
53 | }
54 |
55 | override fun getLifecycle(): Lifecycle {
56 | return lifecycleRegistry
57 | }
58 |
59 | }
60 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_vibration.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
9 |
12 |
13 |
16 |
17 |
18 |
22 |
23 |
28 |
29 |
36 |
37 |
41 |
42 |
51 |
52 |
53 |
54 |
55 |
--------------------------------------------------------------------------------
/app/src/main/java/com/jakewharton/u2020/ui/misc/BindableAdapter.java:
--------------------------------------------------------------------------------
1 | package com.jakewharton.u2020.ui.misc;
2 |
3 | import android.content.Context;
4 | import android.view.LayoutInflater;
5 | import android.view.View;
6 | import android.view.ViewGroup;
7 | import android.widget.BaseAdapter;
8 |
9 | /** An implementation of {@link BaseAdapter} which uses the new/bind pattern for its views. */
10 | public abstract class BindableAdapter extends BaseAdapter {
11 | private final Context context;
12 | private final LayoutInflater inflater;
13 |
14 | public BindableAdapter(Context context) {
15 | this.context = context;
16 | this.inflater = LayoutInflater.from(context);
17 | }
18 |
19 | public Context getContext() {
20 | return context;
21 | }
22 |
23 | @Override public abstract T getItem(int position);
24 |
25 | @Override public final View getView(int position, View view, ViewGroup container) {
26 | if (view == null) {
27 | view = newView(inflater, position, container);
28 | if (view == null) {
29 | throw new IllegalStateException("newView result must not be null.");
30 | }
31 | }
32 | bindView(getItem(position), position, view);
33 | return view;
34 | }
35 |
36 | /** Create a new instance of a view for the specified position. */
37 | public abstract View newView(LayoutInflater inflater, int position, ViewGroup container);
38 |
39 | /** Bind the data for the specified {@code position} to the view. */
40 | public abstract void bindView(T item, int position, View view);
41 |
42 | @Override public final View getDropDownView(int position, View view, ViewGroup container) {
43 | if (view == null) {
44 | view = newDropDownView(inflater, position, container);
45 | if (view == null) {
46 | throw new IllegalStateException("newDropDownView result must not be null.");
47 | }
48 | }
49 | bindDropDownView(getItem(position), position, view);
50 | return view;
51 | }
52 |
53 | /** Create a new instance of a drop-down view for the specified position. */
54 | public View newDropDownView(LayoutInflater inflater, int position, ViewGroup container) {
55 | return newView(inflater, position, container);
56 | }
57 |
58 | /** Bind the data for the specified {@code position} to the drop-down view. */
59 | public void bindDropDownView(T item, int position, View view) {
60 | bindView(item, position, view);
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/app/src/main/java/com/app/missednotificationsreminder/CustomApplicationBase.kt:
--------------------------------------------------------------------------------
1 | package com.app.missednotificationsreminder
2 |
3 | import android.content.Context
4 | import android.util.Log
5 | import androidx.multidex.MultiDex
6 | import androidx.work.Configuration
7 | import com.app.missednotificationsreminder.ui.ActivityHierarchyServer
8 | import com.app.missednotificationsreminder.ui.activity.common.CommonActivityLifecycleCallback
9 | import com.jakewharton.threetenabp.AndroidThreeTen
10 | import com.jakewharton.u2020.data.LumberYard
11 | import dagger.android.support.DaggerApplication
12 | import timber.log.Timber
13 | import timber.log.Timber.DebugTree
14 | import javax.inject.Inject
15 |
16 | /**
17 | * Custom application implementation to provide additional functionality such as dependency injection, leak inspection
18 | *
19 | * @author Eugene Popovich
20 | */
21 | abstract class CustomApplicationBase : DaggerApplication(), Configuration.Provider {
22 | @Inject
23 | lateinit var activityHierarchyServer: ActivityHierarchyServer
24 |
25 | @Inject
26 | lateinit var lumberYard: LumberYard
27 |
28 | override fun attachBaseContext(base: Context) {
29 | super.attachBaseContext(base)
30 | MultiDex.install(this)
31 | }
32 |
33 | override fun onCreate() {
34 | AndroidThreeTen.init(this)
35 | super.onCreate()
36 | // initialize logging
37 | if (BuildConfig.DEBUG) {
38 | Timber.plant(DebugTree())
39 | } else {
40 | Timber.plant(CrashReportingTree())
41 | }
42 | lumberYard.cleanUp()
43 | Timber.plant(lumberYard.tree())
44 | registerActivityLifecycleCallbacks(activityHierarchyServer)
45 | registerActivityLifecycleCallbacks(CommonActivityLifecycleCallback())
46 | }
47 |
48 | override fun getWorkManagerConfiguration() =
49 | Configuration.Builder()
50 | .setMinimumLoggingLevel(if (BuildConfig.DEBUG) Log.VERBOSE else Log.WARN)
51 | .build()
52 |
53 | private class CrashReportingTree : DebugTree() {
54 | override fun log(priority: Int, tag: String?, message: String, t: Throwable?) {
55 | if (priority == Log.VERBOSE || priority == Log.DEBUG) {
56 | // skip debug and verbose messages
57 | return
58 | }
59 | super.log(priority, tag, message, t)
60 | }
61 | }
62 | }
--------------------------------------------------------------------------------
/app/src/main/res/layout/debug_logs_list_item.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
8 |
17 |
18 |
32 |
33 |
49 |
50 |
61 |
62 |
--------------------------------------------------------------------------------
/app/src/debug/java/com/jakewharton/u2020/ui/bugreport/BugReportView.java:
--------------------------------------------------------------------------------
1 | package com.jakewharton.u2020.ui.bugreport;
2 |
3 | import android.content.Context;
4 | import android.text.Editable;
5 | import android.util.AttributeSet;
6 | import android.widget.LinearLayout;
7 |
8 | import com.app.missednotificationsreminder.databinding.BugreportViewBinding;
9 | import com.jakewharton.u2020.ui.misc.EmptyTextWatcher;
10 | import com.jakewharton.u2020.util.Strings;
11 |
12 | public final class BugReportView extends LinearLayout {
13 | BugreportViewBinding mBinding;
14 |
15 | public interface ReportDetailsListener {
16 | void onStateChanged(boolean valid);
17 | }
18 |
19 | private ReportDetailsListener listener;
20 |
21 | public BugReportView(Context context, AttributeSet attrs) {
22 | super(context, attrs);
23 | }
24 |
25 | @Override protected void onFinishInflate() {
26 | super.onFinishInflate();
27 |
28 | mBinding = BugreportViewBinding.bind(this);
29 | mBinding.title.setOnFocusChangeListener((v, hasFocus) -> {
30 | if (!hasFocus) {
31 | mBinding.title.setError(Strings.isBlank(mBinding.title.getText()) ? "Cannot be empty." : null);
32 | }
33 | });
34 | mBinding.title.addTextChangedListener(new EmptyTextWatcher() {
35 | @Override public void afterTextChanged(Editable s) {
36 | if (listener != null) {
37 | listener.onStateChanged(!Strings.isBlank(s));
38 | }
39 | }
40 | });
41 |
42 | mBinding.screenshot.setChecked(true);
43 | mBinding.logs.setChecked(true);
44 | }
45 |
46 | public void setBugReportListener(ReportDetailsListener listener) {
47 | this.listener = listener;
48 | }
49 |
50 | public Report getReport() {
51 | return new Report(String.valueOf(mBinding.title.getText()),
52 | String.valueOf(mBinding.description.getText()), mBinding.screenshot.isChecked(),
53 | mBinding.logs.isChecked());
54 | }
55 |
56 | public static final class Report {
57 | public final String title;
58 | public final String description;
59 | public final boolean includeScreenshot;
60 | public final boolean includeLogs;
61 |
62 | public Report(String title, String description, boolean includeScreenshot,
63 | boolean includeLogs) {
64 | this.title = title;
65 | this.description = description;
66 | this.includeScreenshot = includeScreenshot;
67 | this.includeLogs = includeLogs;
68 | }
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/app/src/main/java/com/app/missednotificationsreminder/settings/reminder/ReminderFragment.kt:
--------------------------------------------------------------------------------
1 | package com.app.missednotificationsreminder.settings.reminder
2 |
3 | import android.os.Bundle
4 | import android.view.View
5 | import androidx.fragment.app.viewModels
6 | import androidx.lifecycle.LiveData
7 | import androidx.lifecycle.ViewModel
8 | import androidx.lifecycle.ViewModelProvider
9 | import androidx.lifecycle.asLiveData
10 | import com.app.missednotificationsreminder.R
11 | import com.app.missednotificationsreminder.databinding.FragmentReminderBinding
12 | import com.app.missednotificationsreminder.di.ViewModelKey
13 | import com.app.missednotificationsreminder.settings.SettingsViewState
14 | import com.app.missednotificationsreminder.ui.fragment.common.CommonFragmentWithViewBinding
15 | import dagger.Binds
16 | import dagger.android.ContributesAndroidInjector
17 | import dagger.multibindings.IntoMap
18 | import kotlinx.coroutines.ExperimentalCoroutinesApi
19 | import kotlinx.coroutines.FlowPreview
20 | import javax.inject.Inject
21 |
22 | /**
23 | * Fragment which displays interval settings view
24 | */
25 | @FlowPreview
26 | @ExperimentalCoroutinesApi
27 | class ReminderFragment : CommonFragmentWithViewBinding(R.layout.fragment_reminder) {
28 | @Inject
29 | lateinit var viewModelFactory: ViewModelProvider.Factory
30 |
31 | private val viewModel by viewModels { viewModelFactory }
32 |
33 | @Inject
34 | lateinit var parentViewState: LiveData
35 |
36 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
37 | super.onViewCreated(view, savedInstanceState)
38 | init()
39 | }
40 |
41 | private fun init() {
42 | viewDataBinding.apply {
43 | // Set the lifecycle owner to the lifecycle of the view
44 | lifecycleOwner = viewLifecycleOwner
45 | parentViewState = this@ReminderFragment.parentViewState
46 | viewModel = this@ReminderFragment.viewModel
47 | viewState = this@ReminderFragment.viewModel.viewState.asLiveData()
48 | }
49 | }
50 |
51 | @dagger.Module
52 | abstract class Module {
53 | @ContributesAndroidInjector
54 | abstract fun contribute(): ReminderFragment
55 |
56 | @Binds
57 | @IntoMap
58 | @ViewModelKey(ReminderViewModel::class)
59 | internal abstract fun bindViewModel(viewModel: ReminderViewModel): ViewModel
60 | }
61 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/app/missednotificationsreminder/util/BatteryUtils.java:
--------------------------------------------------------------------------------
1 | package com.app.missednotificationsreminder.util;
2 |
3 | import android.annotation.TargetApi;
4 | import android.content.Context;
5 | import android.content.Intent;
6 | import android.net.Uri;
7 | import android.os.Build;
8 | import android.os.PowerManager;
9 | import android.provider.Settings;
10 |
11 | /**
12 | * Various battery usage related utilities
13 | */
14 | public class BatteryUtils {
15 |
16 | /**
17 | * Check whether the battery optimization is disabled for the application
18 | *
19 | * @param context
20 | * @return
21 | */
22 | @TargetApi(Build.VERSION_CODES.M) //
23 | public static boolean isBatteryOptimizationDisabled(Context context) {
24 | String packageName = context.getPackageName();
25 | PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
26 | return pm.isIgnoringBatteryOptimizations(packageName);
27 | }
28 |
29 | /**
30 | * Get the intent to either request ignore battery optimization or open battery optimization settings
31 | * depend on whether the battery optimization is already disabled for the application
32 | *
33 | * @param context
34 | * @return
35 | */
36 | @TargetApi(Build.VERSION_CODES.M) //
37 | public static Intent getBatteryOptimizationIntent(Context context) {
38 | Intent intent = new Intent();
39 | intent.setAction(Settings.ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS);
40 | // TODO google didn't allow to use this for the MissedNotificationsReminder app
41 | // String packageName = context.getPackageName();
42 | // PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
43 | // if (pm.isIgnoringBatteryOptimizations(packageName)) {
44 | // intent.setAction(Settings.ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS);
45 | // }
46 | // else {
47 | // intent.setAction(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS);
48 | // intent.setData(Uri.parse("package:" + packageName));
49 | // }
50 | return intent;
51 | }
52 |
53 | /**
54 | * Check whether the battery optimization settings available in the current OS version
55 | *
56 | * @return
57 | */
58 | public static boolean isBatteryOptimizationSettingsAvailable() {
59 | return Build.VERSION.SDK_INT >= Build.VERSION_CODES.M;
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/item_contribute.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
8 |
11 |
12 |
13 |
18 |
19 |
33 |
34 |
48 |
49 |
50 |
--------------------------------------------------------------------------------
/app/src/main/java/com/app/missednotificationsreminder/ui/widget/recyclerview/LifecycleViewHolder.kt:
--------------------------------------------------------------------------------
1 | package com.app.missednotificationsreminder.ui.widget.recyclerview
2 |
3 | import android.view.View
4 | import androidx.annotation.CallSuper
5 | import androidx.lifecycle.Lifecycle
6 | import androidx.lifecycle.LifecycleEventObserver
7 | import androidx.lifecycle.LifecycleOwner
8 | import androidx.lifecycle.LifecycleRegistry
9 | import androidx.recyclerview.widget.RecyclerView
10 | import timber.log.Timber
11 | import java.lang.ref.WeakReference
12 |
13 | abstract class LifecycleViewHolder(itemView: View, parentLifecycle: Lifecycle) : RecyclerView.ViewHolder(itemView), LifecycleOwner {
14 | private var lifecycleRegistry = LifecycleRegistry(this)
15 | private val parentLifecycleReference = WeakReference(parentLifecycle)
16 | private val parentLifecycleEventObserver = LifecycleEventObserver { _, event ->
17 | if (event == Lifecycle.Event.ON_DESTROY) {
18 | onDestroy()
19 | }
20 | }
21 |
22 | @CallSuper
23 | open fun onAttached() {
24 | Timber.d("onAttached")
25 | parentLifecycleReference.get()
26 | ?.takeIf { it.currentState != Lifecycle.State.DESTROYED }
27 | ?.apply {
28 | addObserver(parentLifecycleEventObserver)
29 | // use STARTED instead of CREATED so it may be properly used with databinding and LiveData
30 | lifecycleRegistry.currentState = Lifecycle.State.STARTED
31 | }
32 | }
33 |
34 | @CallSuper
35 | fun onDetached() {
36 | Timber.d("onDetached")
37 | onDestroy()
38 | }
39 |
40 | private fun onDestroy() {
41 | Timber.d("onDestroy()")
42 | if (lifecycleRegistry.currentState != Lifecycle.State.INITIALIZED) {
43 | parentLifecycleReference.get()?.apply { removeObserver(parentLifecycleEventObserver) }
44 | lifecycleRegistry.currentState = Lifecycle.State.DESTROYED
45 | parentLifecycleReference.get()
46 | ?.takeIf { it.currentState != Lifecycle.State.DESTROYED }
47 | ?.run {
48 | // lifecycle can't be fully reused after destroyed
49 | lifecycleRegistry = LifecycleRegistry(this@LifecycleViewHolder)
50 | }
51 | }
52 | }
53 |
54 | override fun getLifecycle(): Lifecycle {
55 | return lifecycleRegistry
56 | }
57 |
58 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/app/missednotificationsreminder/di/ViewModelFactory.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2019 The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.app.missednotificationsreminder.di
18 |
19 | import androidx.lifecycle.ViewModel
20 | import androidx.lifecycle.ViewModelProvider
21 | import dagger.Binds
22 | import dagger.MapKey
23 | import dagger.Module
24 | import javax.inject.Inject
25 | import javax.inject.Provider
26 | import kotlin.reflect.KClass
27 |
28 | /**
29 | * ViewModelFactory which uses Dagger to create the instances.
30 | */
31 | class ViewModelFactory @Inject constructor(
32 | private val creators: @JvmSuppressWildcards Map, Provider>
33 | ) : ViewModelProvider.Factory {
34 | override fun create(modelClass: Class): T {
35 | var creator: Provider? = creators[modelClass]
36 | if (creator == null) {
37 | for ((key, value) in creators) {
38 | if (modelClass.isAssignableFrom(key)) {
39 | creator = value
40 | break
41 | }
42 | }
43 | }
44 | if (creator == null) {
45 | throw IllegalArgumentException("Unknown model class: $modelClass")
46 | }
47 | try {
48 | @Suppress("UNCHECKED_CAST")
49 | return creator.get() as T
50 | } catch (e: Exception) {
51 | throw RuntimeException(e)
52 | }
53 | }
54 | }
55 |
56 | @Module
57 | internal abstract class ViewModelBuilder {
58 | @Binds
59 | internal abstract fun bindViewModelFactory(
60 | factory: ViewModelFactory
61 | ): ViewModelProvider.Factory
62 | }
63 |
64 | @Target(
65 | AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.PROPERTY_SETTER
66 | )
67 | @Retention(AnnotationRetention.RUNTIME)
68 | @MapKey
69 | annotation class ViewModelKey(val value: KClass)
70 |
--------------------------------------------------------------------------------