├── .github └── PULL_REQUEST_TEMPLATE.md ├── .gitignore ├── .idea ├── codeStyles │ ├── Project.xml │ └── codeStyleConfig.xml └── vcs.xml ├── Dangerfile ├── Gemfile ├── Gemfile.lock ├── LICENSE ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-instant-run.pro ├── proguard-rules-release-preprod.pro ├── proguard-rules-release.pro ├── proguard-rules-test.pro ├── proguard-rules.pro └── src │ ├── androidTest │ └── kotlin │ │ └── co │ │ └── netguru │ │ └── baby │ │ └── monitor │ │ └── client │ │ └── database │ │ └── DatabaseTests.kt │ ├── debug │ └── kotlin │ │ └── co │ │ └── netguru │ │ └── baby │ │ └── monitor │ │ └── client │ │ └── application │ │ ├── DebugMetricsHelper.kt │ │ └── RxJavaErrorHandlerImpl.kt │ ├── debugTools │ └── kotlin │ │ └── co │ │ └── netguru │ │ └── baby │ │ └── monitor │ │ └── client │ │ └── feature │ │ └── debug │ │ ├── DebugModule.kt │ │ └── DebugNotificationManager.kt │ ├── main │ ├── AndroidManifest.xml │ ├── assets │ │ ├── model_exp76.pb │ │ └── tiny_conv_dataset.pb │ ├── ic_launcher-web.png │ ├── kotlin │ │ └── co │ │ │ └── netguru │ │ │ └── baby │ │ │ └── monitor │ │ │ └── client │ │ │ ├── application │ │ │ ├── App.kt │ │ │ ├── RxJavaErrorHandler.kt │ │ │ ├── di │ │ │ │ ├── ActivityBindingsModule.kt │ │ │ │ ├── ApplicationComponent.kt │ │ │ │ ├── ApplicationModule.kt │ │ │ │ ├── BabyMonitorGlideModule.kt │ │ │ │ ├── FirebaseModule.kt │ │ │ │ ├── FragmentBindingsModule.kt │ │ │ │ ├── NetworkModule.kt │ │ │ │ ├── NotificationsModule.kt │ │ │ │ ├── ServiceBindingsModule.kt │ │ │ │ └── SharedPreferencesModule.kt │ │ │ ├── firebase │ │ │ │ ├── FirebaseRepository.kt │ │ │ │ └── FirebaseSharedPreferencesWrapper.kt │ │ │ └── scope │ │ │ │ ├── ActivityScope.kt │ │ │ │ └── FragmentScope.kt │ │ │ ├── common │ │ │ ├── DateProvider.kt │ │ │ ├── LocalDateTimeProvider.kt │ │ │ ├── NotificationHandler.kt │ │ │ ├── Optional.kt │ │ │ ├── PermissionUtils.kt │ │ │ ├── Randomiser.kt │ │ │ ├── RunsInBackground.kt │ │ │ ├── SchedulersProvider.kt │ │ │ ├── SingleLiveEvent.kt │ │ │ ├── YesNoDialog.kt │ │ │ ├── base │ │ │ │ ├── AnalyticScreen.kt │ │ │ │ ├── BaseFragment.kt │ │ │ │ ├── BaseServiceWithFacade.kt │ │ │ │ └── ViewModelFactory.kt │ │ │ ├── extensions │ │ │ │ ├── ActivityExtensions.kt │ │ │ │ ├── ContextExtensions.kt │ │ │ │ ├── DataExtensions.kt │ │ │ │ ├── FragmentExtensions.kt │ │ │ │ ├── LiveDataExtensions.kt │ │ │ │ ├── NotificationExtensions.kt │ │ │ │ ├── Optional.kt │ │ │ │ ├── RxExtensions.kt │ │ │ │ ├── SharedPreferencesExtensions.kt │ │ │ │ └── ViewExtensions.kt │ │ │ └── view │ │ │ │ ├── BaseViewHolder.kt │ │ │ │ ├── CustomSurfaceViewRenderer.kt │ │ │ │ ├── PulsatingView.kt │ │ │ │ ├── SeekBarProgress.kt │ │ │ │ ├── StickyHeaderDecorator.kt │ │ │ │ └── StickyHeaderInterface.kt │ │ │ ├── data │ │ │ ├── AppDatabase.kt │ │ │ ├── Converters.kt │ │ │ ├── DataRepository.kt │ │ │ ├── client │ │ │ │ ├── ChildDataDao.kt │ │ │ │ ├── ChildDataEntity.kt │ │ │ │ └── home │ │ │ │ │ ├── ToolbarState.kt │ │ │ │ │ └── log │ │ │ │ │ ├── LogData.kt │ │ │ │ │ ├── LogDataDao.kt │ │ │ │ │ └── LogDataEntity.kt │ │ │ ├── communication │ │ │ │ ├── ClientDataDao.kt │ │ │ │ ├── ClientEntity.kt │ │ │ │ ├── SingleEvent.kt │ │ │ │ ├── nsd │ │ │ │ │ └── DiscoveryStatus.kt │ │ │ │ └── websocket │ │ │ │ │ └── ClientConnectionStatus.kt │ │ │ ├── server │ │ │ │ └── CameraState.kt │ │ │ └── splash │ │ │ │ └── AppStateHandler.kt │ │ │ └── feature │ │ │ ├── analytics │ │ │ ├── AnalyticsManager.kt │ │ │ ├── Event.kt │ │ │ ├── Screen.kt │ │ │ └── UserProperty.kt │ │ │ ├── babynotification │ │ │ ├── BabyEventActionIntentService.kt │ │ │ ├── BabyMonitorMessagingService.kt │ │ │ ├── NotifyBabyEventUseCase.kt │ │ │ ├── OpenCameraUseCase.kt │ │ │ └── SnoozeNotificationUseCase.kt │ │ │ ├── batterylevel │ │ │ ├── LowBatteryReceiver.kt │ │ │ └── NotifyLowBatteryUseCase.kt │ │ │ ├── client │ │ │ ├── configuration │ │ │ │ ├── AllDoneFragment.kt │ │ │ │ └── ParentDeviceInfoFragment.kt │ │ │ └── home │ │ │ │ ├── BackButtonState.kt │ │ │ │ ├── ClientHomeActivity.kt │ │ │ │ ├── ClientHomeViewModel.kt │ │ │ │ ├── RestartAppUseCase.kt │ │ │ │ ├── SendBabyNameUseCase.kt │ │ │ │ ├── SendFirebaseTokenUseCase.kt │ │ │ │ ├── dashboard │ │ │ │ └── ClientDashboardFragment.kt │ │ │ │ ├── livecamera │ │ │ │ ├── ClientLiveCameraFragment.kt │ │ │ │ └── ClientLiveCameraFragmentViewModel.kt │ │ │ │ └── log │ │ │ │ ├── ActivityLogAdapter.kt │ │ │ │ ├── ClientActivityLogFragment.kt │ │ │ │ └── LogsViewHolder.kt │ │ │ ├── communication │ │ │ ├── ConfirmationUseCase.kt │ │ │ ├── communication.kt │ │ │ ├── internet │ │ │ │ └── CheckInternetConnectionUseCase.kt │ │ │ ├── nsd │ │ │ │ ├── DeviceNameProvider.kt │ │ │ │ ├── IDeviceNameProvider.kt │ │ │ │ ├── NsdServiceDiffUtil.kt │ │ │ │ ├── NsdServiceExceptions.kt │ │ │ │ ├── NsdServiceManager.kt │ │ │ │ ├── NsdServicesAdapter.kt │ │ │ │ ├── NsdState.kt │ │ │ │ └── ServiceViewHolder.kt │ │ │ ├── pairing │ │ │ │ ├── ConnectingDevicesFailedFragment.kt │ │ │ │ ├── PairingFragment.kt │ │ │ │ ├── PairingUseCase.kt │ │ │ │ ├── PairingViewModel.kt │ │ │ │ ├── ServiceDiscoveryFragment.kt │ │ │ │ └── ServiceDiscoveryViewModel.kt │ │ │ ├── webrtc │ │ │ │ ├── PeerConnectionExtensions.kt │ │ │ │ ├── StreamState.kt │ │ │ │ ├── base │ │ │ │ │ └── RtcMessageHandler.kt │ │ │ │ ├── client │ │ │ │ │ ├── RtcClient.kt │ │ │ │ │ ├── RtcClientController.kt │ │ │ │ │ └── RtcClientMessageController.kt │ │ │ │ ├── observers │ │ │ │ │ ├── ConnectionObserver.kt │ │ │ │ │ ├── DefaultObserver.kt │ │ │ │ │ └── DefaultSdpObserver.kt │ │ │ │ └── server │ │ │ │ │ ├── WebRtcManager.kt │ │ │ │ │ └── WebRtcService.kt │ │ │ └── websocket │ │ │ │ ├── CustomWebSocketServer.kt │ │ │ │ ├── Message.kt │ │ │ │ ├── MessageController.kt │ │ │ │ ├── MessageParser.kt │ │ │ │ ├── RxWebSocketClient.kt │ │ │ │ ├── WebSocketServerHandler.kt │ │ │ │ └── WebSocketServerService.kt │ │ │ ├── debug │ │ │ ├── DebugState.kt │ │ │ └── DebugView.kt │ │ │ ├── firebasenotification │ │ │ ├── FirebaseInstanceManager.kt │ │ │ ├── FirebaseNotificationSender.kt │ │ │ ├── Message.kt │ │ │ └── NotificationType.kt │ │ │ ├── machinelearning │ │ │ └── MachineLearning.kt │ │ │ ├── noisedetection │ │ │ └── NoiseDetector.kt │ │ │ ├── onboarding │ │ │ ├── FeaturePresentationFragment.kt │ │ │ ├── FinishOnboardingUseCase.kt │ │ │ ├── InfoAboutDevicesFragment.kt │ │ │ ├── OnboardingActivity.kt │ │ │ ├── SpecifyDeviceFragment.kt │ │ │ ├── VoiceRecordingsSettingsFragment.kt │ │ │ └── baby │ │ │ │ ├── ConnectWifiFragment.kt │ │ │ │ ├── PermissionCameraFragment.kt │ │ │ │ ├── PermissionDenied.kt │ │ │ │ ├── PermissionMicrophoneFragment.kt │ │ │ │ ├── SetupInformationFragment.kt │ │ │ │ └── WifiReceiver.kt │ │ │ ├── server │ │ │ ├── ChildMonitorFragment.kt │ │ │ ├── ChildMonitorFragmentModule.kt │ │ │ ├── ChildMonitorViewModel.kt │ │ │ ├── ReceiveFirebaseTokenUseCase.kt │ │ │ ├── ServerActivity.kt │ │ │ └── ServerViewModel.kt │ │ │ ├── settings │ │ │ ├── ChangeState.kt │ │ │ ├── ClientSettingsFragment.kt │ │ │ ├── ConfigurationViewModel.kt │ │ │ ├── ResetAppUseCase.kt │ │ │ ├── SeekBarState.kt │ │ │ ├── ServerSettingsFragment.kt │ │ │ └── SettingsViewModel.kt │ │ │ ├── splash │ │ │ ├── SplashFragment.kt │ │ │ └── SplashViewModel.kt │ │ │ └── voiceAnalysis │ │ │ ├── AacRecorder.kt │ │ │ ├── RecordingController.kt │ │ │ ├── RecordingData.kt │ │ │ ├── VoiceAnalysisController.kt │ │ │ ├── VoiceAnalysisOption.kt │ │ │ ├── VoiceAnalysisService.kt │ │ │ ├── VoiceAnalysisUseCase.kt │ │ │ └── WavFileGenerator.kt │ └── res │ │ ├── drawable-hdpi │ │ ├── heart.png │ │ ├── logo.png │ │ ├── made_with_love.png │ │ ├── white_logo.png │ │ └── wifi.png │ │ ├── drawable-mdpi │ │ ├── heart.png │ │ ├── logo.png │ │ ├── made_with_love.png │ │ ├── white_logo.png │ │ └── wifi.png │ │ ├── drawable-v21 │ │ ├── button_purple_outline_to_purple_rounded.xml │ │ ├── button_rounded_ripple.xml │ │ ├── button_white_outline_to_purple_rounded.xml │ │ └── dark_button_rounded_ripple.xml │ │ ├── drawable-xhdpi │ │ ├── heart.png │ │ ├── logo.png │ │ ├── made_with_love.png │ │ ├── white_logo.png │ │ └── wifi.png │ │ ├── drawable-xxhdpi │ │ ├── heart.png │ │ ├── logo.png │ │ ├── made_with_love.png │ │ ├── white_logo.png │ │ └── wifi.png │ │ ├── drawable-xxxhdpi │ │ ├── heart.png │ │ ├── logo.png │ │ ├── made_with_love.png │ │ ├── white_logo.png │ │ └── wifi.png │ │ ├── drawable │ │ ├── all_circle_white_bg.xml │ │ ├── all_done.xml │ │ ├── animated_done.xml │ │ ├── animated_fail.xml │ │ ├── baby_logo.xml │ │ ├── button_purple_outline_to_purple_rounded.xml │ │ ├── button_rounded_ripple.xml │ │ ├── button_white_outline_to_purple_rounded.xml │ │ ├── circle_primary_bg.xml │ │ ├── circle_primary_dark_bg.xml │ │ ├── dark_button_rounded_ripple.xml │ │ ├── download.xml │ │ ├── failed.xml │ │ ├── feature_a.xml │ │ ├── feature_a_indicator.xml │ │ ├── feature_b.xml │ │ ├── feature_b_indicator.xml │ │ ├── feature_c.xml │ │ ├── feature_c_indicator.xml │ │ ├── feature_d.xml │ │ ├── ic_activity.xml │ │ ├── ic_arrow_back.xml │ │ ├── ic_arrow_down.xml │ │ ├── ic_baby.xml │ │ ├── ic_camera.xml │ │ ├── ic_close.xml │ │ ├── ic_edit.xml │ │ ├── ic_gear_wheel.xml │ │ ├── ic_info.xml │ │ ├── ic_launcher_foreground.xml │ │ ├── ic_microphone.xml │ │ ├── ic_night_mode.xml │ │ ├── ic_night_mode_active.xml │ │ ├── ic_select_photo_camera.xml │ │ ├── ic_select_photo_placeholder.xml │ │ ├── ic_sound_max.xml │ │ ├── ic_sound_min.xml │ │ ├── ic_specify_device_checkmark.xml │ │ ├── ic_sync.xml │ │ ├── monitoring.xml │ │ ├── phone_item_icon.xml │ │ ├── recycler_divider.xml │ │ ├── safety.xml │ │ └── shapes.xml │ │ ├── font │ │ ├── rubik.xml │ │ ├── rubik_bold.otf │ │ ├── rubik_light.otf │ │ ├── rubik_medium.otf │ │ └── rubik_regular.otf │ │ ├── layout │ │ ├── activity_client_home.xml │ │ ├── activity_onboarding.xml │ │ ├── activity_server.xml │ │ ├── debug_view.xml │ │ ├── found_service_item.xml │ │ ├── fragment_all_done.xml │ │ ├── fragment_camera_permission.xml │ │ ├── fragment_child_monitor.xml │ │ ├── fragment_client_activity_log.xml │ │ ├── fragment_client_dashboard.xml │ │ ├── fragment_client_live_camera.xml │ │ ├── fragment_client_settings.xml │ │ ├── fragment_connect_wifi.xml │ │ ├── fragment_connecting_devices.xml │ │ ├── fragment_connecting_setup_information.xml │ │ ├── fragment_denied_permission.xml │ │ ├── fragment_failed_devices_connecting.xml │ │ ├── fragment_feature_a.xml │ │ ├── fragment_feature_b.xml │ │ ├── fragment_feature_c.xml │ │ ├── fragment_info_about_devices.xml │ │ ├── fragment_microphone_permission.xml │ │ ├── fragment_pairing.xml │ │ ├── fragment_parent_device_info.xml │ │ ├── fragment_server_settings.xml │ │ ├── fragment_specify_device.xml │ │ ├── fragment_splash.xml │ │ ├── fragment_voice_recordings_setting.xml │ │ ├── item_log_activity_end_text.xml │ │ ├── item_log_activity_header.xml │ │ ├── item_log_activity_record.xml │ │ ├── onboarding_buttons.xml │ │ ├── seek_bar_progress.xml │ │ ├── toolbar_child.xml │ │ └── toolbar_default.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── navigation │ │ ├── client_home_nav_graph.xml │ │ ├── server_nav_graph.xml │ │ └── splash_nav_graph.xml │ │ ├── values-v21 │ │ └── styles.xml │ │ ├── values │ │ ├── animation.xml │ │ ├── attrs.xml │ │ ├── colors.xml │ │ ├── dimens.xml │ │ ├── ic_launcher_background.xml │ │ ├── resourfes.xml │ │ ├── strings.xml │ │ └── styles.xml │ │ └── xml │ │ ├── progress_scene.xml │ │ └── searching_scene.xml │ ├── release │ └── kotlin │ │ └── co │ │ └── netguru │ │ └── baby │ │ └── monitor │ │ └── client │ │ ├── application │ │ ├── DebugMetricsHelper.kt │ │ └── RxJavaErrorHandlerImpl.kt │ │ └── feature │ │ └── debug │ │ ├── DebugModule.kt │ │ └── DebugNotificationManager.kt │ ├── releasePreprod │ └── kotlin │ │ └── co │ │ └── netguru │ │ └── baby │ │ └── monitor │ │ └── client │ │ └── application │ │ ├── DebugMetricsHelper.kt │ │ └── RxJavaErrorHandlerImpl.kt │ └── test │ ├── kotlin │ └── co │ │ └── netguru │ │ └── baby │ │ └── monitor │ │ ├── AnswerWithPair.kt │ │ ├── RxSchedulersOverrideRule.kt │ │ ├── RxTestSchedulerOverrideRule.kt │ │ ├── TestUtils.kt │ │ └── client │ │ ├── common │ │ └── OptionalTest.kt │ │ ├── data │ │ └── DataRepositoryTest.kt │ │ └── feature │ │ ├── babycrynotification │ │ ├── NotifyBabyEventUseCaseTest.kt │ │ ├── OpenCameraUseCaseTest.kt │ │ └── SnoozeNotificationUseCaseTest.kt │ │ ├── batterylevel │ │ └── NotifyLowBatteryUseCaseTest.kt │ │ ├── client │ │ └── home │ │ │ ├── ClientHomeViewModelTest.kt │ │ │ ├── RestartAppUseCaseTest.kt │ │ │ ├── SendBabyNameUseCaseTest.kt │ │ │ ├── SendFirebaseTokenUseCaseTest.kt │ │ │ └── livecamera │ │ │ └── ClientLiveCameraFragmentViewModelTest.kt │ │ ├── communication │ │ ├── nsd │ │ │ └── NsdServiceManagerTest.kt │ │ └── pairing │ │ │ ├── PairingUseCaseTest.kt │ │ │ └── ServiceDiscoveryViewModelTest.kt │ │ ├── onboarding │ │ └── FinishOnboardingUseCaseTest.kt │ │ ├── server │ │ ├── ChildMonitorViewModelTest.kt │ │ ├── ReceiveFirebaseTokenUseCaseTest.kt │ │ └── ServerViewModelTest.kt │ │ ├── settings │ │ ├── ConfigurationViewModelTest.kt │ │ └── ResetAppUseCaseTest.kt │ │ └── voiceAnalysis │ │ ├── ConfirmationUseCaseTest.kt │ │ ├── RecordingControllerTest.kt │ │ └── VoiceAnalysisControllerTest.kt │ └── resources │ └── mockito-extensions │ └── org.mockito.plugins.MockMaker ├── build.gradle ├── buildsystem ├── bitrise.gradle ├── dependencies.gradle └── secrets.gradle ├── default-detekt-config.yml ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ### Task 2 | 3 | - [JIRA](https://netguru.atlassian.net/browse/BM-) 4 | 5 | ### Description 6 | 7 | 8 | ### Additional Notes (optional) 9 | 10 | 11 | ### Checklist 12 | 13 | - [ ] I am following the [code style guide](https://netguru.atlassian.net/wiki/display/ANDROID/Android+best+practices) 14 | - [ ] The code includes tests of new features 15 | - [ ] The code passes static analysis 16 | - [ ] README.md is up to date 17 | - [ ] Version number is up to date 18 | - [ ] ProGuard configuration files are up to date 19 | - [ ] All temporary TODOs and FIXMEs are removed 20 | - [ ] I have tested the solution on these devices: 21 | * Device A, Android X.Y.Z 22 | * Device B, Android Z.Y 23 | * Virtual device W, Android Y.Y.Z 24 | 25 | ### Merge 26 | 27 | - [ ] Committer 28 | - [ ] Reviewer 29 | - [ ] Project lead 30 | -------------------------------------------------------------------------------- /.idea/codeStyles/codeStyleConfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | git_source(:github) {|repo_name| "https://github.com/#{repo_name}" } 4 | 5 | gem 'danger' 6 | gem 'danger-android_lint' 7 | gem 'danger-kotlin_detekt' 8 | gem 'danger-junit' 9 | gem 'danger-jacoco' 10 | gem 'danger-slack' 11 | gem 'danger-jira' 12 | gem 'danger-apkanalyzer' 13 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/proguard-instant-run.pro: -------------------------------------------------------------------------------- 1 | -keep class android.arch.lifecycle.** {*;} 2 | -keep class co.netguru.baby.monitor.client.feature.onboarding.OnboardingActivity 3 | 4 | #Firebase Database 5 | -keep class co.netguru.baby.monitor.client.data.communication.firebase.** { *; } 6 | -------------------------------------------------------------------------------- /app/proguard-rules-test.pro: -------------------------------------------------------------------------------- 1 | # Additional proguard rules for instrumentation testing 2 | -ignorewarnings 3 | 4 | -keepattributes *Annotation* 5 | 6 | -dontnote junit.framework.** 7 | -dontnote junit.runner.** 8 | 9 | -dontwarn android.test.** 10 | -dontwarn android.support.test.** 11 | -dontwarn org.junit.** 12 | -dontwarn org.hamcrest.** 13 | -dontwarn com.squareup.javawriter.JavaWriter 14 | 15 | -keep class rx.plugins.** { *; } 16 | -keep class org.junit.** { *; } 17 | -keep class co.netguru.android.testcommons.** { *; } 18 | -keep class android.support.test.espresso.** { *; } 19 | -dontwarn org.hamcrest.** 20 | 21 | -keep class android.arch.** { *; } 22 | -keep interface android.arch.** { *; } 23 | 24 | -keep class kotlin.** { *; } 25 | -keep class kotlin.Metadata { *; } 26 | -dontwarn kotlin.** 27 | -keepclassmembers class **$WhenMappings { 28 | ; 29 | } 30 | -keepclassmembers class kotlin.Metadata { 31 | public ; 32 | } 33 | -assumenosideeffects class kotlin.jvm.internal.Intrinsics { 34 | static void checkParameterIsNotNull(java.lang.Object, java.lang.String); 35 | } 36 | 37 | #Firebase Database 38 | -keep class co.netguru.baby.monitor.client.data.communication.firebase.** { *; } 39 | -------------------------------------------------------------------------------- /app/src/debug/kotlin/co/netguru/baby/monitor/client/application/DebugMetricsHelper.kt: -------------------------------------------------------------------------------- 1 | package co.netguru.baby.monitor.client.application 2 | 3 | import timber.log.Timber 4 | import javax.inject.Inject 5 | import javax.inject.Singleton 6 | 7 | /** 8 | * Helper class that initializes a set of debugging tools 9 | * for the debug build type and register crash manager for release type. 10 | * ## Debug type tools: 11 | * - AndroidDevMetrics 12 | * - Stetho 13 | * - StrictMode 14 | * - LeakCanary 15 | * - Timber 16 | * 17 | * ## Release type tools: 18 | * - CrashManager 19 | */ 20 | @Singleton 21 | class DebugMetricsHelper @Inject constructor() { 22 | 23 | internal fun init() { 24 | Timber.plant(Timber.DebugTree()) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/src/debug/kotlin/co/netguru/baby/monitor/client/application/RxJavaErrorHandlerImpl.kt: -------------------------------------------------------------------------------- 1 | package co.netguru.baby.monitor.client.application 2 | 3 | import io.reactivex.exceptions.UndeliverableException 4 | 5 | class RxJavaErrorHandlerImpl : RxJavaErrorHandler() { 6 | 7 | override fun handleUndeliverableException(undeliverableException: UndeliverableException) { 8 | /** 9 | * Crash the app while in debug as undeliverable exception can sometimes be indication of 10 | * bug's that could have been prevented. Check if disposables/cancelables are set properly 11 | * on emitter or backup with tryOnError from emitter if that's not possible. 12 | */ 13 | undeliverableException.printStackTrace() 14 | uncaught(undeliverableException) 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /app/src/main/assets/model_exp76.pb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netguru/baby-monitor-client-android/3c3d8838de7ed8a340ebb79a64f249681669abf2/app/src/main/assets/model_exp76.pb -------------------------------------------------------------------------------- /app/src/main/assets/tiny_conv_dataset.pb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netguru/baby-monitor-client-android/3c3d8838de7ed8a340ebb79a64f249681669abf2/app/src/main/assets/tiny_conv_dataset.pb -------------------------------------------------------------------------------- /app/src/main/ic_launcher-web.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netguru/baby-monitor-client-android/3c3d8838de7ed8a340ebb79a64f249681669abf2/app/src/main/ic_launcher-web.png -------------------------------------------------------------------------------- /app/src/main/kotlin/co/netguru/baby/monitor/client/application/App.kt: -------------------------------------------------------------------------------- 1 | package co.netguru.baby.monitor.client.application 2 | 3 | import co.netguru.baby.monitor.client.application.di.DaggerApplicationComponent 4 | import co.netguru.baby.monitor.client.application.firebase.FirebaseRepository 5 | import com.jakewharton.threetenabp.AndroidThreeTen 6 | import dagger.android.AndroidInjector 7 | import dagger.android.support.DaggerApplication 8 | import io.reactivex.plugins.RxJavaPlugins 9 | import javax.inject.Inject 10 | 11 | class App : DaggerApplication() { 12 | 13 | @Inject 14 | lateinit var debugMetricsHelper: DebugMetricsHelper 15 | 16 | @Inject 17 | lateinit var rxJavaErrorHandler: RxJavaErrorHandler 18 | 19 | @Inject 20 | lateinit var firebaseRepository: FirebaseRepository 21 | 22 | override fun onCreate() { 23 | super.onCreate() 24 | debugMetricsHelper.init() 25 | RxJavaPlugins.setErrorHandler(rxJavaErrorHandler) 26 | AndroidThreeTen.init(this) 27 | firebaseRepository.initializeApp(this) 28 | } 29 | 30 | override fun applicationInjector(): AndroidInjector = 31 | DaggerApplicationComponent.builder().create(this) 32 | } 33 | -------------------------------------------------------------------------------- /app/src/main/kotlin/co/netguru/baby/monitor/client/application/RxJavaErrorHandler.kt: -------------------------------------------------------------------------------- 1 | package co.netguru.baby.monitor.client.application 2 | 3 | import io.reactivex.exceptions.UndeliverableException 4 | import io.reactivex.functions.Consumer 5 | 6 | /** 7 | * [RxJava2 error handling](https://github.com/ReactiveX/RxJava/wiki/What's-different-in-2.0#error-handling) 8 | */ 9 | abstract class RxJavaErrorHandler : Consumer { 10 | 11 | override fun accept(throwable: Throwable) = when (throwable) { 12 | is UndeliverableException -> { 13 | //we log such exceptions but avoid app crash for release as we can't do much in such case. 14 | handleUndeliverableException(throwable) 15 | } 16 | else -> { 17 | //we crash the app else - this is a bug 18 | throwable.printStackTrace() 19 | uncaught(throwable) 20 | } 21 | } 22 | 23 | /** 24 | * Something thrown error after stream finished. 25 | * If this happens often it can be indication of some problem in library or our codebase. 26 | * Make sure that source sets disposable/cancellable while creating stream. 27 | * You can also use tryOnError if the wrapped data source doesn't provide good way to cancel emissions. 28 | */ 29 | abstract fun handleUndeliverableException(undeliverableException: UndeliverableException) 30 | 31 | protected fun uncaught(throwable: Throwable) { 32 | val currentThread = Thread.currentThread() 33 | val handler = currentThread.uncaughtExceptionHandler 34 | handler.uncaughtException(currentThread, throwable) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /app/src/main/kotlin/co/netguru/baby/monitor/client/application/di/ActivityBindingsModule.kt: -------------------------------------------------------------------------------- 1 | package co.netguru.baby.monitor.client.application.di 2 | 3 | import co.netguru.baby.monitor.client.application.scope.ActivityScope 4 | import co.netguru.baby.monitor.client.feature.client.home.ClientHomeActivity 5 | import co.netguru.baby.monitor.client.feature.server.ServerActivity 6 | import dagger.Module 7 | import dagger.android.ContributesAndroidInjector 8 | 9 | @Module 10 | internal abstract class ActivityBindingsModule { 11 | 12 | @ActivityScope 13 | @ContributesAndroidInjector 14 | internal abstract fun clientHomeActivityInjector(): ClientHomeActivity 15 | 16 | @ActivityScope 17 | @ContributesAndroidInjector 18 | internal abstract fun serverActivityInjector(): ServerActivity 19 | } 20 | -------------------------------------------------------------------------------- /app/src/main/kotlin/co/netguru/baby/monitor/client/application/di/ApplicationComponent.kt: -------------------------------------------------------------------------------- 1 | package co.netguru.baby.monitor.client.application.di 2 | 3 | import co.netguru.baby.monitor.client.application.App 4 | import co.netguru.baby.monitor.client.common.base.ViewModelModule 5 | import dagger.Component 6 | import dagger.android.AndroidInjectionModule 7 | import dagger.android.AndroidInjector 8 | import dagger.android.support.AndroidSupportInjectionModule 9 | import javax.inject.Singleton 10 | 11 | @Singleton 12 | @Component( 13 | modules = [ 14 | AndroidInjectionModule::class, 15 | AndroidSupportInjectionModule::class, 16 | ApplicationModule::class, 17 | ViewModelModule::class, 18 | FragmentBindingsModule::class, 19 | ActivityBindingsModule::class, 20 | ServiceBindingsModule::class, 21 | SharedPreferencesModule::class, 22 | NotificationsModule::class, 23 | FirebaseModule::class, 24 | NetworkModule::class 25 | ] 26 | ) 27 | internal interface ApplicationComponent : AndroidInjector { 28 | @Component.Builder 29 | abstract class Builder : AndroidInjector.Builder() 30 | } 31 | -------------------------------------------------------------------------------- /app/src/main/kotlin/co/netguru/baby/monitor/client/application/di/BabyMonitorGlideModule.kt: -------------------------------------------------------------------------------- 1 | package co.netguru.baby.monitor.client.application.di 2 | 3 | import com.bumptech.glide.annotation.GlideModule 4 | import com.bumptech.glide.module.AppGlideModule 5 | 6 | @GlideModule 7 | class BabyMonitorGlideModule : AppGlideModule() 8 | -------------------------------------------------------------------------------- /app/src/main/kotlin/co/netguru/baby/monitor/client/application/di/FirebaseModule.kt: -------------------------------------------------------------------------------- 1 | package co.netguru.baby.monitor.client.application.di 2 | 3 | import android.content.Context 4 | import co.netguru.baby.monitor.client.application.firebase.FirebaseRepository 5 | import co.netguru.baby.monitor.client.application.firebase.FirebaseSharedPreferencesWrapper 6 | import co.netguru.baby.monitor.client.feature.analytics.AnalyticsManager 7 | import co.netguru.baby.monitor.client.feature.firebasenotification.FirebaseInstanceManager 8 | import com.google.firebase.analytics.FirebaseAnalytics 9 | import com.google.firebase.iid.FirebaseInstanceId 10 | import dagger.Module 11 | import dagger.Provides 12 | import javax.inject.Singleton 13 | 14 | @Module 15 | object FirebaseModule { 16 | @Singleton 17 | @Provides 18 | fun firebaseRepository(preferencesWrapper: FirebaseSharedPreferencesWrapper, context: Context) = 19 | FirebaseRepository(preferencesWrapper, context) 20 | 21 | @Singleton 22 | @Provides 23 | fun firebaseInstanceManager() = FirebaseInstanceManager(FirebaseInstanceId.getInstance()) 24 | 25 | @Provides 26 | @Singleton 27 | fun provideAnalyticsManager(context: Context) = 28 | AnalyticsManager(FirebaseAnalytics.getInstance(context)) 29 | } 30 | -------------------------------------------------------------------------------- /app/src/main/kotlin/co/netguru/baby/monitor/client/application/di/NetworkModule.kt: -------------------------------------------------------------------------------- 1 | package co.netguru.baby.monitor.client.application.di 2 | 3 | import android.content.Context 4 | import android.net.nsd.NsdManager 5 | import co.netguru.baby.monitor.client.application.App 6 | import co.netguru.baby.monitor.client.feature.communication.nsd.DeviceNameProvider 7 | import co.netguru.baby.monitor.client.feature.communication.nsd.NsdServiceManager 8 | import com.google.gson.Gson 9 | import dagger.Module 10 | import dagger.Provides 11 | import dagger.Reusable 12 | import okhttp3.OkHttpClient 13 | 14 | @Module 15 | object NetworkModule { 16 | @Reusable 17 | @Provides 18 | fun nsdManager(app: App): NsdManager = app.getSystemService(Context.NSD_SERVICE) as NsdManager 19 | 20 | @Provides 21 | fun nsdServiceManager(nsdManager: NsdManager, deviceNameProvider: DeviceNameProvider) = 22 | NsdServiceManager(nsdManager, deviceNameProvider) 23 | 24 | @Provides 25 | @Reusable 26 | fun provideGson() = 27 | Gson() 28 | 29 | @Provides 30 | @Reusable 31 | fun provideOkHttp() = 32 | OkHttpClient.Builder() 33 | .build() 34 | } 35 | -------------------------------------------------------------------------------- /app/src/main/kotlin/co/netguru/baby/monitor/client/application/di/NotificationsModule.kt: -------------------------------------------------------------------------------- 1 | package co.netguru.baby.monitor.client.application.di 2 | 3 | import android.content.Context 4 | import co.netguru.baby.monitor.client.R 5 | import co.netguru.baby.monitor.client.common.NotificationHandler 6 | import co.netguru.baby.monitor.client.feature.babynotification.NotifyBabyEventUseCase 7 | import co.netguru.baby.monitor.client.feature.firebasenotification.FirebaseNotificationSender 8 | import dagger.Module 9 | import dagger.Provides 10 | 11 | @Module 12 | object NotificationsModule { 13 | @Provides 14 | fun notificationHandler(context: Context) = NotificationHandler(context) 15 | 16 | @Provides 17 | fun provideNotifyBabyEventUseCase( 18 | notificationSender: FirebaseNotificationSender, 19 | context: Context 20 | ) = 21 | NotifyBabyEventUseCase( 22 | notificationSender, 23 | mapOf( 24 | NotifyBabyEventUseCase.CRY_TITLE_KEY 25 | to context.resources.getString(R.string.notification_baby_is_crying_title), 26 | NotifyBabyEventUseCase.NOISE_TITLE_KEY 27 | to context.resources.getString(R.string.notification_noise_detected_title), 28 | NotifyBabyEventUseCase.NOTIFICATION_TEXT_KEY 29 | to context.resources.getString(R.string.notification_baby_is_crying_content) 30 | ) 31 | ) 32 | } 33 | -------------------------------------------------------------------------------- /app/src/main/kotlin/co/netguru/baby/monitor/client/application/di/ServiceBindingsModule.kt: -------------------------------------------------------------------------------- 1 | package co.netguru.baby.monitor.client.application.di 2 | 3 | import co.netguru.baby.monitor.client.feature.babynotification.BabyMonitorMessagingService 4 | import co.netguru.baby.monitor.client.feature.communication.webrtc.server.WebRtcService 5 | import co.netguru.baby.monitor.client.feature.communication.websocket.WebSocketServerService 6 | import co.netguru.baby.monitor.client.feature.babynotification.BabyEventActionIntentService 7 | import co.netguru.baby.monitor.client.feature.voiceAnalysis.VoiceAnalysisService 8 | import dagger.Module 9 | import dagger.android.ContributesAndroidInjector 10 | 11 | @Module 12 | internal abstract class ServiceBindingsModule { 13 | 14 | @ContributesAndroidInjector 15 | internal abstract fun bindVoiceAnalysisService(): VoiceAnalysisService 16 | 17 | @ContributesAndroidInjector 18 | internal abstract fun bindWebRtcService(): WebRtcService 19 | 20 | @ContributesAndroidInjector 21 | internal abstract fun bindWebSocketServerService(): WebSocketServerService 22 | 23 | @ContributesAndroidInjector 24 | internal abstract fun bindMessagingService(): BabyMonitorMessagingService 25 | 26 | @ContributesAndroidInjector 27 | internal abstract fun bindBabyEventActionIntentService(): BabyEventActionIntentService 28 | } 29 | -------------------------------------------------------------------------------- /app/src/main/kotlin/co/netguru/baby/monitor/client/application/di/SharedPreferencesModule.kt: -------------------------------------------------------------------------------- 1 | package co.netguru.baby.monitor.client.application.di 2 | 3 | import android.content.Context 4 | import android.content.SharedPreferences 5 | import co.netguru.baby.monitor.client.application.App 6 | import dagger.Module 7 | import dagger.Provides 8 | import javax.inject.Qualifier 9 | import javax.inject.Singleton 10 | 11 | @Module 12 | class SharedPreferencesModule { 13 | 14 | @ConfigurationPreferencesQualifier 15 | @Singleton 16 | @Provides 17 | fun provideConfigurationSharedPreferences(app: App): SharedPreferences = 18 | app.getSharedPreferences(app.packageName + CONFIGURATION_PREFERENCES, Context.MODE_PRIVATE) 19 | 20 | companion object { 21 | private const val CONFIGURATION_PREFERENCES = "configuration" 22 | } 23 | } 24 | 25 | @Qualifier 26 | annotation class ConfigurationPreferencesQualifier 27 | -------------------------------------------------------------------------------- /app/src/main/kotlin/co/netguru/baby/monitor/client/application/scope/ActivityScope.kt: -------------------------------------------------------------------------------- 1 | package co.netguru.baby.monitor.client.application.scope 2 | 3 | import javax.inject.Scope 4 | 5 | @Scope 6 | annotation class ActivityScope 7 | -------------------------------------------------------------------------------- /app/src/main/kotlin/co/netguru/baby/monitor/client/application/scope/FragmentScope.kt: -------------------------------------------------------------------------------- 1 | package co.netguru.baby.monitor.client.application.scope 2 | 3 | import javax.inject.Scope 4 | 5 | @Scope 6 | annotation class FragmentScope 7 | -------------------------------------------------------------------------------- /app/src/main/kotlin/co/netguru/baby/monitor/client/common/DateProvider.kt: -------------------------------------------------------------------------------- 1 | package co.netguru.baby.monitor.client.common 2 | 3 | import org.threeten.bp.LocalDateTime 4 | import org.threeten.bp.format.DateTimeFormatter 5 | 6 | object DateProvider { 7 | val midnight: LocalDateTime 8 | get() = LocalDateTime.now().withHour(0).withMinute(0).withSecond(0) 9 | val yesterdaysMidnight: LocalDateTime 10 | get() = LocalDateTime.now().withHour(0).withMinute(0).withSecond(0).minusDays(1) 11 | 12 | val headerFormatter = DateTimeFormatter.ofPattern("MMM dd, yyyy") 13 | val timeStampFormatter = DateTimeFormatter.ofPattern("hh:mm:ss a") 14 | } 15 | -------------------------------------------------------------------------------- /app/src/main/kotlin/co/netguru/baby/monitor/client/common/LocalDateTimeProvider.kt: -------------------------------------------------------------------------------- 1 | package co.netguru.baby.monitor.client.common 2 | 3 | import org.threeten.bp.LocalDateTime 4 | import javax.inject.Inject 5 | 6 | class LocalDateTimeProvider @Inject constructor() { 7 | fun now(): LocalDateTime = LocalDateTime.now() 8 | } 9 | -------------------------------------------------------------------------------- /app/src/main/kotlin/co/netguru/baby/monitor/client/common/PermissionUtils.kt: -------------------------------------------------------------------------------- 1 | package co.netguru.baby.monitor.client.common 2 | 3 | import android.app.Activity 4 | import android.content.Context 5 | import android.content.pm.PackageManager 6 | import androidx.core.app.ActivityCompat 7 | import androidx.core.content.ContextCompat 8 | 9 | object PermissionUtils { 10 | 11 | fun arePermissionsGranted(context: Context, vararg permission: String): Boolean { 12 | return !permission 13 | .any { !hasPermission(context, it) } 14 | } 15 | 16 | private fun hasPermission(context: Context, permission: String): Boolean { 17 | return ContextCompat.checkSelfPermission( 18 | context, 19 | permission 20 | ) == PackageManager.PERMISSION_GRANTED 21 | } 22 | 23 | fun getPermissionsRequestResult( 24 | activity: Activity, 25 | requestResultCode: Int, 26 | resultRequestCode: Int, 27 | grantResults: IntArray, 28 | vararg permission: String 29 | ): PermissionResult { 30 | return when { 31 | resultRequestCode != requestResultCode -> PermissionResult.NOT_GRANTED 32 | grantResults.none { it == PackageManager.PERMISSION_DENIED } -> PermissionResult.GRANTED 33 | permission.any { 34 | ActivityCompat.shouldShowRequestPermissionRationale( 35 | activity, 36 | it 37 | ) 38 | } -> PermissionResult.SHOW_RATIONALE 39 | else -> PermissionResult.NOT_GRANTED 40 | } 41 | } 42 | } 43 | 44 | enum class PermissionResult { 45 | GRANTED, 46 | NOT_GRANTED, 47 | SHOW_RATIONALE 48 | } 49 | -------------------------------------------------------------------------------- /app/src/main/kotlin/co/netguru/baby/monitor/client/common/Randomiser.kt: -------------------------------------------------------------------------------- 1 | package co.netguru.baby.monitor.client.common 2 | 3 | import javax.inject.Inject 4 | import kotlin.random.Random 5 | import kotlin.random.nextInt 6 | 7 | class Randomiser @Inject constructor() { 8 | 9 | fun getRandomDigits(numberOfDigits: Int) = 10 | List(numberOfDigits) { Random.nextInt(RANDOM_DIGIT_RANGE) } 11 | 12 | companion object { 13 | private val RANDOM_DIGIT_RANGE = 0..9 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /app/src/main/kotlin/co/netguru/baby/monitor/client/common/RunsInBackground.kt: -------------------------------------------------------------------------------- 1 | package co.netguru.baby.monitor.client.common 2 | 3 | annotation class RunsInBackground 4 | -------------------------------------------------------------------------------- /app/src/main/kotlin/co/netguru/baby/monitor/client/common/SchedulersProvider.kt: -------------------------------------------------------------------------------- 1 | package co.netguru.baby.monitor.client.common 2 | 3 | import io.reactivex.Scheduler 4 | import io.reactivex.android.schedulers.AndroidSchedulers 5 | import io.reactivex.schedulers.Schedulers 6 | import javax.inject.Inject 7 | 8 | interface ISchedulersProvider { 9 | fun io(): Scheduler = Schedulers.io() 10 | fun computation(): Scheduler = Schedulers.computation() 11 | fun mainThread(): Scheduler = AndroidSchedulers.mainThread() 12 | } 13 | 14 | class SchedulersProvider @Inject constructor() : ISchedulersProvider 15 | -------------------------------------------------------------------------------- /app/src/main/kotlin/co/netguru/baby/monitor/client/common/SingleLiveEvent.kt: -------------------------------------------------------------------------------- 1 | package co.netguru.baby.monitor.client.common 2 | 3 | import androidx.annotation.MainThread 4 | import androidx.annotation.Nullable 5 | import androidx.lifecycle.LifecycleOwner 6 | import androidx.lifecycle.MutableLiveData 7 | import androidx.lifecycle.Observer 8 | import timber.log.Timber 9 | import java.util.concurrent.atomic.AtomicBoolean 10 | 11 | class SingleLiveEvent : MutableLiveData() { 12 | 13 | private val mPending = AtomicBoolean(false) 14 | 15 | @MainThread 16 | override fun observe(owner: LifecycleOwner, observer: Observer) { 17 | 18 | if (hasActiveObservers()) { 19 | Timber.w("Multiple observers registered but only one will be notified of changes.") 20 | } 21 | 22 | // Observe the internal MutableLiveData 23 | super.observe(owner, Observer { t -> 24 | if (mPending.compareAndSet(true, false)) { 25 | observer.onChanged(t) 26 | } 27 | }) 28 | } 29 | 30 | @MainThread 31 | override fun setValue(@Nullable t: T?) { 32 | mPending.set(true) 33 | super.setValue(t) 34 | } 35 | 36 | /** 37 | * Used for cases where T is Void, to make calls cleaner. 38 | */ 39 | @MainThread 40 | fun call() { 41 | value = null 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /app/src/main/kotlin/co/netguru/baby/monitor/client/common/base/AnalyticScreen.kt: -------------------------------------------------------------------------------- 1 | package co.netguru.baby.monitor.client.common.base 2 | 3 | import co.netguru.baby.monitor.client.feature.analytics.Screen 4 | 5 | interface AnalyticScreen { 6 | val screen: Screen? 7 | } 8 | -------------------------------------------------------------------------------- /app/src/main/kotlin/co/netguru/baby/monitor/client/common/base/BaseFragment.kt: -------------------------------------------------------------------------------- 1 | package co.netguru.baby.monitor.client.common.base 2 | 3 | import android.os.Bundle 4 | import android.view.LayoutInflater 5 | import android.view.View 6 | import android.view.ViewGroup 7 | import co.netguru.baby.monitor.client.feature.analytics.AnalyticsManager 8 | import co.netguru.baby.monitor.client.feature.analytics.Screen 9 | import dagger.android.support.DaggerFragment 10 | import javax.inject.Inject 11 | 12 | abstract class BaseFragment : DaggerFragment(), AnalyticScreen { 13 | abstract val layoutResource: Int 14 | override val screen: Screen? = null 15 | 16 | @Inject 17 | lateinit var analyticsManager: AnalyticsManager 18 | 19 | override fun onCreateView( 20 | inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? 21 | ) = inflater.inflate(layoutResource, container, false) 22 | 23 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 24 | super.onViewCreated(view, savedInstanceState) 25 | screen?.run { 26 | analyticsManager.setCurrentScreen(requireActivity(), this) 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /app/src/main/kotlin/co/netguru/baby/monitor/client/common/base/BaseServiceWithFacade.kt: -------------------------------------------------------------------------------- 1 | package co.netguru.baby.monitor.client.common.base 2 | 3 | import dagger.android.DaggerService 4 | import javax.inject.Inject 5 | 6 | abstract class BaseServiceWithFacade> : 7 | DaggerService() { 8 | 9 | @Inject 10 | lateinit var serviceController: C 11 | 12 | override fun onCreate() { 13 | super.onCreate() 14 | attachServiceToController() 15 | } 16 | 17 | override fun onDestroy() { 18 | super.onDestroy() 19 | serviceController.detachService() 20 | } 21 | 22 | @Suppress("UNCHECKED_CAST") 23 | private fun attachServiceToController() { 24 | serviceController.attachService(this as T) 25 | } 26 | } 27 | 28 | interface ServiceFacade 29 | 30 | interface ServiceController { 31 | fun attachService(service: T) 32 | fun detachService() 33 | } 34 | -------------------------------------------------------------------------------- /app/src/main/kotlin/co/netguru/baby/monitor/client/common/extensions/ActivityExtensions.kt: -------------------------------------------------------------------------------- 1 | package co.netguru.baby.monitor.client.common.extensions 2 | 3 | import android.app.Activity 4 | import android.media.AudioManager 5 | 6 | fun Activity.controlVideoStreamVolume() { 7 | volumeControlStream = AudioManager.STREAM_VOICE_CALL 8 | } 9 | -------------------------------------------------------------------------------- /app/src/main/kotlin/co/netguru/baby/monitor/client/common/extensions/ContextExtensions.kt: -------------------------------------------------------------------------------- 1 | package co.netguru.baby.monitor.client.common.extensions 2 | 3 | import android.content.Context 4 | import android.content.pm.PackageManager 5 | import androidx.annotation.AttrRes 6 | import androidx.annotation.ColorRes 7 | import androidx.annotation.DrawableRes 8 | import androidx.core.content.ContextCompat 9 | import android.util.TypedValue 10 | import io.reactivex.Single 11 | import java.io.File 12 | import java.io.FileOutputStream 13 | 14 | fun Context.getColorCompat(@ColorRes color: Int) = ContextCompat.getColor(this, color) 15 | 16 | fun Context.getDrawableCompat(@DrawableRes drawable: Int) = 17 | ContextCompat.getDrawable(this, drawable) 18 | 19 | fun Context.getAttributeColor(@AttrRes attrColor: Int): Int { 20 | val typedValue = TypedValue() 21 | theme.resolveAttribute(attrColor, typedValue, true) 22 | return typedValue.data 23 | } 24 | 25 | fun Context.getAttributeDrawable(@AttrRes attrDrawableRes: Int): Int { 26 | val typedValue = TypedValue() 27 | theme.resolveAttribute(attrDrawableRes, typedValue, true) 28 | return typedValue.resourceId 29 | } 30 | 31 | fun Context.allPermissionsGranted(permissions: Array): Boolean { 32 | for (permission in permissions) { 33 | if (ContextCompat.checkSelfPermission(this, permission) 34 | != PackageManager.PERMISSION_GRANTED 35 | ) { 36 | return false 37 | } 38 | } 39 | 40 | return true 41 | } 42 | 43 | fun Context.saveAssetToCache(name: String) = Single.just(File(cacheDir.toString() + name)) 44 | .map { file -> 45 | assets.open(name).use { inputStream -> 46 | val buffer = ByteArray(inputStream.available()) 47 | inputStream.read(buffer) 48 | FileOutputStream(file).use { 49 | it.write(buffer) 50 | } 51 | return@map file 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /app/src/main/kotlin/co/netguru/baby/monitor/client/common/extensions/DataExtensions.kt: -------------------------------------------------------------------------------- 1 | package co.netguru.baby.monitor.client.common.extensions 2 | 3 | import com.google.gson.Gson 4 | 5 | inline fun String.toData(): T? = 6 | Gson().fromJson(this@toData, T::class.java) 7 | 8 | inline fun T.toJson(): String = 9 | Gson().toJson(this@toJson) 10 | 11 | infix fun A?.and(that: B?): Pair? = 12 | if (this == null || that == null) { 13 | null 14 | } else { 15 | Pair(this, that) 16 | } 17 | -------------------------------------------------------------------------------- /app/src/main/kotlin/co/netguru/baby/monitor/client/common/extensions/FragmentExtensions.kt: -------------------------------------------------------------------------------- 1 | package co.netguru.baby.monitor.client.common.extensions 2 | 3 | import android.content.Intent 4 | import android.content.ServiceConnection 5 | import android.content.res.Resources 6 | import android.net.Uri 7 | import android.provider.Settings 8 | import androidx.annotation.StringRes 9 | import androidx.core.content.res.ResourcesCompat 10 | import androidx.fragment.app.Fragment 11 | import com.google.android.material.snackbar.Snackbar 12 | 13 | fun Fragment.showSnackbarMessage( 14 | @StringRes resId: Int, 15 | action: (Snackbar.() -> Unit)? = null 16 | ): Snackbar? { 17 | return view?.run { 18 | Snackbar.make(this, resId, Snackbar.LENGTH_LONG).apply { 19 | action?.invoke(this) 20 | show() 21 | } 22 | } 23 | } 24 | 25 | fun Fragment.startAppSettings() { 26 | Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).run { 27 | addCategory(Intent.CATEGORY_DEFAULT) 28 | data = Uri.parse("package:" + requireContext().packageName) 29 | startActivity(this) 30 | } 31 | } 32 | 33 | fun Fragment.bindService(intentClass: Class<*>, conn: ServiceConnection, flags: Int) { 34 | requireContext().bindService( 35 | Intent(requireContext(), intentClass), 36 | conn, 37 | flags 38 | ) 39 | } 40 | 41 | fun Fragment.getColor(resource: Int, theme: Resources.Theme? = null) = ResourcesCompat.getColor( 42 | resources, 43 | resource, 44 | theme 45 | ) 46 | -------------------------------------------------------------------------------- /app/src/main/kotlin/co/netguru/baby/monitor/client/common/extensions/LiveDataExtensions.kt: -------------------------------------------------------------------------------- 1 | package co.netguru.baby.monitor.client.common.extensions 2 | 3 | import androidx.lifecycle.LifecycleOwner 4 | import androidx.lifecycle.LiveData 5 | import androidx.lifecycle.Observer 6 | 7 | fun LiveData.observeNonNull(owner: LifecycleOwner, observer: (t: T) -> Unit) { 8 | this.observe(owner, Observer { 9 | it?.let(observer) 10 | }) 11 | } 12 | -------------------------------------------------------------------------------- /app/src/main/kotlin/co/netguru/baby/monitor/client/common/extensions/NotificationExtensions.kt: -------------------------------------------------------------------------------- 1 | package co.netguru.baby.monitor.client.common.extensions 2 | 3 | import androidx.core.app.NotificationCompat 4 | 5 | fun NotificationCompat.Builder.addActions(actions: List?): NotificationCompat.Builder { 6 | actions?.forEach { 7 | addAction(it) 8 | } 9 | return this 10 | } 11 | -------------------------------------------------------------------------------- /app/src/main/kotlin/co/netguru/baby/monitor/client/common/extensions/RxExtensions.kt: -------------------------------------------------------------------------------- 1 | package co.netguru.baby.monitor.client.common.extensions 2 | 3 | import io.reactivex.* 4 | import io.reactivex.android.schedulers.AndroidSchedulers 5 | import io.reactivex.schedulers.Schedulers 6 | 7 | fun Completable.applyIoSchedulers() = this.subscribeOn(Schedulers.io()) 8 | .observeOn(AndroidSchedulers.mainThread()) 9 | 10 | fun Completable.applyComputationSchedulers() = this.subscribeOn(Schedulers.computation()) 11 | .observeOn(AndroidSchedulers.mainThread()) 12 | 13 | fun Maybe.applyIoSchedulers() = this.subscribeOn(Schedulers.io()) 14 | .observeOn(AndroidSchedulers.mainThread()) 15 | 16 | fun Maybe.applyComputationSchedulers() = this.subscribeOn(Schedulers.computation()) 17 | .observeOn(AndroidSchedulers.mainThread()) 18 | 19 | fun Single.applyIoSchedulers() = this.subscribeOn(Schedulers.io()) 20 | .observeOn(AndroidSchedulers.mainThread()) 21 | 22 | fun Single.applyComputationSchedulers() = this.subscribeOn(Schedulers.computation()) 23 | .observeOn(AndroidSchedulers.mainThread()) 24 | 25 | fun Observable.applyIoSchedulers() = this.subscribeOn(Schedulers.io()) 26 | .observeOn(AndroidSchedulers.mainThread()) 27 | 28 | fun Observable.applyComputationSchedulers() = this.subscribeOn(Schedulers.computation()) 29 | .observeOn(AndroidSchedulers.mainThread()) 30 | 31 | fun Flowable.applyIoSchedulers(): Flowable = this.subscribeOn(Schedulers.io()) 32 | .observeOn(AndroidSchedulers.mainThread()) 33 | 34 | fun Flowable.applyComputationSchedulers(): Flowable = 35 | this.subscribeOn(Schedulers.computation()) 36 | .observeOn(AndroidSchedulers.mainThread()) 37 | -------------------------------------------------------------------------------- /app/src/main/kotlin/co/netguru/baby/monitor/client/common/extensions/SharedPreferencesExtensions.kt: -------------------------------------------------------------------------------- 1 | package co.netguru.baby.monitor.client.common.extensions 2 | 3 | import android.content.SharedPreferences 4 | 5 | inline fun SharedPreferences.edit(action: SharedPreferences.Editor.() -> Unit) { 6 | val editor = edit() 7 | editor.action() 8 | editor.apply() 9 | } 10 | -------------------------------------------------------------------------------- /app/src/main/kotlin/co/netguru/baby/monitor/client/common/view/BaseViewHolder.kt: -------------------------------------------------------------------------------- 1 | package co.netguru.baby.monitor.client.common.view 2 | 3 | import androidx.recyclerview.widget.RecyclerView 4 | import android.view.View 5 | import kotlinx.android.extensions.LayoutContainer 6 | 7 | abstract class BaseViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView), LayoutContainer { 8 | 9 | override val containerView: View? = itemView 10 | 11 | abstract fun bindView(item: T) 12 | } 13 | -------------------------------------------------------------------------------- /app/src/main/kotlin/co/netguru/baby/monitor/client/common/view/CustomSurfaceViewRenderer.kt: -------------------------------------------------------------------------------- 1 | package co.netguru.baby.monitor.client.common.view 2 | 3 | import android.content.Context 4 | import android.util.AttributeSet 5 | import org.webrtc.EglBase 6 | import org.webrtc.RendererCommon 7 | import org.webrtc.SurfaceViewRenderer 8 | 9 | class CustomSurfaceViewRenderer(context: Context, attrs: AttributeSet?) : SurfaceViewRenderer(context, attrs) { 10 | 11 | internal var initialized = false 12 | 13 | constructor(context: Context) : this(context, null) 14 | 15 | override fun init(sharedContext: EglBase.Context?, rendererEvents: RendererCommon.RendererEvents?) { 16 | super.init(sharedContext, rendererEvents) 17 | initialized = true 18 | } 19 | 20 | override fun release() { 21 | super.release() 22 | initialized = false 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /app/src/main/kotlin/co/netguru/baby/monitor/client/common/view/StickyHeaderInterface.kt: -------------------------------------------------------------------------------- 1 | package co.netguru.baby.monitor.client.common.view 2 | 3 | import android.view.View 4 | 5 | interface StickyHeaderInterface { 6 | 7 | /** 8 | * This method gets called by [StickHeaderItemDecoration] to fetch the position of the header item in the adapter 9 | * that is used for (represents) item at specified position. 10 | * @param itemPosition int. Adapter's position of the item for which to do the search of the position of the header item. 11 | * @return int. Position of the header item in the adapter. 12 | */ 13 | fun getHeaderPositionForItem(itemPosition: Int): Int 14 | 15 | /** 16 | * This method gets called by [StickHeaderItemDecoration] to get layout resource id for the header item at specified adapter's position. 17 | * @param headerPosition int. Position of the header item in the adapter. 18 | * @return int. Layout resource id. 19 | */ 20 | fun getHeaderLayout(headerPosition: Int): Int 21 | 22 | /** 23 | * This method gets called by [StickHeaderItemDecoration] to setup the header View. 24 | * @param header View. Header to set the data on. 25 | * @param headerPosition int. Position of the header item in the adapter. 26 | */ 27 | fun bindHeaderData(header: View, headerPosition: Int) 28 | 29 | /** 30 | * This method gets called by [StickHeaderItemDecoration] to verify whether the item represents a header. 31 | * @param itemPosition int. 32 | * @return true, if item at the specified adapter's position represents a header. 33 | */ 34 | fun isHeader(itemPosition: Int): Boolean 35 | } 36 | -------------------------------------------------------------------------------- /app/src/main/kotlin/co/netguru/baby/monitor/client/data/AppDatabase.kt: -------------------------------------------------------------------------------- 1 | package co.netguru.baby.monitor.client.data 2 | 3 | import androidx.room.Database 4 | import androidx.room.RoomDatabase 5 | import androidx.room.TypeConverters 6 | import co.netguru.baby.monitor.client.data.client.ChildDataDao 7 | import co.netguru.baby.monitor.client.data.client.ChildDataEntity 8 | import co.netguru.baby.monitor.client.data.client.home.log.LogDataDao 9 | import co.netguru.baby.monitor.client.data.client.home.log.LogDataEntity 10 | import co.netguru.baby.monitor.client.data.communication.ClientDataDao 11 | import co.netguru.baby.monitor.client.data.communication.ClientEntity 12 | 13 | @Database( 14 | entities = [ 15 | LogDataEntity::class, 16 | ClientEntity::class, 17 | ChildDataEntity::class 18 | ], 19 | version = 5, 20 | exportSchema = false 21 | ) 22 | @TypeConverters(Converters::class) 23 | abstract class AppDatabase : RoomDatabase() { 24 | abstract fun logDataDao(): LogDataDao 25 | abstract fun clientDao(): ClientDataDao 26 | abstract fun childDataDao(): ChildDataDao 27 | 28 | companion object { 29 | const val DATABASE_NAME = "baby-monitor-database" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /app/src/main/kotlin/co/netguru/baby/monitor/client/data/Converters.kt: -------------------------------------------------------------------------------- 1 | package co.netguru.baby.monitor.client.data 2 | 3 | import androidx.room.TypeConverter 4 | import co.netguru.baby.monitor.client.feature.voiceAnalysis.VoiceAnalysisOption 5 | 6 | class Converters { 7 | @TypeConverter 8 | fun fromName(name: String) = VoiceAnalysisOption.valueOf(name) 9 | 10 | @TypeConverter 11 | fun optionToString(voiceAnalysisOption: VoiceAnalysisOption) = voiceAnalysisOption.name 12 | } 13 | -------------------------------------------------------------------------------- /app/src/main/kotlin/co/netguru/baby/monitor/client/data/client/ChildDataDao.kt: -------------------------------------------------------------------------------- 1 | package co.netguru.baby.monitor.client.data.client 2 | 3 | import androidx.lifecycle.LiveData 4 | import androidx.room.Dao 5 | import androidx.room.Insert 6 | import androidx.room.OnConflictStrategy 7 | import androidx.room.Query 8 | import co.netguru.baby.monitor.client.feature.voiceAnalysis.VoiceAnalysisOption 9 | import io.reactivex.Maybe 10 | 11 | @Dao 12 | interface ChildDataDao { 13 | 14 | @Query("SELECT * FROM CHILD_DATA LIMIT 1") 15 | fun getChildData(): Maybe 16 | 17 | @Query("SELECT * FROM CHILD_DATA LIMIT 1") 18 | fun getChildLiveData(): LiveData 19 | 20 | @Query("SELECT COUNT(id) FROM CHILD_DATA WHERE address LIKE :address") 21 | fun getCount(address: String): Int 22 | 23 | @Insert(onConflict = OnConflictStrategy.REPLACE) 24 | fun insertChildData(data: ChildDataEntity) 25 | 26 | @Query("UPDATE CHILD_DATA SET name = :name WHERE id = 0") 27 | fun updateChildName(name: String): Int 28 | 29 | @Query("UPDATE CHILD_DATA SET snoozeTimeStamp = :notificationSnoozeTimeStamp WHERE id = 0") 30 | fun updateNotificationSnoozeTimeStamp(notificationSnoozeTimeStamp: Long): Int 31 | 32 | @Query("UPDATE CHILD_DATA SET voiceAnalysisOption = :voiceAnalysisOption WHERE id = 0") 33 | fun updateVoiceAnalysisOption(voiceAnalysisOption: VoiceAnalysisOption) 34 | 35 | @Query("UPDATE CHILD_DATA SET noiseLevel = :noiseLevel WHERE id = 0") 36 | fun updateNoiseLevel(noiseLevel: Int) 37 | 38 | @Query("DELETE FROM CHILD_DATA") 39 | fun deleteAll() 40 | } 41 | -------------------------------------------------------------------------------- /app/src/main/kotlin/co/netguru/baby/monitor/client/data/client/ChildDataEntity.kt: -------------------------------------------------------------------------------- 1 | package co.netguru.baby.monitor.client.data.client 2 | 3 | import androidx.room.Entity 4 | import androidx.room.PrimaryKey 5 | import co.netguru.baby.monitor.client.feature.noisedetection.NoiseDetector 6 | import co.netguru.baby.monitor.client.feature.voiceAnalysis.VoiceAnalysisOption 7 | 8 | @Entity(tableName = "CHILD_DATA") 9 | data class ChildDataEntity( 10 | val address: String, 11 | var image: String? = null, 12 | val name: String? = null, 13 | val snoozeTimeStamp: Long? = null, 14 | val voiceAnalysisOption: VoiceAnalysisOption = VoiceAnalysisOption.MACHINE_LEARNING, 15 | val noiseLevel: Int = NoiseDetector.DEFAULT_NOISE_LEVEL, 16 | // Right now we are supporting only one child 17 | @PrimaryKey val id: Int = 0 18 | ) 19 | -------------------------------------------------------------------------------- /app/src/main/kotlin/co/netguru/baby/monitor/client/data/client/home/ToolbarState.kt: -------------------------------------------------------------------------------- 1 | package co.netguru.baby.monitor.client.data.client.home 2 | 3 | enum class ToolbarState { 4 | HIDDEN, HISTORY, DEFAULT 5 | } 6 | -------------------------------------------------------------------------------- /app/src/main/kotlin/co/netguru/baby/monitor/client/data/client/home/log/LogData.kt: -------------------------------------------------------------------------------- 1 | package co.netguru.baby.monitor.client.data.client.home.log 2 | 3 | import org.threeten.bp.LocalDateTime 4 | 5 | sealed class LogData { 6 | 7 | abstract val timeStamp: LocalDateTime 8 | 9 | data class Data( 10 | val action: String, 11 | override val timeStamp: LocalDateTime, 12 | val image: String? = null 13 | ) : LogData() 14 | 15 | data class LogHeader( 16 | override val timeStamp: LocalDateTime 17 | ) : LogData() 18 | 19 | class EndText(override val timeStamp: LocalDateTime) : LogData() 20 | } 21 | -------------------------------------------------------------------------------- /app/src/main/kotlin/co/netguru/baby/monitor/client/data/client/home/log/LogDataDao.kt: -------------------------------------------------------------------------------- 1 | package co.netguru.baby.monitor.client.data.client.home.log 2 | 3 | import androidx.room.Dao 4 | import androidx.room.Insert 5 | import androidx.room.Query 6 | import io.reactivex.* 7 | 8 | @Dao 9 | interface LogDataDao { 10 | 11 | @Query("SELECT * FROM LOG_DATA") 12 | fun getAllData(): Flowable> 13 | 14 | @Insert 15 | fun insertAll(vararg logs: LogDataEntity) 16 | 17 | @Query("DELETE FROM LOG_DATA") 18 | fun deleteAll() 19 | } 20 | -------------------------------------------------------------------------------- /app/src/main/kotlin/co/netguru/baby/monitor/client/data/client/home/log/LogDataEntity.kt: -------------------------------------------------------------------------------- 1 | package co.netguru.baby.monitor.client.data.client.home.log 2 | 3 | import androidx.room.ColumnInfo 4 | import androidx.room.Entity 5 | import androidx.room.PrimaryKey 6 | import org.threeten.bp.LocalDateTime 7 | 8 | @Entity(tableName = "LOG_DATA") 9 | data class LogDataEntity( 10 | @ColumnInfo(name = "action") val action: String, 11 | @ColumnInfo(name = "time_stamp") val timeStamp: String = LocalDateTime.now().toString(), 12 | @ColumnInfo(name = "image") val address: String? = null 13 | ) { 14 | @PrimaryKey(autoGenerate = true) 15 | var id: Int? = null 16 | 17 | fun toLogData(image: String?): LogData.Data = 18 | LogData.Data( 19 | action = this.action, 20 | timeStamp = LocalDateTime.parse(this.timeStamp), 21 | image = image ?: "" 22 | ) 23 | } 24 | -------------------------------------------------------------------------------- /app/src/main/kotlin/co/netguru/baby/monitor/client/data/communication/ClientDataDao.kt: -------------------------------------------------------------------------------- 1 | package co.netguru.baby.monitor.client.data.communication 2 | 3 | import androidx.room.* 4 | import co.netguru.baby.monitor.client.feature.voiceAnalysis.VoiceAnalysisOption 5 | import io.reactivex.Maybe 6 | 7 | @Dao 8 | interface ClientDataDao { 9 | 10 | @Insert(onConflict = OnConflictStrategy.REPLACE) 11 | fun insertClient(data: ClientEntity) 12 | 13 | @Query("SELECT * FROM CLIENT_DATA LIMIT 1") 14 | fun getClientData(): Maybe 15 | 16 | @Query("DELETE FROM CLIENT_DATA") 17 | fun deleteAll() 18 | 19 | @Query("UPDATE CLIENT_DATA SET voiceAnalysisOption = :voiceAnalysisOption WHERE id = 0") 20 | fun updateVoiceAnalysisOption(voiceAnalysisOption: VoiceAnalysisOption) 21 | 22 | @Query("UPDATE CLIENT_DATA SET noiseLevel = :noiseLevel WHERE id = 0") 23 | fun updateNoiseLevel(noiseLevel: Int) 24 | } 25 | -------------------------------------------------------------------------------- /app/src/main/kotlin/co/netguru/baby/monitor/client/data/communication/ClientEntity.kt: -------------------------------------------------------------------------------- 1 | package co.netguru.baby.monitor.client.data.communication 2 | 3 | import androidx.room.ColumnInfo 4 | import androidx.room.Entity 5 | import androidx.room.Index 6 | import androidx.room.PrimaryKey 7 | import co.netguru.baby.monitor.client.feature.noisedetection.NoiseDetector 8 | import co.netguru.baby.monitor.client.feature.voiceAnalysis.VoiceAnalysisOption 9 | 10 | @Entity( 11 | tableName = "CLIENT_DATA", 12 | indices = [Index(value = ["firebase_key"], unique = true)] 13 | ) 14 | data class ClientEntity( 15 | @ColumnInfo(name = "address") val address: String, 16 | @ColumnInfo(name = "firebase_key") var firebaseKey: String, 17 | val voiceAnalysisOption: VoiceAnalysisOption = VoiceAnalysisOption.MACHINE_LEARNING, 18 | val noiseLevel: Int = NoiseDetector.DEFAULT_NOISE_LEVEL, 19 | // There is only one parent handled right now 20 | @PrimaryKey val id: Int? = 0 21 | ) 22 | -------------------------------------------------------------------------------- /app/src/main/kotlin/co/netguru/baby/monitor/client/data/communication/SingleEvent.kt: -------------------------------------------------------------------------------- 1 | package co.netguru.baby.monitor.client.data.communication 2 | 3 | data class SingleEvent( 4 | private val event: T, 5 | private var dispatched: Boolean = false 6 | ) { 7 | var data: T? = event 8 | get() = if (dispatched) { 9 | null 10 | } else { 11 | dispatched = true 12 | event 13 | } 14 | set(value) { 15 | dispatched = false 16 | field = value 17 | } 18 | 19 | fun fetchData() = event 20 | } 21 | -------------------------------------------------------------------------------- /app/src/main/kotlin/co/netguru/baby/monitor/client/data/communication/nsd/DiscoveryStatus.kt: -------------------------------------------------------------------------------- 1 | package co.netguru.baby.monitor.client.data.communication.nsd 2 | 3 | enum class DiscoveryStatus { 4 | STARTED, STOPPED 5 | } 6 | -------------------------------------------------------------------------------- /app/src/main/kotlin/co/netguru/baby/monitor/client/data/communication/websocket/ClientConnectionStatus.kt: -------------------------------------------------------------------------------- 1 | package co.netguru.baby.monitor.client.data.communication.websocket 2 | 3 | 4 | enum class ClientConnectionStatus { 5 | EMPTY, 6 | CLIENT_CONNECTED, 7 | } 8 | -------------------------------------------------------------------------------- /app/src/main/kotlin/co/netguru/baby/monitor/client/data/server/CameraState.kt: -------------------------------------------------------------------------------- 1 | package co.netguru.baby.monitor.client.data.server 2 | 3 | data class CameraState( 4 | val previewEnabled: Boolean, 5 | val streamingEnabled: Boolean 6 | ) 7 | -------------------------------------------------------------------------------- /app/src/main/kotlin/co/netguru/baby/monitor/client/data/splash/AppStateHandler.kt: -------------------------------------------------------------------------------- 1 | package co.netguru.baby.monitor.client.data.splash 2 | 3 | import android.content.SharedPreferences 4 | import co.netguru.baby.monitor.client.application.di.ConfigurationPreferencesQualifier 5 | import co.netguru.baby.monitor.client.common.extensions.edit 6 | import javax.inject.Inject 7 | 8 | class AppStateHandler @Inject constructor( 9 | @ConfigurationPreferencesQualifier private val prefs: SharedPreferences 10 | ) { 11 | internal var appState: AppState 12 | get() = AppState.valueOf( 13 | prefs.getString(APP_STATE_KEY, null) ?: AppState.FIRST_OPEN.toString() 14 | ) 15 | set(value) { 16 | prefs.edit { 17 | putString(APP_STATE_KEY, value.toString()) 18 | } 19 | } 20 | 21 | companion object { 22 | private const val APP_STATE_KEY = "APP_STATE_KEY" 23 | } 24 | } 25 | 26 | enum class AppState { 27 | UNDEFINED, SERVER, CLIENT, FIRST_OPEN 28 | } 29 | -------------------------------------------------------------------------------- /app/src/main/kotlin/co/netguru/baby/monitor/client/feature/analytics/AnalyticsManager.kt: -------------------------------------------------------------------------------- 1 | package co.netguru.baby.monitor.client.feature.analytics 2 | 3 | import android.app.Activity 4 | import androidx.core.os.bundleOf 5 | import com.google.firebase.analytics.FirebaseAnalytics 6 | import timber.log.Timber 7 | 8 | class AnalyticsManager( 9 | private val firebaseAnalytics: FirebaseAnalytics 10 | ) { 11 | fun setCurrentScreen(activity: Activity, screen: Screen) { 12 | firebaseAnalytics.setCurrentScreen(activity, screen.screenName, null) 13 | Timber.d("$SCREEN ${screen.screenName}") 14 | } 15 | 16 | fun logEvent(event: Event) { 17 | when (event) { 18 | is Event.Simple -> firebaseAnalytics.logEvent(event.eventType.eventName, null) 19 | is Event.ParamEvent -> firebaseAnalytics.logEvent( 20 | event.eventType.eventName, 21 | bundleOf(event.param.first.paramName to event.param.second) 22 | ) 23 | } 24 | Timber.d("$EVENT ${event.eventType.eventName}") 25 | } 26 | 27 | fun setUserProperty(userProperty: UserProperty) { 28 | firebaseAnalytics.setUserProperty(userProperty.key, userProperty.value) 29 | } 30 | 31 | companion object { 32 | private const val EVENT = "event" 33 | private const val SCREEN = "screen" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /app/src/main/kotlin/co/netguru/baby/monitor/client/feature/analytics/Event.kt: -------------------------------------------------------------------------------- 1 | package co.netguru.baby.monitor.client.feature.analytics 2 | 3 | import co.netguru.baby.monitor.client.feature.firebasenotification.NotificationType 4 | import java.util.* 5 | 6 | sealed class Event(open val eventType: EventType) { 7 | data class Simple(override val eventType: EventType) : Event(eventType) 8 | sealed class ParamEvent( 9 | override val eventType: EventType, 10 | val param: Pair 11 | ) : Event(eventType) { 12 | data class NotificationSent(val value: NotificationType) : ParamEvent( 13 | EventType.NOTIFICATION_SENT, 14 | EventParam.TYPE to value.name.toLowerCase(Locale.getDefault()) 15 | ) 16 | 17 | data class NightMode(val value: Boolean) : ParamEvent( 18 | EventType.NIGHT_MODE, 19 | EventParam.IS_ENABLED to value 20 | ) 21 | } 22 | } 23 | 24 | enum class EventType(val eventName: String) { 25 | NOTIFICATION_SENT("notification_sent"), 26 | NOTIFICATION_SNOOZE("notification_snooze"), 27 | NOTIFICATION_OPEN_CAMERA("notification_open_camera"), 28 | RESET_APP("reset_app"), 29 | NIGHT_MODE("night_mode"), 30 | VIDEO_STREAM_CONNECTED("video_stream_connected"), 31 | VIDEO_STREAM_ERROR("video_stream_error") 32 | } 33 | 34 | enum class EventParam(val paramName: String) { 35 | TYPE("type"), 36 | IS_ENABLED("is_enabled") 37 | } 38 | -------------------------------------------------------------------------------- /app/src/main/kotlin/co/netguru/baby/monitor/client/feature/analytics/Screen.kt: -------------------------------------------------------------------------------- 1 | package co.netguru.baby.monitor.client.feature.analytics 2 | 3 | enum class Screen(val screenName: String) { 4 | SPLASH("Splash"), 5 | ONBOARDING("Onboarding"), 6 | INFO_ABOUT_DEVICES("InfoAboutDevices"), 7 | SPECIFY_DEVICE("SpecifyDevice"), 8 | VOICE_RECORDINGS_SETTING("VoiceRecordingsSetting"), 9 | CONNECT_WIFI("ConnectWifi"), 10 | PERMISSION_CAMERA("PermissionCamera"), 11 | PERMISSION_MICROPHONE("PermissionMicrophone"), 12 | PERMISSION_DENIED("PermissionDenied"), 13 | SETUP_INFORMATION("SetupInformation"), 14 | CHILD_MONITOR("ChildMonitor"), 15 | PARENT_DEVICE_INFO("ParentDeviceInfo"), 16 | SERVICE_DISCOVERY("ServiceDiscovery"), 17 | PAIRING("Pairing"), 18 | CONNECTION_FAILED("ConnectionFailed"), 19 | ALL_DONE("AllDone"), 20 | CLIENT_LIVE_CAMERA("ClientLiveCamera"), 21 | CLIENT_DASHBOARD("ClientDashboard"), 22 | CLIENT_ACTIVITY_LOG("ClientActivityLog") 23 | } 24 | -------------------------------------------------------------------------------- /app/src/main/kotlin/co/netguru/baby/monitor/client/feature/analytics/UserProperty.kt: -------------------------------------------------------------------------------- 1 | package co.netguru.baby.monitor.client.feature.analytics 2 | 3 | import co.netguru.baby.monitor.client.data.splash.AppState 4 | import co.netguru.baby.monitor.client.feature.voiceAnalysis.VoiceAnalysisOption 5 | 6 | sealed class UserProperty(val value: String, val key: String) { 7 | class VoiceAnalysis(voiceAnalysisOption: VoiceAnalysisOption) : 8 | UserProperty(voiceAnalysisOption.name.toLowerCase(), VOICE_ANALYSIS_KEY) 9 | 10 | class AppStateProperty(appState: AppState) : 11 | UserProperty(appState.name.toLowerCase(), APP_STATE_KEY) 12 | 13 | class NoiseLevel(noiseLevel: Int) : 14 | UserProperty(noiseLevel.toString(), NOISE_LEVEL_KEY) 15 | 16 | companion object { 17 | private const val VOICE_ANALYSIS_KEY = "voice_analysis" 18 | private const val APP_STATE_KEY = "app_state" 19 | private const val NOISE_LEVEL_KEY = "noise_level" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /app/src/main/kotlin/co/netguru/baby/monitor/client/feature/babynotification/BabyEventActionIntentService.kt: -------------------------------------------------------------------------------- 1 | package co.netguru.baby.monitor.client.feature.babynotification 2 | 3 | import android.app.IntentService 4 | import android.content.Intent 5 | import androidx.core.app.NotificationManagerCompat 6 | import androidx.core.os.bundleOf 7 | import androidx.navigation.NavDeepLinkBuilder 8 | import co.netguru.baby.monitor.client.common.NotificationHandler 9 | import co.netguru.baby.monitor.client.feature.babynotification.BabyMonitorMessagingService.Companion.CRYING_NOTIFICATION_ID 10 | import dagger.android.AndroidInjection 11 | import io.reactivex.disposables.CompositeDisposable 12 | import io.reactivex.rxkotlin.addTo 13 | import javax.inject.Inject 14 | 15 | class BabyEventActionIntentService : IntentService(NAME) { 16 | 17 | @Inject 18 | internal lateinit var snoozeNotificationUseCase: SnoozeNotificationUseCase 19 | @Inject 20 | internal lateinit var openCameraUseCase: OpenCameraUseCase 21 | private val disposables = CompositeDisposable() 22 | 23 | override fun onCreate() { 24 | super.onCreate() 25 | AndroidInjection.inject(this) 26 | } 27 | 28 | override fun onHandleIntent(intent: Intent?) { 29 | NotificationManagerCompat.from(this).cancel(CRYING_NOTIFICATION_ID) 30 | 31 | when (intent?.action) { 32 | NotificationHandler.SHOW_CAMERA_ACTION -> openCameraUseCase 33 | .openLiveClientCamera( 34 | NavDeepLinkBuilder(this), 35 | bundleOf(SHOULD_SHOW_SNOOZE_DIALOG to true) 36 | ) 37 | NotificationHandler.SNOOZE_ACTION -> snoozeNotificationUseCase 38 | .snoozeNotifications() 39 | .addTo(disposables) 40 | } 41 | } 42 | 43 | override fun onDestroy() { 44 | super.onDestroy() 45 | disposables.dispose() 46 | } 47 | 48 | companion object { 49 | private const val NAME = "CRYING_ACTION_INTENT_SERVICE" 50 | const val SHOULD_SHOW_SNOOZE_DIALOG = "SHOULD_SHOW_SNOOZE_DIALOG" 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /app/src/main/kotlin/co/netguru/baby/monitor/client/feature/babynotification/OpenCameraUseCase.kt: -------------------------------------------------------------------------------- 1 | package co.netguru.baby.monitor.client.feature.babynotification 2 | 3 | import android.os.Bundle 4 | import androidx.navigation.NavDeepLinkBuilder 5 | import co.netguru.baby.monitor.client.R 6 | import co.netguru.baby.monitor.client.feature.analytics.AnalyticsManager 7 | import co.netguru.baby.monitor.client.feature.analytics.Event 8 | import co.netguru.baby.monitor.client.feature.analytics.EventType 9 | import co.netguru.baby.monitor.client.feature.client.home.ClientHomeActivity 10 | import javax.inject.Inject 11 | 12 | class OpenCameraUseCase @Inject constructor( 13 | private val analyticsManager: AnalyticsManager 14 | ) { 15 | fun openLiveClientCamera(navDeepLinkBuilder: NavDeepLinkBuilder, snoozeDialogArgument: Bundle) { 16 | analyticsManager.logEvent(Event.Simple(EventType.NOTIFICATION_OPEN_CAMERA)) 17 | navDeepLinkBuilder 18 | .setComponentName(ClientHomeActivity::class.java) 19 | .setGraph(R.navigation.client_home_nav_graph) 20 | .setDestination(R.id.clientLiveCamera) 21 | .setArguments(snoozeDialogArgument) 22 | .createTaskStackBuilder() 23 | .startActivities() 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /app/src/main/kotlin/co/netguru/baby/monitor/client/feature/babynotification/SnoozeNotificationUseCase.kt: -------------------------------------------------------------------------------- 1 | package co.netguru.baby.monitor.client.feature.babynotification 2 | 3 | import co.netguru.baby.monitor.client.data.DataRepository 4 | import co.netguru.baby.monitor.client.feature.analytics.AnalyticsManager 5 | import co.netguru.baby.monitor.client.feature.analytics.Event 6 | import co.netguru.baby.monitor.client.feature.analytics.EventType 7 | import io.reactivex.disposables.Disposable 8 | import io.reactivex.schedulers.Schedulers 9 | import timber.log.Timber 10 | import java.util.concurrent.TimeUnit 11 | import javax.inject.Inject 12 | 13 | class SnoozeNotificationUseCase @Inject constructor( 14 | private val dataRepository: DataRepository, 15 | private val analyticsManager: AnalyticsManager 16 | ) { 17 | 18 | fun snoozeNotifications(): Disposable { 19 | analyticsManager.logEvent(Event.Simple(EventType.NOTIFICATION_SNOOZE)) 20 | return dataRepository 21 | .updateChildSnoozeTimestamp(System.currentTimeMillis() + SNOOZE_TIME) 22 | .subscribeOn(Schedulers.io()) 23 | .subscribe { Timber.i("Notification snooze timestamp updated") } 24 | } 25 | 26 | companion object { 27 | private val SNOOZE_TIME = TimeUnit.MINUTES.toMillis(5) 28 | const val SNOOZE_DIALOG_TAG = "SNOOZE_DIALOG" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /app/src/main/kotlin/co/netguru/baby/monitor/client/feature/batterylevel/LowBatteryReceiver.kt: -------------------------------------------------------------------------------- 1 | package co.netguru.baby.monitor.client.feature.batterylevel 2 | 3 | import android.content.BroadcastReceiver 4 | import android.content.Context 5 | import android.content.Intent 6 | import android.content.IntentFilter 7 | import co.netguru.baby.monitor.client.feature.server.ChildMonitorFragmentModule 8 | import javax.inject.Inject 9 | 10 | class LowBatteryReceiver @Inject constructor( 11 | @ChildMonitorFragmentModule.LowBatteryHandler private val lowBatteryHandler: () -> Unit 12 | ) : BroadcastReceiver() { 13 | override fun onReceive(context: Context, intent: Intent) { 14 | if (intent.action != Intent.ACTION_BATTERY_LOW) 15 | throw RuntimeException() 16 | lowBatteryHandler() 17 | } 18 | 19 | fun register(context: Context) { 20 | context.registerReceiver(this, IntentFilter(Intent.ACTION_BATTERY_LOW)) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /app/src/main/kotlin/co/netguru/baby/monitor/client/feature/batterylevel/NotifyLowBatteryUseCase.kt: -------------------------------------------------------------------------------- 1 | package co.netguru.baby.monitor.client.feature.batterylevel 2 | 3 | import co.netguru.baby.monitor.client.feature.firebasenotification.FirebaseNotificationSender 4 | import co.netguru.baby.monitor.client.feature.firebasenotification.NotificationType 5 | import timber.log.Timber 6 | import javax.inject.Inject 7 | 8 | class NotifyLowBatteryUseCase @Inject constructor( 9 | private val notificationSender: FirebaseNotificationSender 10 | ) { 11 | fun notifyLowBattery(title: String, text: String) = 12 | notificationSender.broadcastNotificationToFcm( 13 | title = title, 14 | text = text, 15 | notificationType = NotificationType.LOW_BATTERY_NOTIFICATION 16 | ).also { 17 | Timber.d("notifyLowBattery($title, $text)") 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /app/src/main/kotlin/co/netguru/baby/monitor/client/feature/client/configuration/AllDoneFragment.kt: -------------------------------------------------------------------------------- 1 | package co.netguru.baby.monitor.client.feature.client.configuration 2 | 3 | import android.os.Bundle 4 | import android.view.View 5 | import androidx.navigation.fragment.findNavController 6 | import co.netguru.baby.monitor.client.R 7 | import co.netguru.baby.monitor.client.common.base.BaseFragment 8 | import co.netguru.baby.monitor.client.feature.analytics.Screen 9 | import kotlinx.android.synthetic.main.fragment_all_done.* 10 | 11 | class AllDoneFragment : BaseFragment() { 12 | override val layoutResource = R.layout.fragment_all_done 13 | override val screen: Screen = Screen.ALL_DONE 14 | 15 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 16 | super.onViewCreated(view, savedInstanceState) 17 | addDoneCtrl.setOnClickListener { 18 | findNavController().navigate(R.id.allDoneToClientHome) 19 | requireActivity().finish() 20 | } 21 | allDoneBackIv.setOnClickListener { 22 | findNavController().navigateUp() 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /app/src/main/kotlin/co/netguru/baby/monitor/client/feature/client/configuration/ParentDeviceInfoFragment.kt: -------------------------------------------------------------------------------- 1 | package co.netguru.baby.monitor.client.feature.client.configuration 2 | 3 | import android.os.Bundle 4 | import android.view.View 5 | import androidx.navigation.fragment.findNavController 6 | import co.netguru.baby.monitor.client.R 7 | import co.netguru.baby.monitor.client.common.base.BaseFragment 8 | import co.netguru.baby.monitor.client.feature.analytics.Screen 9 | import kotlinx.android.synthetic.main.fragment_parent_device_info.* 10 | 11 | class ParentDeviceInfoFragment : BaseFragment() { 12 | override val layoutResource = R.layout.fragment_parent_device_info 13 | override val screen: Screen = Screen.PARENT_DEVICE_INFO 14 | 15 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 16 | super.onViewCreated(view, savedInstanceState) 17 | secondAppButtonCtrl.setOnClickListener { 18 | findNavController().navigate(R.id.secondAppInfoToServiceDiscovery) 19 | } 20 | 21 | secondAppInfoBackIv.setOnClickListener { 22 | findNavController().navigateUp() 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /app/src/main/kotlin/co/netguru/baby/monitor/client/feature/client/home/BackButtonState.kt: -------------------------------------------------------------------------------- 1 | package co.netguru.baby.monitor.client.feature.client.home 2 | 3 | data class BackButtonState( 4 | val shouldBeVisible: Boolean, 5 | val shouldShowSnoozeDialog: Boolean 6 | ) 7 | -------------------------------------------------------------------------------- /app/src/main/kotlin/co/netguru/baby/monitor/client/feature/client/home/RestartAppUseCase.kt: -------------------------------------------------------------------------------- 1 | package co.netguru.baby.monitor.client.feature.client.home 2 | 3 | import android.content.Intent 4 | import androidx.appcompat.app.AppCompatActivity 5 | import io.reactivex.Completable 6 | import javax.inject.Inject 7 | 8 | class RestartAppUseCase @Inject constructor() { 9 | 10 | fun restartApp(activity: AppCompatActivity): Completable = Completable.fromAction { 11 | val intent = 12 | activity.baseContext.packageManager.getLaunchIntentForPackage(activity.baseContext.packageName) 13 | ?.apply { 14 | addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP) 15 | addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) 16 | } 17 | 18 | activity.startActivity(intent) 19 | activity.finish() 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /app/src/main/kotlin/co/netguru/baby/monitor/client/feature/client/home/SendBabyNameUseCase.kt: -------------------------------------------------------------------------------- 1 | package co.netguru.baby.monitor.client.feature.client.home 2 | 3 | import co.netguru.baby.monitor.client.data.DataRepository 4 | import co.netguru.baby.monitor.client.feature.communication.websocket.Message 5 | import co.netguru.baby.monitor.client.feature.communication.websocket.RxWebSocketClient 6 | import io.reactivex.Completable 7 | import timber.log.Timber 8 | import javax.inject.Inject 9 | 10 | class SendBabyNameUseCase @Inject constructor( 11 | private val dataRepository: DataRepository 12 | ) { 13 | fun streamBabyName(client: RxWebSocketClient): Completable = 14 | dataRepository.getChildData() 15 | .map { it.name.orEmpty() } 16 | .flatMapCompletable { name -> 17 | sendBabyName(client, name) 18 | } 19 | 20 | private fun sendBabyName(client: RxWebSocketClient, babyName: String): Completable = 21 | client.send(Message(babyName = babyName)) 22 | .doOnError { Timber.w("Couldn't send baby name: $babyName.") } 23 | .doOnComplete { Timber.d("Baby name sent: $babyName.") } 24 | } 25 | -------------------------------------------------------------------------------- /app/src/main/kotlin/co/netguru/baby/monitor/client/feature/client/home/SendFirebaseTokenUseCase.kt: -------------------------------------------------------------------------------- 1 | package co.netguru.baby.monitor.client.feature.client.home 2 | 3 | import co.netguru.baby.monitor.client.feature.communication.websocket.Message 4 | import co.netguru.baby.monitor.client.feature.communication.websocket.RxWebSocketClient 5 | import co.netguru.baby.monitor.client.feature.firebasenotification.FirebaseInstanceManager 6 | import io.reactivex.Completable 7 | import javax.inject.Inject 8 | 9 | class SendFirebaseTokenUseCase @Inject constructor( 10 | private val firebaseInstanceManager: FirebaseInstanceManager 11 | ) { 12 | 13 | fun sendFirebaseToken(client: RxWebSocketClient): Completable = 14 | firebaseInstanceManager.getFirebaseToken() 15 | .flatMapCompletable { token -> 16 | client.send(Message(pushNotificationsToken = token)) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /app/src/main/kotlin/co/netguru/baby/monitor/client/feature/communication/communication.kt: -------------------------------------------------------------------------------- 1 | package co.netguru.baby.monitor.client.feature.communication 2 | 3 | const val SERVER_PORT = 10001 4 | -------------------------------------------------------------------------------- /app/src/main/kotlin/co/netguru/baby/monitor/client/feature/communication/internet/CheckInternetConnectionUseCase.kt: -------------------------------------------------------------------------------- 1 | package co.netguru.baby.monitor.client.feature.communication.internet 2 | 3 | import io.reactivex.Single 4 | import timber.log.Timber 5 | import java.io.IOException 6 | import java.net.InetSocketAddress 7 | import java.net.Socket 8 | import javax.inject.Inject 9 | 10 | class CheckInternetConnectionUseCase @Inject constructor() { 11 | 12 | fun hasInternetConnection(): Single { 13 | return Single.fromCallable { 14 | var isConnected = false 15 | try { 16 | val socket = Socket() 17 | val socketAddress = InetSocketAddress(GOOGLE_DNS_IP, PORT) 18 | 19 | socket.connect(socketAddress, TIMEOUT_MS) 20 | socket.close() 21 | 22 | isConnected = true 23 | } catch (e: IOException) { 24 | Timber.i(e) 25 | } 26 | isConnected 27 | } 28 | } 29 | companion object { 30 | private const val TIMEOUT_MS = 1500 31 | private const val PORT = 53 32 | private const val GOOGLE_DNS_IP = "8.8.8.8" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /app/src/main/kotlin/co/netguru/baby/monitor/client/feature/communication/nsd/DeviceNameProvider.kt: -------------------------------------------------------------------------------- 1 | package co.netguru.baby.monitor.client.feature.communication.nsd 2 | 3 | import com.jaredrummler.android.device.DeviceName 4 | import javax.inject.Inject 5 | 6 | class DeviceNameProvider @Inject constructor() : IDeviceNameProvider { 7 | override fun getDeviceName(): String = DeviceName.getDeviceName() 8 | } 9 | -------------------------------------------------------------------------------- /app/src/main/kotlin/co/netguru/baby/monitor/client/feature/communication/nsd/IDeviceNameProvider.kt: -------------------------------------------------------------------------------- 1 | package co.netguru.baby.monitor.client.feature.communication.nsd 2 | 3 | interface IDeviceNameProvider { 4 | fun getDeviceName(): String 5 | } 6 | -------------------------------------------------------------------------------- /app/src/main/kotlin/co/netguru/baby/monitor/client/feature/communication/nsd/NsdServiceDiffUtil.kt: -------------------------------------------------------------------------------- 1 | package co.netguru.baby.monitor.client.feature.communication.nsd 2 | 3 | import android.net.nsd.NsdServiceInfo 4 | import androidx.recyclerview.widget.DiffUtil 5 | 6 | class NsdServiceDiffUtil : DiffUtil.ItemCallback() { 7 | override fun areItemsTheSame(oldItem: NsdServiceInfo, newItem: NsdServiceInfo): Boolean { 8 | return oldItem == newItem 9 | } 10 | 11 | override fun areContentsTheSame(oldItem: NsdServiceInfo, newItem: NsdServiceInfo): Boolean { 12 | return oldItem.host == newItem.host 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /app/src/main/kotlin/co/netguru/baby/monitor/client/feature/communication/nsd/NsdServiceExceptions.kt: -------------------------------------------------------------------------------- 1 | package co.netguru.baby.monitor.client.feature.communication.nsd 2 | 3 | import java.lang.RuntimeException 4 | 5 | class ResolveFailedException : RuntimeException("Service resolution failed") 6 | class RegistrationFailedException : RuntimeException("Service registration failed") 7 | class StartDiscoveryFailedException : RuntimeException("Service discovery failed") 8 | -------------------------------------------------------------------------------- /app/src/main/kotlin/co/netguru/baby/monitor/client/feature/communication/nsd/NsdServicesAdapter.kt: -------------------------------------------------------------------------------- 1 | package co.netguru.baby.monitor.client.feature.communication.nsd 2 | 3 | import android.net.nsd.NsdServiceInfo 4 | import android.view.View 5 | import android.view.ViewGroup 6 | import androidx.recyclerview.widget.ListAdapter 7 | import androidx.recyclerview.widget.RecyclerView 8 | import co.netguru.baby.monitor.client.R 9 | 10 | class NsdServicesAdapter(private val onServiceClick: (nsdServiceInfo: NsdServiceInfo) -> Unit) : 11 | ListAdapter(NsdServiceDiffUtil()) { 12 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ServiceViewHolder { 13 | val view = View.inflate(parent.context, R.layout.found_service_item, null) 14 | val layoutParams = RecyclerView.LayoutParams( 15 | ViewGroup.LayoutParams.MATCH_PARENT, 16 | view.resources.getDimension(R.dimen.nsd_service_item_height).toInt() 17 | ) 18 | view.layoutParams = layoutParams 19 | return ServiceViewHolder(view) 20 | .apply { 21 | itemView.setOnClickListener { 22 | onServiceClick.invoke(getItem(adapterPosition)) 23 | } 24 | } 25 | } 26 | 27 | override fun onBindViewHolder(holder: ServiceViewHolder, position: Int) { 28 | holder.bind(getItem(position)) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /app/src/main/kotlin/co/netguru/baby/monitor/client/feature/communication/nsd/NsdState.kt: -------------------------------------------------------------------------------- 1 | package co.netguru.baby.monitor.client.feature.communication.nsd 2 | 3 | import android.net.nsd.NsdServiceInfo 4 | 5 | sealed class NsdState { 6 | data class InProgress( 7 | val serviceInfoList: List 8 | ) : NsdState() 9 | 10 | data class Error(val throwable: Throwable) : NsdState() 11 | 12 | data class Completed( 13 | val serviceInfoList: List 14 | ) : NsdState() 15 | } 16 | -------------------------------------------------------------------------------- /app/src/main/kotlin/co/netguru/baby/monitor/client/feature/communication/nsd/ServiceViewHolder.kt: -------------------------------------------------------------------------------- 1 | package co.netguru.baby.monitor.client.feature.communication.nsd 2 | 3 | import android.net.nsd.NsdServiceInfo 4 | import android.view.View 5 | import androidx.recyclerview.widget.RecyclerView 6 | import co.netguru.baby.monitor.client.R 7 | import kotlinx.android.synthetic.main.found_service_item.view.* 8 | 9 | class ServiceViewHolder(view: View) : RecyclerView.ViewHolder(view) { 10 | 11 | fun bind(nsdServiceInfo: NsdServiceInfo) { 12 | val deviceName = nsdServiceInfo.serviceName.removeSuffix(NsdServiceManager.SERVICE_NAME) 13 | itemView.deviceName.text = 14 | if (deviceName.isNotEmpty()) deviceName else itemView.resources.getString( 15 | R.string.unknown_device 16 | ) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /app/src/main/kotlin/co/netguru/baby/monitor/client/feature/communication/pairing/ConnectingDevicesFailedFragment.kt: -------------------------------------------------------------------------------- 1 | package co.netguru.baby.monitor.client.feature.communication.pairing 2 | 3 | import android.os.Bundle 4 | import android.view.View 5 | import androidx.activity.OnBackPressedCallback 6 | import androidx.navigation.fragment.findNavController 7 | import co.netguru.baby.monitor.client.R 8 | import co.netguru.baby.monitor.client.common.base.BaseFragment 9 | import co.netguru.baby.monitor.client.feature.analytics.Screen 10 | import kotlinx.android.synthetic.main.fragment_failed_devices_connecting.* 11 | 12 | class ConnectingDevicesFailedFragment : BaseFragment() { 13 | override val layoutResource = R.layout.fragment_failed_devices_connecting 14 | override val screen: Screen = Screen.CONNECTION_FAILED 15 | 16 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 17 | super.onViewCreated(view, savedInstanceState) 18 | configurationFailedTryAgainButton.setOnClickListener { 19 | findNavController().navigate(R.id.connectionFailedToServiceDiscovery) 20 | } 21 | setupOnBackPressedHandling() 22 | } 23 | 24 | private fun setupOnBackPressedHandling() { 25 | requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner, object : 26 | OnBackPressedCallback(true) { 27 | override fun handleOnBackPressed() { 28 | findNavController().navigate(R.id.connectionFailedToServiceDiscovery) 29 | } 30 | }) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /app/src/main/kotlin/co/netguru/baby/monitor/client/feature/communication/pairing/PairingViewModel.kt: -------------------------------------------------------------------------------- 1 | package co.netguru.baby.monitor.client.feature.communication.pairing 2 | 3 | import androidx.lifecycle.LiveData 4 | import androidx.lifecycle.ViewModel 5 | import co.netguru.baby.monitor.client.common.Randomiser 6 | import java.net.URI 7 | import javax.inject.Inject 8 | 9 | class PairingViewModel @Inject constructor( 10 | private val pairingUseCase: PairingUseCase, 11 | randomiser: Randomiser 12 | ) : ViewModel() { 13 | 14 | internal val pairingCompletedState: LiveData = pairingUseCase.pairingCompletedState 15 | val randomPairingCode = 16 | randomiser.getRandomDigits(NUMBER_OF_DIGITS_PAIRING_CODE).joinToString("") 17 | 18 | fun pair(address: URI) { 19 | pairingUseCase.pair(address, randomPairingCode) 20 | } 21 | 22 | fun cancelPairing() { 23 | pairingUseCase.cancelPairing() 24 | } 25 | 26 | override fun onCleared() { 27 | pairingUseCase.dispose() 28 | super.onCleared() 29 | } 30 | 31 | companion object { 32 | private const val NUMBER_OF_DIGITS_PAIRING_CODE = 4 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /app/src/main/kotlin/co/netguru/baby/monitor/client/feature/communication/pairing/ServiceDiscoveryViewModel.kt: -------------------------------------------------------------------------------- 1 | package co.netguru.baby.monitor.client.feature.communication.pairing 2 | 3 | import android.net.wifi.WifiManager 4 | import androidx.lifecycle.LiveData 5 | import androidx.lifecycle.ViewModel 6 | import co.netguru.baby.monitor.client.feature.communication.nsd.NsdServiceManager 7 | import co.netguru.baby.monitor.client.feature.communication.nsd.NsdState 8 | import javax.inject.Inject 9 | 10 | class ServiceDiscoveryViewModel @Inject constructor( 11 | private val nsdServiceManager: NsdServiceManager 12 | ) : ViewModel() { 13 | 14 | private var multicastLock: WifiManager.MulticastLock? = null 15 | val nsdStateLiveData: LiveData = nsdServiceManager.nsdStateLiveData 16 | 17 | internal fun discoverNsdService( 18 | wifiManager: WifiManager 19 | ) { 20 | acquireMulticastLock(wifiManager) 21 | nsdServiceManager.discoverService() 22 | } 23 | 24 | private fun acquireMulticastLock(wifiManager: WifiManager) { 25 | // Pixel workaround for discovering services 26 | multicastLock = wifiManager.createMulticastLock(MUTLICAST_LOCK_TAG) 27 | multicastLock?.setReferenceCounted(true) 28 | multicastLock?.acquire() 29 | } 30 | 31 | internal fun stopNsdServiceDiscovery() { 32 | releaseMulticastLock() 33 | nsdServiceManager.stopServiceDiscovery() 34 | } 35 | 36 | private fun releaseMulticastLock() { 37 | multicastLock?.release() 38 | multicastLock = null 39 | } 40 | 41 | override fun onCleared() { 42 | nsdServiceManager.stopServiceDiscovery() 43 | super.onCleared() 44 | } 45 | 46 | companion object { 47 | internal const val MUTLICAST_LOCK_TAG = "multicastLock" 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /app/src/main/kotlin/co/netguru/baby/monitor/client/feature/communication/webrtc/StreamState.kt: -------------------------------------------------------------------------------- 1 | package co.netguru.baby.monitor.client.feature.communication.webrtc 2 | 3 | import org.webrtc.IceCandidate 4 | import org.webrtc.MediaStream 5 | import org.webrtc.PeerConnection 6 | 7 | sealed class StreamState 8 | data class ConnectionState(val connectionState: RtcConnectionState) : StreamState() 9 | data class GatheringState(val gatheringState: PeerConnection.IceGatheringState?) : StreamState() 10 | data class OnIceCandidatesChange(val iceCandidateState: IceCandidateState) : StreamState() 11 | data class OnAddStream(val mediaStream: MediaStream) : StreamState() 12 | 13 | sealed class IceCandidateState 14 | data class OnIceCandidateAdded(val iceCandidate: IceCandidate) : IceCandidateState() 15 | data class OnIceCandidatesRemoved(val iceCandidates: Array?) : IceCandidateState() 16 | 17 | sealed class RtcConnectionState { 18 | object ConnectionOffer : RtcConnectionState() 19 | object Completed : RtcConnectionState() 20 | object Checking : RtcConnectionState() 21 | object Connected : RtcConnectionState() 22 | object Disconnected : RtcConnectionState() 23 | object Error : RtcConnectionState() 24 | } 25 | -------------------------------------------------------------------------------- /app/src/main/kotlin/co/netguru/baby/monitor/client/feature/communication/webrtc/base/RtcMessageHandler.kt: -------------------------------------------------------------------------------- 1 | package co.netguru.baby.monitor.client.feature.communication.webrtc.base 2 | 3 | import co.netguru.baby.monitor.client.feature.communication.websocket.Message 4 | 5 | interface RtcMessageHandler { 6 | fun handleSdpAnswerMessage(sdpData: Message.SdpData) 7 | fun handleIceCandidateMessage(iceCandidateData: Message.IceCandidateData) 8 | fun handleBabyDeviceSdpError(error: String) 9 | } 10 | -------------------------------------------------------------------------------- /app/src/main/kotlin/co/netguru/baby/monitor/client/feature/communication/webrtc/observers/DefaultObserver.kt: -------------------------------------------------------------------------------- 1 | package co.netguru.baby.monitor.client.feature.communication.webrtc.observers 2 | 3 | import org.webrtc.* 4 | 5 | open class DefaultObserver : PeerConnection.Observer { 6 | override fun onIceCandidate(iceCandidate: IceCandidate) = Unit 7 | override fun onDataChannel(dataChannel: DataChannel?) = Unit 8 | override fun onIceConnectionReceivingChange(receiving: Boolean) = Unit 9 | override fun onIceConnectionChange(iceConnectionState: PeerConnection.IceConnectionState?) = Unit 10 | override fun onIceGatheringChange(iceGatheringState: PeerConnection.IceGatheringState?) = Unit 11 | override fun onAddStream(mediaStream: MediaStream) = Unit 12 | override fun onSignalingChange(signalingState: PeerConnection.SignalingState?) = Unit 13 | override fun onIceCandidatesRemoved(iceCandidates: Array?) = Unit 14 | override fun onRemoveStream(mediaStream: MediaStream?) = Unit 15 | override fun onRenegotiationNeeded() = Unit 16 | override fun onAddTrack(rtpReceiver: RtpReceiver?, mediaStreams: Array?) = Unit 17 | } 18 | -------------------------------------------------------------------------------- /app/src/main/kotlin/co/netguru/baby/monitor/client/feature/communication/webrtc/observers/DefaultSdpObserver.kt: -------------------------------------------------------------------------------- 1 | package co.netguru.baby.monitor.client.feature.communication.webrtc.observers 2 | 3 | import org.webrtc.SdpObserver 4 | import org.webrtc.SessionDescription 5 | 6 | open class DefaultSdpObserver( 7 | private val onSetFailure: (error: String?) -> Unit = {}, 8 | private val onSetSuccess: () -> Unit = {}, 9 | private val onCreateSuccess: (sessionDescription: SessionDescription?) -> Unit = {}, 10 | private val onCreateFailure: (error: String?) -> Unit = {} 11 | ) : SdpObserver { 12 | 13 | override fun onSetFailure(error: String?) { 14 | onSetFailure.invoke(error) 15 | } 16 | 17 | override fun onSetSuccess() { 18 | onSetSuccess.invoke() 19 | } 20 | override fun onCreateSuccess(sessionDescription: SessionDescription?) { 21 | onCreateSuccess.invoke(sessionDescription) 22 | } 23 | override fun onCreateFailure(error: String?){ 24 | onCreateFailure.invoke(error) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/src/main/kotlin/co/netguru/baby/monitor/client/feature/communication/websocket/Message.kt: -------------------------------------------------------------------------------- 1 | package co.netguru.baby.monitor.client.feature.communication.websocket 2 | 3 | import com.google.gson.annotations.SerializedName 4 | 5 | data class Message( 6 | // Action 7 | @SerializedName("action") val action: String? = null, 8 | // Rtc 9 | @SerializedName("iceCandidate") val iceCandidate: IceCandidateData? = null, 10 | @SerializedName("errorSDP") val sdpError: String? = null, 11 | @SerializedName("offerSDP") val sdpOffer: SdpData? = null, 12 | @SerializedName("answerSDP") val sdpAnswer: SdpData? = null, 13 | // Config 14 | @SerializedName("baby_name") val babyName: String? = null, 15 | @SerializedName("pushNotificationsToken") val pushNotificationsToken: String? = null, 16 | @SerializedName("voiceAnalysisOption") val voiceAnalysisOption: String? = null, 17 | @SerializedName("noiseLevel") val noiseLevel: Int? = null, 18 | // Pairing 19 | @SerializedName("pairingCode") val pairingCode: String? = null, 20 | @SerializedName("pairingResponse") val pairingApproved: Boolean? = null, 21 | @SerializedName("confirmationId") val confirmationId: String? = null 22 | ) { 23 | data class SdpData( 24 | @SerializedName("sdp") val sdp: String, 25 | @SerializedName("type") val type: String 26 | ) 27 | 28 | data class IceCandidateData( 29 | @SerializedName("candidate") val sdp: String, 30 | @SerializedName("id") val sdpMid: String, 31 | @SerializedName("label") val sdpMLineIndex: Int 32 | ) 33 | 34 | companion object { 35 | const val RESET_ACTION = "reset" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /app/src/main/kotlin/co/netguru/baby/monitor/client/feature/communication/websocket/MessageController.kt: -------------------------------------------------------------------------------- 1 | package co.netguru.baby.monitor.client.feature.communication.websocket 2 | 3 | import io.reactivex.Observable 4 | 5 | interface MessageController { 6 | fun sendMessage(message: Message) 7 | fun receivedMessages(): Observable 8 | } 9 | -------------------------------------------------------------------------------- /app/src/main/kotlin/co/netguru/baby/monitor/client/feature/communication/websocket/MessageParser.kt: -------------------------------------------------------------------------------- 1 | package co.netguru.baby.monitor.client.feature.communication.websocket 2 | 3 | import com.google.gson.Gson 4 | import com.google.gson.JsonParseException 5 | import timber.log.Timber 6 | import javax.inject.Inject 7 | 8 | class MessageParser @Inject constructor( 9 | private val gson: Gson 10 | ) { 11 | fun parseWebSocketMessage(event: RxWebSocketClient.Event.Message): Message? { 12 | return try { 13 | gson.fromJson(event.message, Message::class.java) 14 | } catch (e: JsonParseException) { 15 | Timber.w(e) 16 | null 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /app/src/main/kotlin/co/netguru/baby/monitor/client/feature/debug/DebugState.kt: -------------------------------------------------------------------------------- 1 | package co.netguru.baby.monitor.client.feature.debug 2 | 3 | data class DebugState( 4 | val notificationInformation: String, 5 | val cryingProbability: Float, 6 | val decibels: Int 7 | ) 8 | -------------------------------------------------------------------------------- /app/src/main/kotlin/co/netguru/baby/monitor/client/feature/firebasenotification/FirebaseInstanceManager.kt: -------------------------------------------------------------------------------- 1 | package co.netguru.baby.monitor.client.feature.firebasenotification 2 | 3 | import com.google.firebase.iid.FirebaseInstanceId 4 | import io.reactivex.Single 5 | import javax.inject.Inject 6 | 7 | class FirebaseInstanceManager @Inject constructor( 8 | private val firebaseInstanceId: FirebaseInstanceId 9 | ) { 10 | fun getFirebaseToken(): Single = Single.create { emitter -> 11 | firebaseInstanceId.instanceId.addOnCompleteListener { task -> 12 | if (task.isSuccessful) { 13 | task.result?.let { emitter.onSuccess(it.token) } 14 | } else { 15 | emitter.onError(task.exception ?: Throwable()) 16 | } 17 | } 18 | } 19 | 20 | fun invalidateFirebaseToken() { 21 | firebaseInstanceId.deleteInstanceId() 22 | } 23 | 24 | companion object { 25 | internal const val PUSH_NOTIFICATIONS_KEY = "PUSH_NOTIFICATIONS_KEY" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /app/src/main/kotlin/co/netguru/baby/monitor/client/feature/firebasenotification/Message.kt: -------------------------------------------------------------------------------- 1 | package co.netguru.baby.monitor.client.feature.firebasenotification 2 | 3 | import com.google.gson.annotations.SerializedName 4 | 5 | data class Message( 6 | @SerializedName("to") 7 | val firebaseToken: String, 8 | @SerializedName("notification") 9 | val notification: Notification? = null, 10 | @SerializedName("data") 11 | val data: Data? = null 12 | ) 13 | 14 | data class Notification( 15 | @SerializedName("title") 16 | val title: String, 17 | @SerializedName("text") 18 | val text: String 19 | ) 20 | 21 | data class Data( 22 | @SerializedName("title") 23 | val title: String, 24 | @SerializedName("text") 25 | val text: String, 26 | @SerializedName("notification_type") 27 | val type: String 28 | ) 29 | -------------------------------------------------------------------------------- /app/src/main/kotlin/co/netguru/baby/monitor/client/feature/firebasenotification/NotificationType.kt: -------------------------------------------------------------------------------- 1 | package co.netguru.baby.monitor.client.feature.firebasenotification 2 | 3 | enum class NotificationType { 4 | CRY_NOTIFICATION, 5 | LOW_BATTERY_NOTIFICATION, 6 | NOISE_NOTIFICATION, 7 | DEFAULT 8 | } 9 | -------------------------------------------------------------------------------- /app/src/main/kotlin/co/netguru/baby/monitor/client/feature/noisedetection/NoiseDetector.kt: -------------------------------------------------------------------------------- 1 | package co.netguru.baby.monitor.client.feature.noisedetection 2 | 3 | import co.netguru.baby.monitor.client.feature.voiceAnalysis.AacRecorder.Companion.SAMPLING_RATE 4 | import io.reactivex.Single 5 | import timber.log.Timber 6 | import javax.inject.Inject 7 | import kotlin.math.log10 8 | import kotlin.math.roundToInt 9 | import kotlin.math.sqrt 10 | 11 | class NoiseDetector @Inject constructor() { 12 | fun processData(data: ShortArray) = Single.fromCallable { 13 | var decibels = 0.0 14 | if (data.isNotEmpty()) { 15 | var totalSquared = 0.0 16 | for (soundBit in data) { 17 | totalSquared += (soundBit * soundBit).toDouble() 18 | } 19 | 20 | val quadraticMeanPressure = sqrt(totalSquared / data.size) 21 | decibels = FORMULA_CONSTANT * log10(quadraticMeanPressure) 22 | } 23 | Timber.d("Sound Db: $decibels") 24 | return@fromCallable decibels.roundToInt() 25 | } 26 | 27 | companion object { 28 | internal const val DATA_SIZE = SAMPLING_RATE / 10 29 | const val DEFAULT_NOISE_LEVEL = 65 30 | private const val FORMULA_CONSTANT = 20 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /app/src/main/kotlin/co/netguru/baby/monitor/client/feature/onboarding/FinishOnboardingUseCase.kt: -------------------------------------------------------------------------------- 1 | package co.netguru.baby.monitor.client.feature.onboarding 2 | 3 | import co.netguru.baby.monitor.client.data.DataRepository 4 | import co.netguru.baby.monitor.client.data.splash.AppState 5 | import javax.inject.Inject 6 | 7 | class FinishOnboardingUseCase @Inject constructor( 8 | private val dataRepository: DataRepository 9 | ) { 10 | 11 | fun finishOnboarding() { 12 | dataRepository.saveConfiguration(AppState.UNDEFINED) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /app/src/main/kotlin/co/netguru/baby/monitor/client/feature/onboarding/InfoAboutDevicesFragment.kt: -------------------------------------------------------------------------------- 1 | package co.netguru.baby.monitor.client.feature.onboarding 2 | 3 | import android.os.Bundle 4 | import android.text.Html 5 | import android.view.View 6 | import androidx.navigation.fragment.findNavController 7 | import co.netguru.baby.monitor.client.R 8 | import co.netguru.baby.monitor.client.common.base.BaseFragment 9 | import co.netguru.baby.monitor.client.feature.analytics.Screen 10 | import kotlinx.android.synthetic.main.fragment_info_about_devices.* 11 | 12 | class InfoAboutDevicesFragment : BaseFragment() { 13 | override val layoutResource = R.layout.fragment_info_about_devices 14 | override val screen: Screen = Screen.INFO_ABOUT_DEVICES 15 | 16 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 17 | super.onViewCreated(view, savedInstanceState) 18 | specifyDeviceDescriptionTv.text = Html.fromHtml(getString(R.string.sync_description)) 19 | specifyDeviceBtn.setOnClickListener { 20 | findNavController().navigate(R.id.infoAboutDevicesToSpecifyDevice) 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /app/src/main/kotlin/co/netguru/baby/monitor/client/feature/onboarding/OnboardingActivity.kt: -------------------------------------------------------------------------------- 1 | package co.netguru.baby.monitor.client.feature.onboarding 2 | 3 | import android.os.Bundle 4 | import androidx.appcompat.app.AppCompatActivity 5 | import androidx.navigation.findNavController 6 | import co.netguru.baby.monitor.client.R 7 | 8 | class OnboardingActivity : AppCompatActivity() { 9 | 10 | override fun onCreate(savedInstanceState: Bundle?) { 11 | super.onCreate(savedInstanceState) 12 | setContentView(R.layout.activity_onboarding) 13 | } 14 | 15 | override fun onBackPressed() { 16 | val controller = findNavController(R.id.onboardingNavigationHostFragment).currentDestination?.id 17 | ?: 0 18 | if (controller == R.id.permissionMicrophone || controller == R.id.permissionMicrophone || 19 | controller == R.id.setupInformation) { 20 | findNavController(R.id.onboardingNavigationHostFragment).popBackStack(R.id.connectWiFi, false) 21 | } else if (controller == R.id.allDone) { 22 | findNavController(R.id.onboardingNavigationHostFragment).popBackStack(R.id.parentDeviceInfo, false) 23 | } else { 24 | super.onBackPressed() 25 | } 26 | } 27 | 28 | override fun onSupportNavigateUp() = 29 | findNavController(R.id.onboardingNavigationHostFragment).navigateUp() 30 | } 31 | -------------------------------------------------------------------------------- /app/src/main/kotlin/co/netguru/baby/monitor/client/feature/onboarding/SpecifyDeviceFragment.kt: -------------------------------------------------------------------------------- 1 | package co.netguru.baby.monitor.client.feature.onboarding 2 | 3 | import android.os.Bundle 4 | import android.view.View 5 | import androidx.navigation.fragment.findNavController 6 | import co.netguru.baby.monitor.client.R 7 | import co.netguru.baby.monitor.client.common.base.BaseFragment 8 | import co.netguru.baby.monitor.client.feature.analytics.Screen 9 | import kotlinx.android.synthetic.main.fragment_specify_device.* 10 | 11 | class SpecifyDeviceFragment : BaseFragment() { 12 | override val layoutResource = R.layout.fragment_specify_device 13 | override val screen: Screen = Screen.SPECIFY_DEVICE 14 | 15 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 16 | super.onViewCreated(view, savedInstanceState) 17 | babyCtl.setOnClickListener { 18 | findNavController().navigate(R.id.specifyDeviceToFeatureD) 19 | } 20 | parentCtl.setOnClickListener { 21 | findNavController().navigate(R.id.specifyDeviceToParentDeviceInfo) 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /app/src/main/kotlin/co/netguru/baby/monitor/client/feature/onboarding/VoiceRecordingsSettingsFragment.kt: -------------------------------------------------------------------------------- 1 | package co.netguru.baby.monitor.client.feature.onboarding 2 | 3 | import android.os.Bundle 4 | import android.view.View 5 | import androidx.lifecycle.ViewModelProvider 6 | import androidx.lifecycle.ViewModelProviders 7 | import androidx.navigation.fragment.findNavController 8 | import co.netguru.baby.monitor.client.R 9 | import co.netguru.baby.monitor.client.common.base.BaseFragment 10 | import co.netguru.baby.monitor.client.feature.analytics.Screen 11 | import co.netguru.baby.monitor.client.feature.settings.ConfigurationViewModel 12 | import kotlinx.android.synthetic.main.fragment_voice_recordings_setting.* 13 | import javax.inject.Inject 14 | 15 | class VoiceRecordingsSettingsFragment : BaseFragment() { 16 | override val layoutResource = R.layout.fragment_voice_recordings_setting 17 | override val screen: Screen = Screen.VOICE_RECORDINGS_SETTING 18 | 19 | @Inject 20 | lateinit var factory: ViewModelProvider.Factory 21 | 22 | private val viewModel by lazy { ViewModelProviders.of(this, factory)[ConfigurationViewModel::class.java] } 23 | 24 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 25 | super.onViewCreated(view, savedInstanceState) 26 | featureDNextBtn.setOnClickListener { 27 | findNavController().navigate(R.id.featureDToConnecting) 28 | } 29 | featureDSwitch.setOnCheckedChangeListener { buttonView, isChecked -> 30 | viewModel.setUploadEnabled(isChecked) 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /app/src/main/kotlin/co/netguru/baby/monitor/client/feature/onboarding/baby/PermissionCameraFragment.kt: -------------------------------------------------------------------------------- 1 | package co.netguru.baby.monitor.client.feature.onboarding.baby 2 | 3 | import android.Manifest 4 | import androidx.navigation.fragment.findNavController 5 | import co.netguru.baby.monitor.client.R 6 | import co.netguru.baby.monitor.client.common.base.BaseFragment 7 | import co.netguru.baby.monitor.client.common.extensions.allPermissionsGranted 8 | import co.netguru.baby.monitor.client.feature.analytics.Screen 9 | 10 | class PermissionCameraFragment : BaseFragment() { 11 | override val layoutResource = R.layout.fragment_camera_permission 12 | override val screen: Screen = Screen.PERMISSION_CAMERA 13 | 14 | override fun onResume() { 15 | super.onResume() 16 | requestPermissions(permissions, PERMISSIONS_REQUEST_CODE) 17 | } 18 | 19 | override fun onRequestPermissionsResult(requestCode: Int, permissions: Array, grantResults: IntArray) { 20 | super.onRequestPermissionsResult(requestCode, permissions, grantResults) 21 | findNavController().navigate( 22 | if (requireContext().allPermissionsGranted(Companion.permissions)) { 23 | R.id.permissionCameraToPermissionMicrophone 24 | } else { 25 | R.id.permissionCameraToPermissionDenied 26 | } 27 | ) 28 | } 29 | 30 | companion object { 31 | private const val PERMISSIONS_REQUEST_CODE = 125 32 | 33 | private val permissions = arrayOf( 34 | Manifest.permission.CAMERA 35 | ) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /app/src/main/kotlin/co/netguru/baby/monitor/client/feature/onboarding/baby/PermissionDenied.kt: -------------------------------------------------------------------------------- 1 | package co.netguru.baby.monitor.client.feature.onboarding.baby 2 | 3 | import android.os.Bundle 4 | import android.view.View 5 | import androidx.navigation.fragment.findNavController 6 | import co.netguru.baby.monitor.client.R 7 | import co.netguru.baby.monitor.client.common.base.BaseFragment 8 | import co.netguru.baby.monitor.client.feature.analytics.Screen 9 | import kotlinx.android.synthetic.main.fragment_denied_permission.* 10 | 11 | class PermissionDenied : BaseFragment() { 12 | override val layoutResource = R.layout.fragment_denied_permission 13 | override val screen: Screen = Screen.PERMISSION_DENIED 14 | 15 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 16 | super.onViewCreated(view, savedInstanceState) 17 | deniedRetryButtonCtrl.setOnClickListener { 18 | findNavController().popBackStack(R.id.connectWiFi, false) 19 | } 20 | deniedSureButtonCtrl.setOnClickListener { 21 | requireActivity().finish() 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /app/src/main/kotlin/co/netguru/baby/monitor/client/feature/onboarding/baby/PermissionMicrophoneFragment.kt: -------------------------------------------------------------------------------- 1 | package co.netguru.baby.monitor.client.feature.onboarding.baby 2 | 3 | import android.Manifest 4 | import androidx.navigation.fragment.findNavController 5 | import co.netguru.baby.monitor.client.R 6 | import co.netguru.baby.monitor.client.common.base.BaseFragment 7 | import co.netguru.baby.monitor.client.common.extensions.allPermissionsGranted 8 | import co.netguru.baby.monitor.client.feature.analytics.Screen 9 | 10 | class PermissionMicrophoneFragment : BaseFragment() { 11 | override val layoutResource = R.layout.fragment_microphone_permission 12 | override val screen: Screen = Screen.PERMISSION_MICROPHONE 13 | 14 | override fun onResume() { 15 | super.onResume() 16 | requestPermissions(permissions, PERMISSIONS_REQUEST_CODE) 17 | } 18 | 19 | override fun onRequestPermissionsResult(requestCode: Int, permissions: Array, grantResults: IntArray) { 20 | super.onRequestPermissionsResult(requestCode, permissions, grantResults) 21 | findNavController().navigate( 22 | if (requireContext().allPermissionsGranted(Companion.permissions)) { 23 | R.id.permissionMicrophoneToSetupInformation 24 | } else { 25 | R.id.permissionMicrophoneToPermissionDenied 26 | } 27 | ) 28 | } 29 | 30 | companion object { 31 | private const val PERMISSIONS_REQUEST_CODE = 126 32 | 33 | private val permissions = arrayOf( 34 | Manifest.permission.RECORD_AUDIO 35 | ) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /app/src/main/kotlin/co/netguru/baby/monitor/client/feature/onboarding/baby/SetupInformationFragment.kt: -------------------------------------------------------------------------------- 1 | package co.netguru.baby.monitor.client.feature.onboarding.baby 2 | 3 | import android.os.Bundle 4 | import android.view.View 5 | import androidx.navigation.fragment.findNavController 6 | import co.netguru.baby.monitor.client.R 7 | import co.netguru.baby.monitor.client.common.base.BaseFragment 8 | import co.netguru.baby.monitor.client.feature.analytics.Screen 9 | import kotlinx.android.synthetic.main.fragment_connecting_setup_information.* 10 | 11 | class SetupInformationFragment : BaseFragment() { 12 | override val layoutResource = R.layout.fragment_connecting_setup_information 13 | override val screen: Screen = Screen.SETUP_INFORMATION 14 | 15 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 16 | super.onViewCreated(view, savedInstanceState) 17 | connectionInformationMbtn.setOnClickListener { 18 | findNavController().navigate(R.id.setupInformationToServer) 19 | requireActivity().finish() 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /app/src/main/kotlin/co/netguru/baby/monitor/client/feature/onboarding/baby/WifiReceiver.kt: -------------------------------------------------------------------------------- 1 | package co.netguru.baby.monitor.client.feature.onboarding.baby 2 | 3 | import android.content.BroadcastReceiver 4 | import android.content.Context 5 | import android.content.Intent 6 | import android.content.IntentFilter 7 | import android.net.NetworkInfo 8 | import android.net.wifi.WifiManager 9 | import androidx.lifecycle.MutableLiveData 10 | import co.netguru.baby.monitor.client.data.communication.SingleEvent 11 | 12 | class WifiReceiver : BroadcastReceiver() { 13 | 14 | internal val isWifiConnected = MutableLiveData>() 15 | 16 | override fun onReceive(context: Context?, intent: Intent?) { 17 | val action = intent?.action ?: return 18 | if (action == WifiManager.NETWORK_STATE_CHANGED_ACTION) { 19 | val info: NetworkInfo = intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO) 20 | isWifiConnected.postValue(SingleEvent(event = info.isConnected)) 21 | } 22 | } 23 | 24 | companion object { 25 | val intentFilter = IntentFilter().apply { 26 | addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION) 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /app/src/main/kotlin/co/netguru/baby/monitor/client/feature/server/ChildMonitorFragmentModule.kt: -------------------------------------------------------------------------------- 1 | package co.netguru.baby.monitor.client.feature.server 2 | 3 | import co.netguru.baby.monitor.client.application.scope.FragmentScope 4 | import dagger.Module 5 | import dagger.Provides 6 | import javax.inject.Qualifier 7 | 8 | @Module 9 | class ChildMonitorFragmentModule { 10 | @Provides 11 | @LowBatteryHandler 12 | @FragmentScope 13 | fun provideLowBatteryHandler(fragment: ChildMonitorFragment): () -> Unit = 14 | fragment::handleLowBattery 15 | 16 | @Qualifier 17 | annotation class LowBatteryHandler 18 | } 19 | -------------------------------------------------------------------------------- /app/src/main/kotlin/co/netguru/baby/monitor/client/feature/server/ChildMonitorViewModel.kt: -------------------------------------------------------------------------------- 1 | package co.netguru.baby.monitor.client.feature.server 2 | 3 | import androidx.lifecycle.LiveData 4 | import androidx.lifecycle.MutableLiveData 5 | import androidx.lifecycle.ViewModel 6 | import co.netguru.baby.monitor.client.feature.analytics.AnalyticsManager 7 | import co.netguru.baby.monitor.client.feature.analytics.Event 8 | import co.netguru.baby.monitor.client.feature.batterylevel.NotifyLowBatteryUseCase 9 | import io.reactivex.disposables.CompositeDisposable 10 | import io.reactivex.rxkotlin.plusAssign 11 | import io.reactivex.rxkotlin.subscribeBy 12 | import timber.log.Timber 13 | import javax.inject.Inject 14 | 15 | class ChildMonitorViewModel @Inject constructor( 16 | private val notifyLowBatteryUseCase: NotifyLowBatteryUseCase, 17 | private val analyticsManager: AnalyticsManager 18 | ) : ViewModel() { 19 | 20 | private val disposables = CompositeDisposable() 21 | 22 | private val mutableNightModeStatus = MutableLiveData() 23 | internal val nightModeStatus: LiveData = mutableNightModeStatus 24 | 25 | fun notifyLowBattery(title: String, text: String) { 26 | disposables += notifyLowBatteryUseCase.notifyLowBattery(title = title, text = text) 27 | .subscribeBy( 28 | onComplete = { 29 | Timber.d("Low battery notification posted.") 30 | }, 31 | onError = { error -> 32 | Timber.w(error, "Error posting low battery notification.") 33 | } 34 | ) 35 | } 36 | 37 | fun switchNightMode() { 38 | val currentStatus = mutableNightModeStatus.value == true 39 | analyticsManager.logEvent(Event.ParamEvent.NightMode(!currentStatus)) 40 | mutableNightModeStatus.postValue(!currentStatus) 41 | } 42 | 43 | override fun onCleared() { 44 | disposables.clear() 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /app/src/main/kotlin/co/netguru/baby/monitor/client/feature/server/ReceiveFirebaseTokenUseCase.kt: -------------------------------------------------------------------------------- 1 | package co.netguru.baby.monitor.client.feature.server 2 | 3 | import co.netguru.baby.monitor.client.data.DataRepository 4 | import co.netguru.baby.monitor.client.data.communication.ClientEntity 5 | import io.reactivex.Completable 6 | import javax.inject.Inject 7 | 8 | class ReceiveFirebaseTokenUseCase @Inject constructor(private val dataRepository: DataRepository) { 9 | fun receiveToken(ipAddress: String, token: String): Completable = 10 | dataRepository.insertClientData(ClientEntity(address = ipAddress, firebaseKey = token)) 11 | } 12 | -------------------------------------------------------------------------------- /app/src/main/kotlin/co/netguru/baby/monitor/client/feature/settings/ChangeState.kt: -------------------------------------------------------------------------------- 1 | package co.netguru.baby.monitor.client.feature.settings 2 | 3 | sealed class ChangeState { 4 | object InProgress : ChangeState() 5 | object Completed : ChangeState() 6 | object Failed : ChangeState() 7 | } 8 | -------------------------------------------------------------------------------- /app/src/main/kotlin/co/netguru/baby/monitor/client/feature/settings/SeekBarState.kt: -------------------------------------------------------------------------------- 1 | package co.netguru.baby.monitor.client.feature.settings 2 | 3 | sealed class SeekBarState { 4 | data class StartTracking(val initialValue: Int) : SeekBarState() 5 | data class ProgressChange(val progress: Int) : SeekBarState() 6 | data class EndTracking(val endValue: Int) : SeekBarState() 7 | } 8 | -------------------------------------------------------------------------------- /app/src/main/kotlin/co/netguru/baby/monitor/client/feature/splash/SplashViewModel.kt: -------------------------------------------------------------------------------- 1 | package co.netguru.baby.monitor.client.feature.splash 2 | 3 | import androidx.lifecycle.MutableLiveData 4 | import androidx.lifecycle.ViewModel 5 | import co.netguru.baby.monitor.client.data.DataRepository 6 | import co.netguru.baby.monitor.client.data.splash.AppState 7 | import io.reactivex.Single 8 | import io.reactivex.disposables.CompositeDisposable 9 | import io.reactivex.rxkotlin.addTo 10 | import io.reactivex.rxkotlin.subscribeBy 11 | import io.reactivex.rxkotlin.zipWith 12 | import io.reactivex.schedulers.Schedulers 13 | import timber.log.Timber 14 | import java.util.concurrent.TimeUnit 15 | import javax.inject.Inject 16 | 17 | class SplashViewModel @Inject constructor( 18 | private val dataRepository: DataRepository 19 | ) : ViewModel() { 20 | internal val appState = MutableLiveData() 21 | 22 | private val compositeDisposable = CompositeDisposable() 23 | 24 | internal fun getSavedState() { 25 | Single.timer(DELAY_MILLISECONDS, TimeUnit.MILLISECONDS) 26 | .zipWith(dataRepository.getSavedState()) 27 | .subscribeOn(Schedulers.io()) 28 | .subscribeBy( 29 | onSuccess = { 30 | appState.postValue(it.second) 31 | }, 32 | onError = Timber::e 33 | ).addTo(compositeDisposable) 34 | } 35 | 36 | override fun onCleared() { 37 | compositeDisposable.dispose() 38 | super.onCleared() 39 | } 40 | 41 | companion object { 42 | private const val DELAY_MILLISECONDS = 1000L 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /app/src/main/kotlin/co/netguru/baby/monitor/client/feature/voiceAnalysis/AacRecorder.kt: -------------------------------------------------------------------------------- 1 | package co.netguru.baby.monitor.client.feature.voiceAnalysis 2 | 3 | import android.media.AudioFormat 4 | import android.media.AudioRecord 5 | import android.media.MediaRecorder 6 | import io.reactivex.Observable 7 | import timber.log.Timber 8 | import javax.inject.Inject 9 | 10 | class AacRecorder @Inject constructor() { 11 | 12 | fun startRecording(): Observable = Observable.create { emitter -> 13 | Timber.i("starting recording") 14 | var shouldStopRecording = false 15 | val bufferSize = findBestBufferSize() 16 | val audioRecord = AudioRecord( 17 | MediaRecorder.AudioSource.MIC, 18 | SAMPLING_RATE, 19 | AudioFormat.CHANNEL_IN_MONO, 20 | AudioFormat.ENCODING_PCM_16BIT, 21 | bufferSize 22 | ) 23 | Timber.i("recording started") 24 | val buffer = ByteArray(bufferSize) 25 | 26 | emitter.setCancellable { 27 | shouldStopRecording = true 28 | audioRecord.stop() 29 | audioRecord.release() 30 | Timber.i("release") 31 | } 32 | 33 | while (!shouldStopRecording) { 34 | if (audioRecord.recordingState == AudioRecord.RECORDSTATE_STOPPED) audioRecord.startRecording() 35 | audioRecord.read(buffer, 0, buffer.size) 36 | emitter.onNext(buffer) 37 | } 38 | } 39 | 40 | private fun findBestBufferSize(): Int { 41 | var bufferSize = AudioRecord.getMinBufferSize( 42 | SAMPLING_RATE, 43 | AudioFormat.CHANNEL_IN_MONO, 44 | AudioFormat.ENCODING_PCM_16BIT 45 | ) * 2 46 | if (bufferSize == AudioRecord.ERROR || bufferSize == AudioRecord.ERROR_BAD_VALUE) { 47 | bufferSize = SAMPLING_RATE * 2 48 | } 49 | return bufferSize 50 | } 51 | 52 | companion object { 53 | internal const val SAMPLING_RATE = 44_100 54 | internal const val CHANNELS = 1 55 | internal const val BITS_PER_SAMPLE = 16 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /app/src/main/kotlin/co/netguru/baby/monitor/client/feature/voiceAnalysis/RecordingData.kt: -------------------------------------------------------------------------------- 1 | package co.netguru.baby.monitor.client.feature.voiceAnalysis 2 | 3 | sealed class RecordingData { 4 | data class MachineLearning(val byteArray: ByteArray, val shortArray: ShortArray) : 5 | RecordingData() 6 | 7 | data class NoiseDetection(val shortArray: ShortArray) : RecordingData() 8 | data class Raw(val byteArray: ByteArray) : RecordingData() 9 | } 10 | -------------------------------------------------------------------------------- /app/src/main/kotlin/co/netguru/baby/monitor/client/feature/voiceAnalysis/VoiceAnalysisOption.kt: -------------------------------------------------------------------------------- 1 | package co.netguru.baby.monitor.client.feature.voiceAnalysis 2 | 3 | enum class VoiceAnalysisOption { 4 | MACHINE_LEARNING, 5 | NOISE_DETECTION 6 | } 7 | -------------------------------------------------------------------------------- /app/src/main/kotlin/co/netguru/baby/monitor/client/feature/voiceAnalysis/VoiceAnalysisService.kt: -------------------------------------------------------------------------------- 1 | package co.netguru.baby.monitor.client.feature.voiceAnalysis 2 | 3 | import android.content.Intent 4 | import android.os.Binder 5 | import android.widget.Toast 6 | import androidx.annotation.UiThread 7 | import co.netguru.baby.monitor.client.common.base.BaseServiceWithFacade 8 | import co.netguru.baby.monitor.client.common.base.ServiceFacade 9 | import io.reactivex.Single 10 | import timber.log.Timber 11 | 12 | class VoiceAnalysisService : ServiceFacade, 13 | BaseServiceWithFacade() { 14 | 15 | override fun onBind(intent: Intent?) = VoiceAnalysisBinder() 16 | 17 | @UiThread 18 | fun complain(message: String, error: Throwable) { 19 | Timber.w(error, message) 20 | Toast.makeText(this, message, Toast.LENGTH_SHORT).show() 21 | } 22 | 23 | fun saveDataToFile(rawData: ByteArray): Single { 24 | return WavFileGenerator.saveAudio( 25 | applicationContext, 26 | rawData, 27 | AacRecorder.BITS_PER_SAMPLE, 28 | AacRecorder.CHANNELS, 29 | AacRecorder.SAMPLING_RATE 30 | ) 31 | } 32 | 33 | inner class VoiceAnalysisBinder : Binder() { 34 | fun startRecording() { 35 | Timber.i("Start recording") 36 | serviceController.startRecording() 37 | } 38 | 39 | fun stopRecording() { 40 | Timber.i("Stop recording") 41 | serviceController.stopRecording() 42 | } 43 | 44 | fun setVoiceAnalysisOption(voiceAnalysisOption: VoiceAnalysisOption) { 45 | serviceController.voiceAnalysisOption = voiceAnalysisOption 46 | } 47 | 48 | fun setNoiseDetectionLevel(level: Int) { 49 | serviceController.noiseLevel = level 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /app/src/main/kotlin/co/netguru/baby/monitor/client/feature/voiceAnalysis/VoiceAnalysisUseCase.kt: -------------------------------------------------------------------------------- 1 | package co.netguru.baby.monitor.client.feature.voiceAnalysis 2 | 3 | import co.netguru.baby.monitor.client.data.DataRepository 4 | import co.netguru.baby.monitor.client.feature.communication.websocket.Message 5 | import co.netguru.baby.monitor.client.feature.communication.websocket.RxWebSocketClient 6 | import io.reactivex.Completable 7 | import timber.log.Timber 8 | import javax.inject.Inject 9 | 10 | class VoiceAnalysisUseCase @Inject constructor( 11 | private val dataRepository: DataRepository 12 | ) { 13 | fun sendInitialVoiceAnalysisOption(client: RxWebSocketClient): Completable = 14 | dataRepository.getChildData() 15 | .map { it.voiceAnalysisOption } 16 | .flatMapCompletable { voiceAnalysisOption -> 17 | sendVoiceAnalysisOption(client, voiceAnalysisOption) 18 | } 19 | 20 | private fun sendVoiceAnalysisOption( 21 | client: RxWebSocketClient, 22 | voiceAnalysisOption: VoiceAnalysisOption 23 | ): Completable = 24 | client.send(Message(voiceAnalysisOption = voiceAnalysisOption.name)) 25 | .doOnError { Timber.w("Couldn't send option: $voiceAnalysisOption.") } 26 | .doOnComplete { Timber.d("Option sent: $voiceAnalysisOption.") } 27 | } 28 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/heart.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netguru/baby-monitor-client-android/3c3d8838de7ed8a340ebb79a64f249681669abf2/app/src/main/res/drawable-hdpi/heart.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netguru/baby-monitor-client-android/3c3d8838de7ed8a340ebb79a64f249681669abf2/app/src/main/res/drawable-hdpi/logo.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/made_with_love.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netguru/baby-monitor-client-android/3c3d8838de7ed8a340ebb79a64f249681669abf2/app/src/main/res/drawable-hdpi/made_with_love.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/white_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netguru/baby-monitor-client-android/3c3d8838de7ed8a340ebb79a64f249681669abf2/app/src/main/res/drawable-hdpi/white_logo.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/wifi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netguru/baby-monitor-client-android/3c3d8838de7ed8a340ebb79a64f249681669abf2/app/src/main/res/drawable-hdpi/wifi.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/heart.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netguru/baby-monitor-client-android/3c3d8838de7ed8a340ebb79a64f249681669abf2/app/src/main/res/drawable-mdpi/heart.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netguru/baby-monitor-client-android/3c3d8838de7ed8a340ebb79a64f249681669abf2/app/src/main/res/drawable-mdpi/logo.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/made_with_love.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netguru/baby-monitor-client-android/3c3d8838de7ed8a340ebb79a64f249681669abf2/app/src/main/res/drawable-mdpi/made_with_love.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/white_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netguru/baby-monitor-client-android/3c3d8838de7ed8a340ebb79a64f249681669abf2/app/src/main/res/drawable-mdpi/white_logo.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/wifi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netguru/baby-monitor-client-android/3c3d8838de7ed8a340ebb79a64f249681669abf2/app/src/main/res/drawable-mdpi/wifi.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-v21/button_purple_outline_to_purple_rounded.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v21/button_rounded_ripple.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v21/button_white_outline_to_purple_rounded.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v21/dark_button_rounded_ripple.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/heart.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netguru/baby-monitor-client-android/3c3d8838de7ed8a340ebb79a64f249681669abf2/app/src/main/res/drawable-xhdpi/heart.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netguru/baby-monitor-client-android/3c3d8838de7ed8a340ebb79a64f249681669abf2/app/src/main/res/drawable-xhdpi/logo.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/made_with_love.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netguru/baby-monitor-client-android/3c3d8838de7ed8a340ebb79a64f249681669abf2/app/src/main/res/drawable-xhdpi/made_with_love.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/white_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netguru/baby-monitor-client-android/3c3d8838de7ed8a340ebb79a64f249681669abf2/app/src/main/res/drawable-xhdpi/white_logo.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/wifi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netguru/baby-monitor-client-android/3c3d8838de7ed8a340ebb79a64f249681669abf2/app/src/main/res/drawable-xhdpi/wifi.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/heart.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netguru/baby-monitor-client-android/3c3d8838de7ed8a340ebb79a64f249681669abf2/app/src/main/res/drawable-xxhdpi/heart.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netguru/baby-monitor-client-android/3c3d8838de7ed8a340ebb79a64f249681669abf2/app/src/main/res/drawable-xxhdpi/logo.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/made_with_love.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netguru/baby-monitor-client-android/3c3d8838de7ed8a340ebb79a64f249681669abf2/app/src/main/res/drawable-xxhdpi/made_with_love.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/white_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netguru/baby-monitor-client-android/3c3d8838de7ed8a340ebb79a64f249681669abf2/app/src/main/res/drawable-xxhdpi/white_logo.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/wifi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netguru/baby-monitor-client-android/3c3d8838de7ed8a340ebb79a64f249681669abf2/app/src/main/res/drawable-xxhdpi/wifi.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/heart.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netguru/baby-monitor-client-android/3c3d8838de7ed8a340ebb79a64f249681669abf2/app/src/main/res/drawable-xxxhdpi/heart.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netguru/baby-monitor-client-android/3c3d8838de7ed8a340ebb79a64f249681669abf2/app/src/main/res/drawable-xxxhdpi/logo.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/made_with_love.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netguru/baby-monitor-client-android/3c3d8838de7ed8a340ebb79a64f249681669abf2/app/src/main/res/drawable-xxxhdpi/made_with_love.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/white_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netguru/baby-monitor-client-android/3c3d8838de7ed8a340ebb79a64f249681669abf2/app/src/main/res/drawable-xxxhdpi/white_logo.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/wifi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netguru/baby-monitor-client-android/3c3d8838de7ed8a340ebb79a64f249681669abf2/app/src/main/res/drawable-xxxhdpi/wifi.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/all_circle_white_bg.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/animated_done.xml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 13 | 20 | 21 | 22 | 23 | 24 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/baby_logo.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/button_purple_outline_to_purple_rounded.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/button_rounded_ripple.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/button_white_outline_to_purple_rounded.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/circle_primary_bg.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/circle_primary_dark_bg.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/dark_button_rounded_ripple.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/download.xml: -------------------------------------------------------------------------------- 1 | 6 | 12 | 18 | 19 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/failed.xml: -------------------------------------------------------------------------------- 1 | 6 | 12 | 19 | 27 | 35 | 36 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/feature_a_indicator.xml: -------------------------------------------------------------------------------- 1 | 6 | 12 | 20 | 28 | 29 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/feature_b_indicator.xml: -------------------------------------------------------------------------------- 1 | 6 | 14 | 20 | 28 | 29 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/feature_c_indicator.xml: -------------------------------------------------------------------------------- 1 | 6 | 14 | 22 | 28 | 29 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_activity.xml: -------------------------------------------------------------------------------- 1 | 6 | 13 | 21 | 28 | 29 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_arrow_back.xml: -------------------------------------------------------------------------------- 1 | 6 | 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_arrow_down.xml: -------------------------------------------------------------------------------- 1 | 6 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_baby.xml: -------------------------------------------------------------------------------- 1 | 6 | 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_camera.xml: -------------------------------------------------------------------------------- 1 | 6 | 12 | 18 | 19 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_close.xml: -------------------------------------------------------------------------------- 1 | 6 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_edit.xml: -------------------------------------------------------------------------------- 1 | 6 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_gear_wheel.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_info.xml: -------------------------------------------------------------------------------- 1 | 6 | 12 | 18 | 19 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_microphone.xml: -------------------------------------------------------------------------------- 1 | 8 | 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_night_mode.xml: -------------------------------------------------------------------------------- 1 | 6 | 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_night_mode_active.xml: -------------------------------------------------------------------------------- 1 | 6 | 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_select_photo_camera.xml: -------------------------------------------------------------------------------- 1 | 6 | 14 | 22 | 30 | 31 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_select_photo_placeholder.xml: -------------------------------------------------------------------------------- 1 | 6 | 10 | 14 | 15 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_sound_max.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_sound_min.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_specify_device_checkmark.xml: -------------------------------------------------------------------------------- 1 | 6 | 14 | 22 | 28 | 29 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/monitoring.xml: -------------------------------------------------------------------------------- 1 | 6 | 12 | 18 | 19 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/phone_item_icon.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/recycler_divider.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/safety.xml: -------------------------------------------------------------------------------- 1 | 6 | 12 | 18 | 24 | 25 | -------------------------------------------------------------------------------- /app/src/main/res/font/rubik.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 8 | 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/font/rubik_bold.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netguru/baby-monitor-client-android/3c3d8838de7ed8a340ebb79a64f249681669abf2/app/src/main/res/font/rubik_bold.otf -------------------------------------------------------------------------------- /app/src/main/res/font/rubik_light.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netguru/baby-monitor-client-android/3c3d8838de7ed8a340ebb79a64f249681669abf2/app/src/main/res/font/rubik_light.otf -------------------------------------------------------------------------------- /app/src/main/res/font/rubik_medium.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netguru/baby-monitor-client-android/3c3d8838de7ed8a340ebb79a64f249681669abf2/app/src/main/res/font/rubik_medium.otf -------------------------------------------------------------------------------- /app/src/main/res/font/rubik_regular.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netguru/baby-monitor-client-android/3c3d8838de7ed8a340ebb79a64f249681669abf2/app/src/main/res/font/rubik_regular.otf -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_onboarding.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_server.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 16 | 17 | 25 | 26 | -------------------------------------------------------------------------------- /app/src/main/res/layout/debug_view.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 16 | 17 | 23 | 24 | 30 | 31 | -------------------------------------------------------------------------------- /app/src/main/res/layout/found_service_item.xml: -------------------------------------------------------------------------------- 1 | 2 | 12 | 13 | 20 | 21 | 29 | 30 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_client_activity_log.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 20 | 21 | 34 | 35 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_client_live_camera.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 16 | 17 | 26 | 27 | 40 | 41 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_log_activity_end_text.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 18 | 19 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_log_activity_header.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 20 | 21 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /app/src/main/res/layout/seek_bar_progress.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 16 | 17 | 22 | 23 | 37 | 38 | -------------------------------------------------------------------------------- /app/src/main/res/layout/toolbar_default.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 19 | 20 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netguru/baby-monitor-client-android/3c3d8838de7ed8a340ebb79a64f249681669abf2/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netguru/baby-monitor-client-android/3c3d8838de7ed8a340ebb79a64f249681669abf2/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netguru/baby-monitor-client-android/3c3d8838de7ed8a340ebb79a64f249681669abf2/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netguru/baby-monitor-client-android/3c3d8838de7ed8a340ebb79a64f249681669abf2/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netguru/baby-monitor-client-android/3c3d8838de7ed8a340ebb79a64f249681669abf2/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netguru/baby-monitor-client-android/3c3d8838de7ed8a340ebb79a64f249681669abf2/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netguru/baby-monitor-client-android/3c3d8838de7ed8a340ebb79a64f249681669abf2/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netguru/baby-monitor-client-android/3c3d8838de7ed8a340ebb79a64f249681669abf2/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netguru/baby-monitor-client-android/3c3d8838de7ed8a340ebb79a64f249681669abf2/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netguru/baby-monitor-client-android/3c3d8838de7ed8a340ebb79a64f249681669abf2/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/navigation/client_home_nav_graph.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 13 | 14 | 17 | 18 | 21 | 22 | 23 | 28 | 29 | 34 | 35 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /app/src/main/res/navigation/server_nav_graph.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /app/src/main/res/values-v21/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 14 | 15 | -------------------------------------------------------------------------------- /app/src/main/res/values/animation.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 500 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/values/attrs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #ffffff 4 | #211e35 5 | #9c8fff 6 | 7 | #4d211e35 8 | #80211e35 9 | 10 | #00000000 11 | #FFFFFF 12 | #4DFFFFFF 13 | #000000 14 | #D1000000 15 | 16 | #FAFAFA 17 | 18 | #7c75f6 19 | #999c8fff 20 | #BDBDBD 21 | 22 | #33FFFFFF 23 | 24 | #211f36 25 | 26 | #33FF99 27 | #9b9b9b 28 | #363253 29 | #F44336 30 | 31 | -------------------------------------------------------------------------------- /app/src/main/res/values/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #9C8FFF 4 | -------------------------------------------------------------------------------- /app/src/main/res/values/resourfes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10sp 5 | 11sp 6 | 12sp 7 | 13sp 8 | 14sp 9 | 15sp 10 | 16sp 11 | 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | 12 | 13 | 17 | 18 | 26 | 27 | 33 | 34 | -------------------------------------------------------------------------------- /app/src/release/kotlin/co/netguru/baby/monitor/client/application/DebugMetricsHelper.kt: -------------------------------------------------------------------------------- 1 | package co.netguru.baby.monitor.client.application 2 | 3 | import javax.inject.Inject 4 | import javax.inject.Singleton 5 | 6 | /** 7 | * Helper class that initializes a set of debugging tools 8 | * for the debug build type and register crash manager for release type. 9 | * ## Debug type tools: 10 | * - AndroidDevMetrics 11 | * - Stetho 12 | * - StrictMode 13 | * - LeakCanary 14 | * - Timber 15 | * 16 | * ## Release type tools: 17 | * - CrashManager 18 | */ 19 | @Singleton 20 | class DebugMetricsHelper @Inject constructor() { 21 | 22 | internal fun init() { 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /app/src/release/kotlin/co/netguru/baby/monitor/client/application/RxJavaErrorHandlerImpl.kt: -------------------------------------------------------------------------------- 1 | package co.netguru.baby.monitor.client.application 2 | 3 | import io.reactivex.exceptions.UndeliverableException 4 | import timber.log.Timber 5 | 6 | class RxJavaErrorHandlerImpl : RxJavaErrorHandler() { 7 | 8 | override fun handleUndeliverableException(undeliverableException: UndeliverableException) { 9 | //TODO - decide whether this should be logged and passed or not to used crash reporter 10 | //often occurring might be indication of some problem in library or our codebase but definitely shouldn't crash the app 11 | //Crashlytics.logException(undeliverableException) 12 | Timber.e(undeliverableException) 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /app/src/release/kotlin/co/netguru/baby/monitor/client/feature/debug/DebugModule.kt: -------------------------------------------------------------------------------- 1 | package co.netguru.baby.monitor.client.feature.debug 2 | 3 | import io.reactivex.Observable 4 | import javax.inject.Inject 5 | 6 | @Suppress("unused") 7 | class DebugModule @Inject constructor() { 8 | fun sendCryingProbabilityEvent(cryingProbability: Float) = Unit 9 | fun sendNotificationEvent(notificationInformation: String) = Unit 10 | fun sendSoundEvent(decibels: Int) = Unit 11 | fun debugStateObservable(): Observable = Observable.empty() 12 | } 13 | -------------------------------------------------------------------------------- /app/src/release/kotlin/co/netguru/baby/monitor/client/feature/debug/DebugNotificationManager.kt: -------------------------------------------------------------------------------- 1 | package co.netguru.baby.monitor.client.feature.debug 2 | 3 | import android.content.Context 4 | import javax.inject.Inject 5 | 6 | class DebugNotificationManager @Inject constructor() { 7 | fun show(context: Context) {} 8 | fun clear(context: Context) {} 9 | } 10 | -------------------------------------------------------------------------------- /app/src/releasePreprod/kotlin/co/netguru/baby/monitor/client/application/DebugMetricsHelper.kt: -------------------------------------------------------------------------------- 1 | package co.netguru.baby.monitor.client.application 2 | 3 | import timber.log.Timber 4 | import javax.inject.Inject 5 | import javax.inject.Singleton 6 | 7 | /** 8 | * Helper class that initializes a set of debugging tools 9 | * for the debug build type and register crash manager for release type. 10 | * ## Debug type tools: 11 | * - AndroidDevMetrics 12 | * - Stetho 13 | * - StrictMode 14 | * - LeakCanary 15 | * - Timber 16 | * 17 | * ## Release type tools: 18 | * - CrashManager 19 | */ 20 | @Singleton 21 | class DebugMetricsHelper @Inject constructor() { 22 | 23 | internal fun init() { 24 | Timber.plant(Timber.DebugTree()) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/src/releasePreprod/kotlin/co/netguru/baby/monitor/client/application/RxJavaErrorHandlerImpl.kt: -------------------------------------------------------------------------------- 1 | package co.netguru.baby.monitor.client.application 2 | 3 | import io.reactivex.exceptions.UndeliverableException 4 | import timber.log.Timber 5 | 6 | class RxJavaErrorHandlerImpl : RxJavaErrorHandler() { 7 | 8 | override fun handleUndeliverableException(undeliverableException: UndeliverableException) { 9 | //TODO - decide whether this should be logged and passed or not to used crash reporter 10 | //often occurring might be indication of some problem in library or our codebase but definitely shouldn't crash the app 11 | //Crashlytics.logException(undeliverableException) 12 | Timber.e(undeliverableException) 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /app/src/test/kotlin/co/netguru/baby/monitor/AnswerWithPair.kt: -------------------------------------------------------------------------------- 1 | package co.netguru.baby.monitor 2 | 3 | import org.mockito.internal.stubbing.defaultanswers.ReturnsEmptyValues 4 | import org.mockito.invocation.InvocationOnMock 5 | import org.mockito.stubbing.Answer 6 | 7 | class AnswerWithPair( 8 | private val clazz: Class<*>, 9 | private val pair: Pair, T> 10 | ) : Answer { 11 | private val delegate = ReturnsEmptyValues() 12 | 13 | @Throws(Throwable::class) 14 | override fun answer(invocation: InvocationOnMock): Any = invocation.apply { 15 | return when (method.returnType) { 16 | clazz -> mock 17 | pair.first -> pair.second 18 | else -> delegate.answer(this) 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /app/src/test/kotlin/co/netguru/baby/monitor/RxSchedulersOverrideRule.kt: -------------------------------------------------------------------------------- 1 | package co.netguru.baby.monitor 2 | 3 | import io.reactivex.android.plugins.RxAndroidPlugins 4 | import io.reactivex.plugins.RxJavaPlugins 5 | import io.reactivex.schedulers.Schedulers 6 | import org.junit.rules.TestRule 7 | import org.junit.runner.Description 8 | import org.junit.runners.model.Statement 9 | 10 | class RxSchedulersOverrideRule : TestRule { 11 | 12 | override fun apply(base: Statement, description: Description): Statement { 13 | return object : Statement() { 14 | @Throws(Throwable::class) 15 | override fun evaluate() { 16 | RxAndroidPlugins.setInitMainThreadSchedulerHandler { Schedulers.trampoline() } 17 | RxJavaPlugins.setComputationSchedulerHandler { Schedulers.trampoline() } 18 | RxJavaPlugins.setIoSchedulerHandler { Schedulers.trampoline() } 19 | RxJavaPlugins.setNewThreadSchedulerHandler { Schedulers.trampoline() } 20 | RxJavaPlugins.setSingleSchedulerHandler { Schedulers.trampoline() } 21 | base.evaluate() 22 | 23 | RxJavaPlugins.reset() 24 | RxAndroidPlugins.reset() 25 | } 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /app/src/test/kotlin/co/netguru/baby/monitor/RxTestSchedulerOverrideRule.kt: -------------------------------------------------------------------------------- 1 | package co.netguru.baby.monitor 2 | 3 | import io.reactivex.android.plugins.RxAndroidPlugins 4 | import io.reactivex.plugins.RxJavaPlugins 5 | import io.reactivex.schedulers.Schedulers 6 | import io.reactivex.schedulers.TestScheduler 7 | import org.junit.rules.TestRule 8 | import org.junit.runner.Description 9 | import org.junit.runners.model.Statement 10 | 11 | /** 12 | * Rule helpful while testing blocking/waiting rxjava code. 13 | * All handlers are set to main thread, Computation scheduler is replaced with [TestScheduler] 14 | * accessible through [testScheduler] property. 15 | */ 16 | class RxTestSchedulerOverrideRule : TestRule { 17 | val testScheduler = TestScheduler() 18 | 19 | override fun apply(base: Statement, description: Description): Statement { 20 | return object : Statement() { 21 | @Throws(Throwable::class) 22 | override fun evaluate() { 23 | RxAndroidPlugins.setInitMainThreadSchedulerHandler { Schedulers.trampoline() } 24 | RxJavaPlugins.setComputationSchedulerHandler { testScheduler } 25 | RxJavaPlugins.setIoSchedulerHandler { Schedulers.trampoline() } 26 | RxJavaPlugins.setNewThreadSchedulerHandler { Schedulers.trampoline() } 27 | RxJavaPlugins.setSingleSchedulerHandler { Schedulers.trampoline() } 28 | base.evaluate() 29 | 30 | RxJavaPlugins.reset() 31 | RxAndroidPlugins.reset() 32 | } 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /app/src/test/kotlin/co/netguru/baby/monitor/TestUtils.kt: -------------------------------------------------------------------------------- 1 | package co.netguru.baby.monitor 2 | 3 | import com.nhaarman.mockitokotlin2.inOrder 4 | import com.nhaarman.mockitokotlin2.mock 5 | 6 | object TestUtils { 7 | inline fun mockBuilder(mockResult: R) = 8 | mock(defaultAnswer = AnswerWithPair( 9 | T::class.java, 10 | R::class.java to mockResult 11 | ) 12 | ) 13 | 14 | fun verifyInOrder(mock: T, body: T.() -> Unit) = 15 | with(inOrder(mock)) { verify(mock).body() } 16 | } 17 | -------------------------------------------------------------------------------- /app/src/test/kotlin/co/netguru/baby/monitor/client/feature/babycrynotification/SnoozeNotificationUseCaseTest.kt: -------------------------------------------------------------------------------- 1 | package co.netguru.baby.monitor.client.feature.babycrynotification 2 | 3 | import co.netguru.baby.monitor.RxSchedulersOverrideRule 4 | import co.netguru.baby.monitor.client.data.DataRepository 5 | import co.netguru.baby.monitor.client.feature.analytics.AnalyticsManager 6 | import co.netguru.baby.monitor.client.feature.analytics.Event 7 | import co.netguru.baby.monitor.client.feature.analytics.EventType 8 | import co.netguru.baby.monitor.client.feature.babynotification.SnoozeNotificationUseCase 9 | import com.nhaarman.mockitokotlin2.* 10 | import io.reactivex.Completable 11 | import org.junit.Rule 12 | import org.junit.Test 13 | 14 | class SnoozeNotificationUseCaseTest { 15 | 16 | @get:Rule 17 | val schedulersRule = RxSchedulersOverrideRule() 18 | 19 | private val dataRepository: DataRepository = mock { 20 | on { updateChildSnoozeTimestamp(any()) }.doReturn(Completable.complete()) 21 | } 22 | private val analyticsManager: AnalyticsManager = mock() 23 | private val snoozeNotificationUseCase = 24 | SnoozeNotificationUseCase( 25 | dataRepository, 26 | analyticsManager 27 | ) 28 | 29 | @Test 30 | fun `should update snoozeTimestamp on snoozeNotifications`() { 31 | snoozeNotificationUseCase.snoozeNotifications() 32 | 33 | verify(dataRepository).updateChildSnoozeTimestamp(any()) 34 | } 35 | 36 | @Test 37 | fun `should send snoozeNotification event to firebase`() { 38 | snoozeNotificationUseCase.snoozeNotifications() 39 | 40 | verify(analyticsManager).logEvent(check { 41 | it is Event.Simple && it.eventType == EventType.NOTIFICATION_SNOOZE 42 | }) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /app/src/test/kotlin/co/netguru/baby/monitor/client/feature/batterylevel/NotifyLowBatteryUseCaseTest.kt: -------------------------------------------------------------------------------- 1 | package co.netguru.baby.monitor.client.feature.batterylevel 2 | 3 | import co.netguru.baby.monitor.RxSchedulersOverrideRule 4 | import co.netguru.baby.monitor.client.feature.firebasenotification.FirebaseNotificationSender 5 | import co.netguru.baby.monitor.client.feature.firebasenotification.NotificationType 6 | import com.nhaarman.mockitokotlin2.mock 7 | import com.nhaarman.mockitokotlin2.verify 8 | import org.junit.Rule 9 | import org.junit.Test 10 | 11 | class NotifyLowBatteryUseCaseTest { 12 | 13 | @get:Rule 14 | val schedulersRule = RxSchedulersOverrideRule() 15 | 16 | private val notificationSender: FirebaseNotificationSender = mock() 17 | private val notifyLowBatteryUseCase = NotifyLowBatteryUseCase(notificationSender) 18 | 19 | @Test 20 | fun `should send low battery notification on notifyLowBattery`() { 21 | val title = "title" 22 | val text = "text" 23 | notifyLowBatteryUseCase.notifyLowBattery(title, text) 24 | 25 | verify(notificationSender).broadcastNotificationToFcm( 26 | title, 27 | text, 28 | NotificationType.LOW_BATTERY_NOTIFICATION 29 | ) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /app/src/test/kotlin/co/netguru/baby/monitor/client/feature/client/home/RestartAppUseCaseTest.kt: -------------------------------------------------------------------------------- 1 | package co.netguru.baby.monitor.client.feature.client.home 2 | 3 | import android.content.Context 4 | import android.content.Intent 5 | import android.content.pm.PackageManager 6 | import androidx.appcompat.app.AppCompatActivity 7 | import co.netguru.baby.monitor.RxSchedulersOverrideRule 8 | import com.nhaarman.mockitokotlin2.doReturn 9 | import com.nhaarman.mockitokotlin2.mock 10 | import com.nhaarman.mockitokotlin2.verify 11 | import org.junit.Rule 12 | import org.junit.Test 13 | 14 | class RestartAppUseCaseTest { 15 | 16 | @get:Rule 17 | val schedulersRule = RxSchedulersOverrideRule() 18 | 19 | private val restartAppUseCase: RestartAppUseCase = RestartAppUseCase() 20 | private val intent: Intent = mock() 21 | private val packageName = "packageName" 22 | private val activity: AppCompatActivity = mock { 23 | val context: Context = mock { 24 | val packageManagerMock: PackageManager = mock { 25 | on { getLaunchIntentForPackage(packageName) }.doReturn(intent) 26 | } 27 | on { packageManager }.doReturn(packageManagerMock) 28 | on { packageName }.doReturn(packageName) 29 | } 30 | on { baseContext }.doReturn(context) 31 | } 32 | 33 | @Test 34 | fun `should restart App`() { 35 | restartAppUseCase 36 | .restartApp(activity) 37 | .subscribe() 38 | 39 | verify(activity).startActivity(intent) 40 | verify(activity).finish() 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /app/src/test/kotlin/co/netguru/baby/monitor/client/feature/client/home/SendBabyNameUseCaseTest.kt: -------------------------------------------------------------------------------- 1 | package co.netguru.baby.monitor.client.feature.client.home 2 | 3 | import co.netguru.baby.monitor.RxSchedulersOverrideRule 4 | import co.netguru.baby.monitor.client.data.DataRepository 5 | import co.netguru.baby.monitor.client.data.client.ChildDataEntity 6 | import co.netguru.baby.monitor.client.feature.communication.websocket.RxWebSocketClient 7 | import com.nhaarman.mockitokotlin2.* 8 | import io.reactivex.Completable 9 | import io.reactivex.Maybe 10 | import org.junit.Assert.assertEquals 11 | import org.junit.Rule 12 | import org.junit.Test 13 | 14 | class SendBabyNameUseCaseTest { 15 | 16 | @get:Rule 17 | val schedulersRule = RxSchedulersOverrideRule() 18 | 19 | private val childDataEntity = ChildDataEntity("", name = "name") 20 | private val dataRepository: DataRepository = mock { 21 | on { getChildData() }.doReturn(Maybe.just(childDataEntity)) 22 | } 23 | private val rxWebSocketClient: RxWebSocketClient = mock { 24 | on { send(any()) }.doReturn(Completable.complete()) 25 | } 26 | private val sendBabyNameUseCase = SendBabyNameUseCase(dataRepository) 27 | 28 | @Test 29 | fun `should send baby name message`() { 30 | sendBabyNameUseCase 31 | .streamBabyName(rxWebSocketClient) 32 | .test() 33 | .assertComplete() 34 | 35 | verify(dataRepository).getChildData() 36 | verify(rxWebSocketClient).send(check { 37 | assertEquals(childDataEntity.name, it.babyName) 38 | }) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /app/src/test/kotlin/co/netguru/baby/monitor/client/feature/client/home/SendFirebaseTokenUseCaseTest.kt: -------------------------------------------------------------------------------- 1 | package co.netguru.baby.monitor.client.feature.client.home 2 | 3 | import co.netguru.baby.monitor.client.feature.communication.websocket.Message 4 | import co.netguru.baby.monitor.client.feature.communication.websocket.RxWebSocketClient 5 | import co.netguru.baby.monitor.client.feature.firebasenotification.FirebaseInstanceManager 6 | import com.google.gson.Gson 7 | import com.nhaarman.mockitokotlin2.* 8 | import io.reactivex.Completable 9 | import io.reactivex.Single 10 | import org.junit.Assert 11 | import org.junit.Test 12 | 13 | class SendFirebaseTokenUseCaseTest { 14 | 15 | private val token = "token" 16 | private val firebaseInstanceManager: FirebaseInstanceManager = mock { 17 | on { getFirebaseToken() }.doReturn(Single.just(token)) 18 | } 19 | private val rxWebSocketClient: RxWebSocketClient = mock { 20 | on { send(any()) }.doReturn(Completable.complete()) 21 | } 22 | private val gson: Gson = mock { 23 | on { toJson(any()) }.doReturn(token) 24 | } 25 | private val sendFirebaseTokenUseCase = SendFirebaseTokenUseCase(firebaseInstanceManager) 26 | 27 | @Test 28 | fun sendFirebaseToken() { 29 | sendFirebaseTokenUseCase.sendFirebaseToken(rxWebSocketClient) 30 | .test() 31 | .assertComplete() 32 | 33 | verify(rxWebSocketClient).send(check { 34 | Assert.assertTrue(it.pushNotificationsToken?.contains(token) == true) 35 | }) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /app/src/test/kotlin/co/netguru/baby/monitor/client/feature/communication/pairing/ServiceDiscoveryViewModelTest.kt: -------------------------------------------------------------------------------- 1 | package co.netguru.baby.monitor.client.feature.communication.pairing 2 | 3 | import android.net.wifi.WifiManager 4 | import androidx.arch.core.executor.testing.InstantTaskExecutorRule 5 | import co.netguru.baby.monitor.RxSchedulersOverrideRule 6 | import co.netguru.baby.monitor.client.feature.communication.nsd.NsdServiceManager 7 | import com.nhaarman.mockitokotlin2.doReturn 8 | import com.nhaarman.mockitokotlin2.mock 9 | import com.nhaarman.mockitokotlin2.verify 10 | import org.junit.Rule 11 | import org.junit.Test 12 | 13 | class ServiceDiscoveryViewModelTest { 14 | 15 | @get:Rule 16 | val schedulersRule = RxSchedulersOverrideRule() 17 | 18 | @get:Rule 19 | val instantExecutorRule = InstantTaskExecutorRule() 20 | 21 | private val nsdServiceManager: NsdServiceManager = mock() 22 | 23 | private val multicastLock: WifiManager.MulticastLock = mock() 24 | private val wifiManager: WifiManager = mock { 25 | on { createMulticastLock(ServiceDiscoveryViewModel.MUTLICAST_LOCK_TAG) }.doReturn( 26 | multicastLock 27 | ) 28 | } 29 | 30 | private val serviceDiscoveryViewModel = ServiceDiscoveryViewModel( 31 | nsdServiceManager 32 | ) 33 | 34 | @Test 35 | fun `should start and stop nsdService`() { 36 | serviceDiscoveryViewModel.discoverNsdService(wifiManager) 37 | 38 | verify(nsdServiceManager).discoverService() 39 | 40 | serviceDiscoveryViewModel.stopNsdServiceDiscovery() 41 | 42 | verify(nsdServiceManager).stopServiceDiscovery() 43 | } 44 | 45 | @Test 46 | fun `should handle Wifi MulticastLock while starting and stopping searching`() { 47 | serviceDiscoveryViewModel.discoverNsdService(wifiManager) 48 | 49 | verify(multicastLock).acquire() 50 | verify(multicastLock).setReferenceCounted(true) 51 | 52 | serviceDiscoveryViewModel.stopNsdServiceDiscovery() 53 | 54 | verify(multicastLock).release() 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /app/src/test/kotlin/co/netguru/baby/monitor/client/feature/onboarding/FinishOnboardingUseCaseTest.kt: -------------------------------------------------------------------------------- 1 | package co.netguru.baby.monitor.client.feature.onboarding 2 | 3 | import co.netguru.baby.monitor.client.data.DataRepository 4 | import co.netguru.baby.monitor.client.data.splash.AppState 5 | import com.nhaarman.mockitokotlin2.mock 6 | import com.nhaarman.mockitokotlin2.verify 7 | import org.junit.Test 8 | 9 | class FinishOnboardingUseCaseTest { 10 | 11 | private val dataRepository = mock() 12 | private val finishOnboardingUseCase = FinishOnboardingUseCase(dataRepository) 13 | 14 | @Test 15 | fun `should save undefined state`() { 16 | finishOnboardingUseCase.finishOnboarding() 17 | 18 | verify(dataRepository).saveConfiguration(AppState.UNDEFINED) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /app/src/test/kotlin/co/netguru/baby/monitor/client/feature/server/ReceiveFirebaseTokenUseCaseTest.kt: -------------------------------------------------------------------------------- 1 | package co.netguru.baby.monitor.client.feature.server 2 | 3 | import co.netguru.baby.monitor.client.data.DataRepository 4 | import com.nhaarman.mockitokotlin2.check 5 | import com.nhaarman.mockitokotlin2.mock 6 | import com.nhaarman.mockitokotlin2.verify 7 | import org.junit.Assert.assertEquals 8 | import org.junit.Test 9 | 10 | class ReceiveFirebaseTokenUseCaseTest { 11 | 12 | private val dataRepository: DataRepository = mock() 13 | private val receiveFirebaseTokenUseCase = ReceiveFirebaseTokenUseCase(dataRepository) 14 | 15 | @Test 16 | fun `should save received token in dataRepository`() { 17 | val address = "address" 18 | val token = "token" 19 | receiveFirebaseTokenUseCase.receiveToken(address, token) 20 | 21 | verify(dataRepository).insertClientData(check { 22 | assertEquals(address, it.address) 23 | assertEquals(token, it.firebaseKey) 24 | }) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker: -------------------------------------------------------------------------------- 1 | mock-maker-inline -------------------------------------------------------------------------------- /buildsystem/secrets.gradle: -------------------------------------------------------------------------------- 1 | static def varToCamelCase(String envVarName) { 2 | envVarName.split('_').collect { it -> it.toLowerCase().capitalize() }.join() 3 | } 4 | 5 | ext { 6 | if (isBitrise) { 7 | keyProperty = { var -> 8 | prop = System.getenv(var) 9 | if (prop == null || prop.isEmpty()) { 10 | if (bitrise.cli.isPr) { 11 | prop = "undefined" 12 | } 13 | else { 14 | throw new MissingPropertyException("Missing environment variable $var") 15 | } 16 | } 17 | prop 18 | } 19 | } else { 20 | keyConfigPath = "${projectDir.path}/secret.properties" 21 | File keyConfigFile = file(keyConfigPath) 22 | keyProps = new Properties(); 23 | 24 | if (keyConfigFile.exists()) { 25 | keyProps.load(keyConfigFile.newInputStream()) 26 | } else { 27 | throw new FileNotFoundException("File $keyConfigPath not found") 28 | } 29 | 30 | keyProperty = { var -> 31 | propNameCase = varToCamelCase(var) 32 | prop = keyProps[propNameCase] 33 | if (prop == null || prop.toString().isEmpty()) 34 | throw new MissingPropertyException("Missing property $propNameCase") 35 | prop 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | android.enableJetifier=true 10 | android.useAndroidX=true 11 | org.gradle.jvmargs=-Xmx1536m 12 | android.enableR8=true 13 | # When configured, Gradle will run in incubating parallel mode. 14 | # This option should only be used with decoupled projects. More details, visit 15 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 16 | # org.gradle.parallel=true 17 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netguru/baby-monitor-client-android/3c3d8838de7ed8a340ebb79a64f249681669abf2/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Fri Mar 29 11:22:03 CET 2019 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-5.4.1-all.zip 7 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | --------------------------------------------------------------------------------