├── .github └── PULL_REQUEST_TEMPLATE.md ├── .gitignore ├── .idea └── codeStyles │ ├── Project.xml │ └── codeStyleConfig.xml ├── LICENSE ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules-test.pro ├── proguard-rules.pro └── src │ ├── debug │ └── kotlin │ │ └── co │ │ └── netguru │ │ └── android │ │ └── carrecognition │ │ └── application │ │ ├── DebugMetricsHelper.kt │ │ └── RxJavaErrorHandlerImpl.kt │ ├── main │ ├── AndroidManifest.xml │ ├── assets │ │ ├── CarClassifierModel_190125_1802.pb │ │ ├── CarDetectorModel_181130_1111.pb │ │ ├── button_ripple.json │ │ ├── cars.json │ │ ├── cars_labels.txt │ │ ├── lottie_viewfinder_animation.json │ │ ├── progress-bar-linear.json │ │ ├── progress-bar.json │ │ └── rating_bar.json │ ├── ic_launcher-web.png │ ├── kotlin │ │ └── co │ │ │ └── netguru │ │ │ └── android │ │ │ └── carrecognition │ │ │ ├── application │ │ │ ├── ActivityBindingModule.kt │ │ │ ├── App.kt │ │ │ ├── ApplicationComponent.kt │ │ │ ├── ApplicationModule.kt │ │ │ ├── FragmentBindingModule.kt │ │ │ ├── RxJavaErrorHandler.kt │ │ │ ├── TFModule.kt │ │ │ └── scope │ │ │ │ ├── ActivityScope.kt │ │ │ │ ├── AppScope.kt │ │ │ │ └── FragmentScope.kt │ │ │ ├── common │ │ │ ├── AnimationUtils.kt │ │ │ ├── LimitedList.kt │ │ │ ├── MetricsUtils.kt │ │ │ ├── MultimapExt.kt │ │ │ ├── Optional.kt │ │ │ ├── extensions │ │ │ │ ├── ActivityExtensions.kt │ │ │ │ ├── BitmapExtentions.kt │ │ │ │ ├── ContextExtensions.kt │ │ │ │ ├── FuctionalExtentions.kt │ │ │ │ ├── RxExtensions.kt │ │ │ │ ├── SharedPreferencesExtensions.kt │ │ │ │ ├── TensorFlowInferenceInterfaceExtensions.kt │ │ │ │ └── ViewExtensions.kt │ │ │ └── view │ │ │ │ └── DotsIndicator.kt │ │ │ ├── data │ │ │ ├── SharedPreferencesController.kt │ │ │ ├── ar │ │ │ │ ├── ArActivityUtils.kt │ │ │ │ └── StickerRenderable.kt │ │ │ ├── db │ │ │ │ ├── AppDatabase.kt │ │ │ │ ├── Cars.kt │ │ │ │ ├── CarsDao.kt │ │ │ │ ├── CarsList.kt │ │ │ │ └── DatabaseInitializer.kt │ │ │ └── recognizer │ │ │ │ ├── Car.kt │ │ │ │ ├── Recognition.kt │ │ │ │ ├── TFlowRecognizer.kt │ │ │ │ └── Tfwrapper.kt │ │ │ └── feature │ │ │ ├── camera │ │ │ ├── BottomSheetView.kt │ │ │ ├── CameraActivity.kt │ │ │ ├── CameraActivityModule.kt │ │ │ ├── CameraContract.kt │ │ │ ├── CameraPresenter.kt │ │ │ ├── ProgressView.kt │ │ │ └── RecognitionTextSwitcher.kt │ │ │ ├── cars │ │ │ ├── CarListActivity.kt │ │ │ ├── CarListContract.kt │ │ │ ├── CarListModule.kt │ │ │ ├── CarListPageTransformer.kt │ │ │ ├── CarListPresenter.kt │ │ │ ├── CarsPagerAdapter.kt │ │ │ ├── CircleProgressView.kt │ │ │ └── HorizontalProgressView.kt │ │ │ ├── onboarding │ │ │ ├── OnboardingActivity.kt │ │ │ ├── OnboardingAdapter.kt │ │ │ ├── OnboardingDiscoverFragment.kt │ │ │ ├── OnboardingFragment.kt │ │ │ ├── OnboardingRecognizeFragment.kt │ │ │ ├── OnboardingStayUpdatedFragment.kt │ │ │ └── PageFragment.kt │ │ │ └── splash │ │ │ └── SplashActivity.kt │ ├── res │ │ ├── anim │ │ │ ├── car_label_in_animation.xml │ │ │ └── car_label_out_animation.xml │ │ ├── color │ │ │ └── permission_button_color_selector.xml │ │ ├── drawable-hdpi │ │ │ ├── img_blue_car.png │ │ │ ├── img_next.png │ │ │ └── img_scan.png │ │ ├── drawable-mdpi │ │ │ ├── img_blue_car.png │ │ │ ├── img_next.png │ │ │ └── img_scan.png │ │ ├── drawable-nodpi │ │ │ ├── car_list_button_background_translucent.xml │ │ │ ├── close_recognition_button_background_translucent.xml │ │ │ ├── ford.png │ │ │ ├── ford_locked.png │ │ │ ├── fordfiesta.png │ │ │ ├── fordfiesta_locked.png │ │ │ ├── honda.png │ │ │ ├── honda_locked.png │ │ │ ├── hondacivic.png │ │ │ ├── hondacivic_locked.png │ │ │ ├── nissan.png │ │ │ ├── nissan_locked.png │ │ │ ├── nissanqashqai.png │ │ │ ├── nissanqashqai_locked.png │ │ │ ├── toyota.png │ │ │ ├── toyota_locked.png │ │ │ ├── toyotacamry.png │ │ │ ├── toyotacamry_locked.png │ │ │ ├── toyotacorolla.png │ │ │ ├── toyotacorolla_locked.png │ │ │ ├── volkswagen.png │ │ │ ├── volkswagen_locked.png │ │ │ ├── volkswagengolf.png │ │ │ ├── volkswagengolf_locked.png │ │ │ ├── volkswagenpassat.png │ │ │ ├── volkswagenpassat_locked.png │ │ │ ├── volkswagentiguan.png │ │ │ ├── volkswagentiguan_locked.png │ │ │ └── volskwagen_locked.png │ │ ├── drawable-xhdpi │ │ │ ├── img_blue_car.png │ │ │ ├── img_next.png │ │ │ └── img_scan.png │ │ ├── drawable-xxhdpi │ │ │ ├── img_blue_car.png │ │ │ ├── img_next.png │ │ │ └── img_scan.png │ │ ├── drawable-xxxhdpi │ │ │ ├── img_blue_car.png │ │ │ ├── img_next.png │ │ │ └── img_scan.png │ │ ├── drawable │ │ │ ├── camera_activity_bottom_sheet_background.xml │ │ │ ├── car_list_button.xml │ │ │ ├── car_list_button_background.xml │ │ │ ├── car_list_button_translucent.xml │ │ │ ├── circular_progress.xml │ │ │ ├── circular_progress_background.xml │ │ │ ├── close_recognition_button_translucent.xml │ │ │ ├── copyright_by.xml │ │ │ ├── dot_background.xml │ │ │ ├── extend_animator.xml │ │ │ ├── google_button.xml │ │ │ ├── google_button_background.xml │ │ │ ├── ic_arrow.xml │ │ │ ├── ic_launcher_background.xml │ │ │ ├── ic_launcher_foreground.xml │ │ │ ├── next_button_background.xml │ │ │ ├── onboarding_progress_circle.xml │ │ │ ├── permission_camera_image.xml │ │ │ ├── pin.xml │ │ │ ├── pin_background.xml │ │ │ ├── pointer.xml │ │ │ ├── rounded_bar.xml │ │ │ ├── scan_button_background.xml │ │ │ ├── splash_background.xml │ │ │ └── white_google_button.xml │ │ ├── font │ │ │ ├── blokk.ttf │ │ │ └── gligoth.TTF │ │ ├── layout │ │ │ ├── base_activity.xml │ │ │ ├── bottom_sheet_view.xml │ │ │ ├── camera_activity_bottom_sheet.xml │ │ │ ├── camera_activity_container.xml │ │ │ ├── camera_activity_content.xml │ │ │ ├── camera_activity_permission.xml │ │ │ ├── car_list_item_view.xml │ │ │ ├── car_list_view.xml │ │ │ ├── circle_progress_bar_with_label.xml │ │ │ ├── circle_progress_view.xml │ │ │ ├── dot_layout.xml │ │ │ ├── horizontal_progress_view.xml │ │ │ ├── keep.xml │ │ │ ├── onboarding_fragment.xml │ │ │ ├── onboarding_inside_fragment.xml │ │ │ └── sticker.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 │ │ ├── raw │ │ │ ├── keep.xml │ │ │ ├── onboarding_1.mp4 │ │ │ ├── onboarding_2.mp4 │ │ │ └── onboarding_3.mp4 │ │ ├── values │ │ │ ├── attrs.xml │ │ │ ├── colors.xml │ │ │ ├── dimens.xml │ │ │ ├── integers.xml │ │ │ ├── strings.xml │ │ │ └── styles.xml │ │ └── xml │ │ │ └── anim_drawable.xml │ └── rs │ │ └── yuv420888.rs │ ├── release │ └── kotlin │ │ └── co │ │ └── netguru │ │ └── android │ │ └── carrecognition │ │ └── application │ │ ├── DebugMetricsHelper.kt │ │ └── RxJavaErrorHandlerImpl.kt │ ├── staging │ └── kotlin │ │ └── co │ │ └── netguru │ │ └── android │ │ └── carrecognition │ │ └── application │ │ ├── DebugMetricsHelper.kt │ │ └── RxJavaErrorHandlerImpl.kt │ └── test │ ├── kotlin │ └── co │ │ └── netguru │ │ └── android │ │ └── carrecognition │ │ ├── RxSchedulersOverrideRule.kt │ │ ├── RxTestSchedulerOverrideRule.kt │ │ ├── common │ │ ├── LimitedListTest.kt │ │ ├── MetricsUtilsTest.kt │ │ └── OptionalTest.kt │ │ └── feature │ │ ├── camera │ │ └── CameraPresenterTest.kt │ │ └── cars │ │ └── CarListPresenterTest.kt │ └── resources │ └── mockito-extensions │ └── org.mockito.plugins.MockMaker ├── build.gradle ├── buildsystem ├── bitrise.gradle ├── dependencies.gradle ├── secrets.gradle └── sonarqube.gradle ├── codecov.yml ├── 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/NAT-) 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 -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ### Android 2 | 3 | # Built application files 4 | *.apk 5 | *.ap_ 6 | 7 | # Files for the ART/Dalvik VM 8 | *.dex 9 | 10 | # Java class files 11 | *.class 12 | 13 | # Generated files 14 | bin/ 15 | gen/ 16 | out/ 17 | 18 | # Gradle files 19 | .gradle/ 20 | build/ 21 | 22 | # Local configuration file (sdk path, etc) 23 | local.properties 24 | 25 | # Application secrets 26 | secret.properties 27 | 28 | Proguard folder generated by Eclipse 29 | #proguard/ 30 | 31 | # Log Files 32 | *.log 33 | 34 | # Android Studio Navigation editor temp files 35 | .navigation/ 36 | 37 | # Android Studio captures folder 38 | captures/ 39 | 40 | # Keystore files 41 | *.jks 42 | 43 | ### JetBrains 44 | 45 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm 46 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 47 | 48 | # IDEA/Android Studio ignores: 49 | *.iml 50 | *.ipr 51 | *.iws 52 | /.idea/* 53 | # IDEA/Android Studio Ignore exceptions 54 | !/.idea/fileTemplates/ 55 | !/.idea/inspectionProfiles/ 56 | !/.idea/scopes/ 57 | !/.idea/codeStyleSettings.xml 58 | !/.idea/encodings.xml 59 | !/.idea/copyright/ 60 | !/.idea/compiler.xml 61 | !/.idea/codeStyles 62 | 63 | # Project files backups generated on failures 64 | projectFilesBackup*/ 65 | 66 | ## Plugin-specific files: 67 | 68 | # IntelliJ 69 | /out/ 70 | 71 | # mpeltonen/sbt-idea plugin 72 | .idea_modules/ 73 | 74 | # JIRA plugin 75 | atlassian-ide-plugin.xml 76 | 77 | # Crashlytics plugin (for Android Studio and IntelliJ) 78 | com_crashlytics_export_strings.xml 79 | crashlytics.properties 80 | crashlytics-build.properties 81 | fabric.properties 82 | 83 | # Firebase 84 | google-services.json 85 | 86 | ### Temporary files 87 | 88 | ## Vim 89 | [._]*.s[a-w][a-z] 90 | [._]s[a-w][a-z] 91 | Session.vim 92 | .netrwhist 93 | tags 94 | 95 | ## OS X 96 | *.DS_Store 97 | .AppleDouble 98 | .LSOverride 99 | # Icon must end with two \r 100 | Icon 101 | # Thumbnails 102 | ._* 103 | # Files that might appear in the root of a volume 104 | .DocumentRevisions-V100 105 | .fseventsd 106 | .Spotlight-V100 107 | .TemporaryItems 108 | .Trashes 109 | .VolumeIcon.icns 110 | .com.apple.timemachine.donotpresent 111 | # Directories potentially created on remote AFP share 112 | .AppleDB 113 | .AppleDesktop 114 | Network Trash Folder 115 | Temporary Items 116 | .apdisk 117 | 118 | ## Linux 119 | *~ 120 | 121 | # temporary files which can be created if a process still has a handle open of a deleted file 122 | .fuse_hidden* 123 | 124 | # KDE directory preferences 125 | .directory 126 | 127 | # Linux trash folder which might appear on any partition or disk 128 | .Trash-* 129 | -------------------------------------------------------------------------------- /.idea/codeStyles/codeStyleConfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Car Regonition 2 | 3 | | environment | google play | app center | google play | 4 | |-------------|-----------------------|--------------------|---------------| 5 | | Android | [Google Play](https://play.google.com/store/apps/details?id=co.netguru.android.carrecognition) | [App Center](https://appcenter.ms/orgs/office-4dmm/apps/car-recognition-android)| [![Build Status](https://app.bitrise.io/app/9daec990ebe15a1e/status.svg?token=gGmNVn-KF3WX1axova7c3A&branch=master)](https://app.bitrise.io/app/9daec990ebe15a1e) | 6 | 7 | 8 | ## About 9 | Welcome to the **Car Regonition** project. It's an internal application made for detecting cars and showing informations about them. 10 | 11 | ## Testing 12 | - Unit Testing using Mockito + JUnit. Tests concerns Presnters layer and partially Model layer of MVP architecture 13 | 14 | ## Building 15 | 1. Clone repository: 16 | 17 | ```bash 18 | # over https: 19 | git clone https://github.com/netguru/car-recognition-android.git 20 | # or over SSH: 21 | git@github.com:netguru/car-recognition-android.git 22 | ``` 23 | 2. Place `secrets.properties` file in application root folder: 24 | ```bash 25 | AppCenterAppId=ACTUAL_KEY 26 | # (...) 27 | ``` 28 | 3. Open the project with Android Studio. 29 | 30 | ### Build types 31 | 32 | #### Debug 33 | - debuggable, 34 | - disabled ProGuard, 35 | - uses built-in shrinking (no obfuscation), 36 | - disabled crash reporting. 37 | 38 | #### Staging 39 | - non-debuggable, 40 | - uses full ProGuard configuration, 41 | - enables zipAlign, shrinkResources, 42 | - enabled crash reporting. 43 | 44 | #### Release 45 | - non-debuggable, 46 | - uses full ProGuard configuration, 47 | - enables zipAlign, shrinkResources, 48 | - enabled crash reporting. 49 | 50 | ### Build properties 51 | 52 | | Property | External property name | Environment variable | 53 | |----------------------|------------------------|----------------------| 54 | | App Center App ID | AppCenterAppId | APP_CENTER_APP_ID | 55 | | Sonar access token | - | SONAR_ACCESS_TOKEN | 56 | 57 | ### Bitrise 58 | Bitrise is separated for workflow mentioned below. Feature, MasterPr and Master workflows are responsible for builds stagingRelease flavours of the project which is used for developing new features. 59 | - feature - workflow triggered on Push or PR marge to develop branch; 60 | - develop - workflow triggered on Push to develop branch; 61 | - production - workflow triggered on Push to production branch; 62 | 63 | ### AppCenter / Fabric environments 64 | - Project use App Center for beta distribution and Crash managing and monitoring. Deploy to App Center is performed automatically by Bitrise system in develop and production step. 65 | 66 | ### Supported devices 67 | Supported devices are listed here: https://developers.google.com/ar/discover/supported-devices 68 | 69 | ## Related repositories 70 | - [iOS](https://github.com/netguru/car-recognition-ios) 71 | - [Machine Learning](https://github.com/netguru/car-recognition-ml) 72 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/proguard-rules-test.pro: -------------------------------------------------------------------------------- 1 | # Additional proguard rules for instrumentation testing 2 | 3 | -keep class rx.plugins.** { *; } 4 | -keep class org.junit.** { *; } 5 | -keep class co.netguru.android.testcommons.** { *; } 6 | -keep class android.support.test.espresso.** { *; } 7 | -dontwarn org.hamcrest.** 8 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /Users/maciek/Library/Android/sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | 19 | # base option from *App Dev Note* 20 | -optimizationpasses 5 21 | -dontusemixedcaseclassnames 22 | -dontskipnonpubliclibraryclasses 23 | -dontskipnonpubliclibraryclassmembers 24 | -dontpreverify 25 | -printmapping mapping.txt 26 | -optimizations !code/simplification/arithmetic,!field/*,!class/merging/* 27 | -keepattributes LineNumberTable,SourceFile,Signature,*Annotation*,Exceptions,InnerClasses 28 | -dontwarn org.checkerframework.** 29 | # Keep native methods 30 | -keepclassmembers class * { 31 | native ; 32 | } 33 | 34 | # remove log call 35 | -assumenosideeffects class android.util.Log { 36 | public static *** d(...); 37 | } 38 | -assumenosideeffects class timber.log.Timber { 39 | public static *** d(...); 40 | } 41 | 42 | # Models! 43 | # TODO 07.09.2017 Rule should be adjusted to current project - all models used with GSON should keep their members name 44 | # TODO 07.09.2017 or all their members should be annotated with @SerializedName(). 45 | -keepclassmembernames class co.netguru.android.carrecognition.data.**.model.** { *; } 46 | -keepclassmembernames class co.netguru.android.carrecognition.data.db.** { *; } 47 | 48 | # app compat-v7 49 | -keep class android.support.v7.widget.SearchView { *; } 50 | 51 | # FragmentArgs 52 | -keep class com.hannesdorfmann.fragmentargs.** { *; } 53 | 54 | # Gson 55 | -keep class sun.misc.Unsafe { *; } 56 | 57 | # retrofit 58 | -keepclasseswithmembers class * { 59 | @retrofit2.http.* ; 60 | } 61 | -dontnote retrofit2.Platform 62 | -dontnote retrofit2.Platform$IOS$MainThreadExecutor 63 | -dontwarn retrofit2.Platform$Java8 64 | -keepattributes Signature 65 | -keepattributes Exceptions 66 | 67 | # dagger 68 | -keepclassmembers,allowobfuscation class * { 69 | @javax.inject.* *; 70 | @dagger.* *; 71 | (); 72 | } 73 | -keep class javax.inject.** { *; } 74 | -keep class **$$ModuleAdapter 75 | -keep class **$$InjectAdapter 76 | -keep class **$$StaticInjection 77 | -keep class dagger.** { *; } 78 | -dontwarn dagger.internal.codegen.** 79 | 80 | # stetho 81 | -dontwarn org.apache.http.** 82 | -keep class com.facebook.stetho.dumpapp.** { *; } 83 | -keep class com.facebook.stetho.server.** { *; } 84 | -dontwarn com.facebook.stetho.dumpapp.** 85 | -dontwarn com.facebook.stetho.server.** 86 | 87 | # leak canary 88 | -keep class org.eclipse.mat.** { *; } 89 | -keep class com.squareup.leakcanary.** { *; } 90 | -dontwarn android.app.Notification 91 | 92 | # fabric 93 | -dontwarn com.crashlytics.android.** 94 | 95 | # glide 96 | -keep public class * implements com.bumptech.glide.module.GlideModule 97 | -keep public class * extends com.bumptech.glide.AppGlideModule 98 | -keep public enum com.bumptech.glide.load.resource.bitmap.ImageHeaderParser$** { 99 | **[] $VALUES; 100 | public *; 101 | } 102 | 103 | -dontwarn okhttp3.** 104 | -dontwarn okio.** 105 | -dontwarn javax.annotation.** 106 | 107 | #dagger 108 | -dontwarn com.google.errorprone.annotations.* 109 | 110 | #Keep the R 111 | -keepattributes InnerClasses 112 | -keep class **.R 113 | -keep class **.R$* { 114 | ; 115 | } 116 | -------------------------------------------------------------------------------- /app/src/debug/kotlin/co/netguru/android/carrecognition/application/DebugMetricsHelper.kt: -------------------------------------------------------------------------------- 1 | package co.netguru.android.carrecognition.application 2 | 3 | import android.content.Context 4 | import android.os.Handler 5 | import android.os.StrictMode 6 | import co.netguru.android.carrecognition.application.scope.AppScope 7 | import com.facebook.stetho.Stetho 8 | import com.frogermcs.androiddevmetrics.AndroidDevMetrics 9 | import com.github.moduth.blockcanary.BlockCanary 10 | import com.github.moduth.blockcanary.BlockCanaryContext 11 | import com.nshmura.strictmodenotifier.StrictModeNotifier 12 | import com.squareup.leakcanary.LeakCanary 13 | import timber.log.Timber 14 | import javax.inject.Inject 15 | 16 | /** 17 | * Helper class that initializes a set of debugging tools 18 | * for the debug build type and register crash manager for release type. 19 | * ## Debug type tools: 20 | * - AndroidDevMetrics 21 | * - Stetho 22 | * - StrictMode 23 | * - LeakCanary 24 | * - Timber 25 | * 26 | * ## Release type tools: 27 | * - CrashManager 28 | * 29 | * ## Staging type tools: 30 | * - CrashManager 31 | */ 32 | @AppScope 33 | class DebugMetricsHelper @Inject constructor() { 34 | 35 | internal fun init(context: Context) { 36 | // LeakCanary 37 | if (LeakCanary.isInAnalyzerProcess(context.applicationContext as App)) { 38 | // This process is dedicated to LeakCanary for heap analysis. 39 | // You should not init your app in this process. 40 | return 41 | } 42 | LeakCanary.install(context.applicationContext as App) 43 | 44 | // Stetho 45 | Stetho.initialize( 46 | Stetho.newInitializerBuilder(context) 47 | .enableDumpapp(Stetho.defaultDumperPluginsProvider(context)) 48 | .enableWebKitInspector(Stetho.defaultInspectorModulesProvider(context)) 49 | .build() 50 | ) 51 | 52 | //Timber 53 | Timber.plant(Timber.DebugTree()) 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /app/src/debug/kotlin/co/netguru/android/carrecognition/application/RxJavaErrorHandlerImpl.kt: -------------------------------------------------------------------------------- 1 | package co.netguru.android.carrecognition.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/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 14 | 15 | 22 | 23 | 26 | 27 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 41 | 45 | 46 | 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /app/src/main/assets/CarClassifierModel_190125_1802.pb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netguru/CarLens-Android/321fa52c5cc0a339b27737b626163f8f72348cd9/app/src/main/assets/CarClassifierModel_190125_1802.pb -------------------------------------------------------------------------------- /app/src/main/assets/CarDetectorModel_181130_1111.pb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netguru/CarLens-Android/321fa52c5cc0a339b27737b626163f8f72348cd9/app/src/main/assets/CarDetectorModel_181130_1111.pb -------------------------------------------------------------------------------- /app/src/main/assets/cars.json: -------------------------------------------------------------------------------- 1 | { 2 | "cars": [ 3 | { 4 | "id": "HondaCivic", 5 | "brand": "Honda", 6 | "model": "Civic", 7 | "description": "Originally a subcompact, the Civic has gone through several generational changes, becoming both larger and more upmarket.", 8 | "stars": 2, 9 | "acceleration_mph": 10.0, 10 | "speed_mph": 135, 11 | "power": 120, 12 | "engine": 1000, 13 | "brand_logo_image": "Honda", 14 | "brand_logo_image_locked": "Honda_locked", 15 | "image": "HondaCivic", 16 | "image_locked": "HondaCivic_locked" 17 | }, 18 | { 19 | "id": "FordFiesta", 20 | "brand": "Ford", 21 | "model": "Fiesta", 22 | "description": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.", 23 | "stars": 1, 24 | "acceleration_mph": 12.7, 25 | "speed_mph": 118, 26 | "power": 70, 27 | "engine": 1000, 28 | "brand_logo_image": "Ford", 29 | "brand_logo_image_locked": "Ford_locked", 30 | "image": "FordFiesta", 31 | "image_locked": "FordFiesta_locked" 32 | }, 33 | { 34 | "id": "NissanQashqai", 35 | "brand": "Nissan", 36 | "model": "Qashqai", 37 | "description": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.", 38 | "stars": 3, 39 | "acceleration_mph": 9.0, 40 | "speed_mph": 140, 41 | "power": 163, 42 | "engine": 1600, 43 | "brand_logo_image": "Nissan", 44 | "brand_logo_image_locked": "Nissan_locked", 45 | "image": "NissanQashqai", 46 | "image_locked": "NissanQashqai_locked" 47 | }, 48 | { 49 | "id": "VolkswagenPassat", 50 | "brand": "Volkswagen", 51 | "model": "Passat", 52 | "description": "Volkswagen Passat is a large family car manufactured and marketed by Volkswagen since 1973.", 53 | "stars": 4, 54 | "acceleration_mph": 8.0, 55 | "speed_mph": 148, 56 | "power": 280, 57 | "engine": 2000, 58 | "brand_logo_image": "Volkswagen", 59 | "brand_logo_image_locked": "Volkswagen_locked", 60 | "image": "VolkswagenPassat", 61 | "image_locked": "VolkswagenPassat_locked" 62 | } 63 | ] 64 | } 65 | -------------------------------------------------------------------------------- /app/src/main/assets/cars_labels.txt: -------------------------------------------------------------------------------- 1 | ford_fiesta 2 | honda_civic 3 | nissan_qashqai 4 | other_car 5 | volkswagen_passat 6 | -------------------------------------------------------------------------------- /app/src/main/assets/progress-bar-linear.json: -------------------------------------------------------------------------------- 1 | {"v":"4.10.1","fr":60,"ip":0,"op":40,"w":125,"h":27,"nm":"bar/8","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"Mask 2","td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,1,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-9.08,2.17],[-25.54,0.06],[0,-0.49],[0,0],[1.1,0],[0,0],[0,0.38],[-0.38,0.01]],"o":[[24.92,-5.93],[0.49,0],[0,0],[0,1.1],[0,0],[-0.38,0],[0,-0.38],[35.49,-0.19]],"v":[[67.54,21],[124.11,0],[125,0.88],[125,23.93],[123,25.93],[0.69,25.93],[0,25.24],[0.69,24.54]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.933332979679,0.952941000462,0.96078401804,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":3600,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"Shape Layer 1","tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"n":"0p833_0p833_0p167_0p167","t":0,"s":[-63.5,13.5,0],"e":[62.5,13.5,0],"to":[21,0,0],"ti":[-21,0,0]},{"t":40}],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[127.951,32.929],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[0.848253676471,0,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":1,"lj":1,"ml":4,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"gf","o":{"a":0,"k":100,"ix":10},"r":1,"g":{"p":3,"k":{"a":0,"k":[0,1,0.349,0.412,0.426,1,0.451,0.325,0.852,1,0.553,0.239],"ix":9}},"s":{"a":0,"k":[0,0],"ix":5},"e":{"a":0,"k":[100,0],"ix":6},"t":1,"nm":"Gradient Fill 1","mn":"ADBE Vector Graphic - G-Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-1.024,-0.036],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":3600,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"Mask","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,1,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-9.08,2.17],[-25.54,0.06],[0,-0.49],[0,0],[1.1,0],[0,0],[0,0.38],[-0.38,0.01]],"o":[[24.92,-5.93],[0.49,0],[0,0],[0,1.1],[0,0],[-0.38,0],[0,-0.38],[35.49,-0.19]],"v":[[67.54,21],[124.11,0],[125,0.88],[125,23.93],[123,25.93],[0.69,25.93],[0,25.24],[0.69,24.54]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.933332979679,0.952941000462,0.96078401804,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":3600,"st":0,"bm":0}]} -------------------------------------------------------------------------------- /app/src/main/assets/progress-bar.json: -------------------------------------------------------------------------------- 1 | {"v":"4.10.1","fr":60,"ip":0,"op":40,"w":125,"h":27,"nm":"bar/8","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"Mask 2","td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,1,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-9.08,2.17],[-25.54,0.06],[0,-0.49],[0,0],[1.1,0],[0,0],[0,0.38],[-0.38,0.01]],"o":[[24.92,-5.93],[0.49,0],[0,0],[0,1.1],[0,0],[-0.38,0],[0,-0.38],[35.49,-0.19]],"v":[[67.54,21],[124.11,0],[125,0.88],[125,23.93],[123,25.93],[0.69,25.93],[0,25.24],[0.69,24.54]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.933332979679,0.952941000462,0.96078401804,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":3600,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"Shape Layer 1","tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.32,"y":1},"o":{"x":0.68,"y":0},"n":"0p32_1_0p68_0","t":0,"s":[-63.5,13.5,0],"e":[62.5,13.5,0],"to":[21,0,0],"ti":[-21,0,0]},{"t":40}],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[127.951,32.929],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[0.848253676471,0,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":1,"lj":1,"ml":4,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"gf","o":{"a":0,"k":100,"ix":10},"r":1,"g":{"p":3,"k":{"a":0,"k":[0,1,0.349,0.412,0.426,1,0.451,0.325,0.852,1,0.553,0.239],"ix":9}},"s":{"a":0,"k":[0,0],"ix":5},"e":{"a":0,"k":[100,0],"ix":6},"t":1,"nm":"Gradient Fill 1","mn":"ADBE Vector Graphic - G-Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-1.024,-0.036],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":3600,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"Mask","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,1,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-9.08,2.17],[-25.54,0.06],[0,-0.49],[0,0],[1.1,0],[0,0],[0,0.38],[-0.38,0.01]],"o":[[24.92,-5.93],[0.49,0],[0,0],[0,1.1],[0,0],[-0.38,0],[0,-0.38],[35.49,-0.19]],"v":[[67.54,21],[124.11,0],[125,0.88],[125,23.93],[123,25.93],[0.69,25.93],[0,25.24],[0.69,24.54]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.933332979679,0.952941000462,0.96078401804,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":3600,"st":0,"bm":0}]} -------------------------------------------------------------------------------- /app/src/main/assets/rating_bar.json: -------------------------------------------------------------------------------- 1 | {"v":"4.10.1","fr":60,"ip":0,"op":60,"w":74,"h":14,"nm":"stars","ddd":0,"assets":[{"id":"comp_0","layers":[{"ddd":0,"ind":1,"ty":4,"nm":"Shape Layer 1","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[7,7,0],"ix":2},"a":{"a":0,"k":[-4.25,-4.781,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.676,0.676,0.33],"y":[1,1,1]},"o":{"x":[0.67,0.67,0.67],"y":[0,0,0]},"n":["0p676_1_0p67_0","0p676_1_0p67_0","0p33_1_0p67_0"],"t":40,"s":[100,100,100],"e":[150,150,100]},{"t":52}],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"sr","sy":1,"d":1,"pt":{"a":0,"k":5,"ix":3},"p":{"a":0,"k":[0,0],"ix":4},"r":{"a":0,"k":0,"ix":5},"ir":{"a":0,"k":2.126,"ix":6},"is":{"a":0,"k":0,"ix":8},"or":{"a":0,"k":4.253,"ix":7},"os":{"a":0,"k":0,"ix":9},"ix":1,"nm":"Polystar Path 1","mn":"ADBE Vector Shape - Star","hd":false},{"ty":"st","c":{"a":0,"k":[0.848253676471,0,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":1,"lj":1,"ml":4,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":1,"k":[{"i":{"x":[0.615],"y":[1]},"o":{"x":[0.67],"y":[0]},"n":["0p615_1_0p67_0"],"t":40,"s":[0.933333337307,0.952941179276,0.960784316063,1],"e":[1,0.552941176471,0.239215686275,1]},{"t":54}],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-4.25,-4.375],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Polystar 1","np":3,"cix":2,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":3600,"st":0,"bm":0}]}],"layers":[{"ddd":0,"ind":1,"ty":3,"nm":"▽ Stacked Group","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,2,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"ip":0,"op":3600,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":0,"nm":"↻ star/full copy 7","parent":1,"refId":"comp_0","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[60,-2.5,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"w":14,"h":14,"ip":0,"op":3600,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":0,"nm":"↻ star/full copy 6","parent":1,"refId":"comp_0","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[45,-2.5,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"w":14,"h":14,"ip":-11,"op":3589,"st":-11,"bm":0},{"ddd":0,"ind":4,"ty":0,"nm":"↻ star/full copy 5","parent":1,"refId":"comp_0","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[30,-2.5,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"w":14,"h":14,"ip":-21,"op":3579,"st":-21,"bm":0},{"ddd":0,"ind":5,"ty":0,"nm":"↻ star/full copy 4","parent":1,"refId":"comp_0","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[15,-2.5,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"w":14,"h":14,"ip":-30,"op":3570,"st":-30,"bm":0},{"ddd":0,"ind":6,"ty":0,"nm":"↻ star/full copy 3","parent":1,"refId":"comp_0","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,-2.5,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"w":14,"h":14,"ip":-40,"op":3560,"st":-40,"bm":0}]} 2 | -------------------------------------------------------------------------------- /app/src/main/ic_launcher-web.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netguru/CarLens-Android/321fa52c5cc0a339b27737b626163f8f72348cd9/app/src/main/ic_launcher-web.png -------------------------------------------------------------------------------- /app/src/main/kotlin/co/netguru/android/carrecognition/application/ActivityBindingModule.kt: -------------------------------------------------------------------------------- 1 | package co.netguru.android.carrecognition.application 2 | 3 | import co.netguru.android.carrecognition.application.scope.ActivityScope 4 | import co.netguru.android.carrecognition.feature.camera.CameraActivity 5 | import co.netguru.android.carrecognition.feature.camera.CameraActivityModule 6 | import co.netguru.android.carrecognition.feature.cars.CarListActivity 7 | import co.netguru.android.carrecognition.feature.cars.CarListModule 8 | import co.netguru.android.carrecognition.feature.splash.SplashActivity 9 | import dagger.Module 10 | import dagger.android.AndroidInjectionModule 11 | import dagger.android.ContributesAndroidInjector 12 | 13 | 14 | @Module(includes = [AndroidInjectionModule::class]) 15 | internal abstract class ActivityBindingsModule { 16 | 17 | @ActivityScope 18 | @ContributesAndroidInjector() 19 | internal abstract fun splashActivityInjector(): SplashActivity 20 | 21 | @ActivityScope 22 | @ContributesAndroidInjector(modules = [(CameraActivityModule::class)]) 23 | internal abstract fun cameraActivityInjector(): CameraActivity 24 | 25 | @ActivityScope 26 | @ContributesAndroidInjector(modules = [(CarListModule::class)]) 27 | internal abstract fun carListActivityInjector(): CarListActivity 28 | } 29 | -------------------------------------------------------------------------------- /app/src/main/kotlin/co/netguru/android/carrecognition/application/App.kt: -------------------------------------------------------------------------------- 1 | package co.netguru.android.carrecognition.application 2 | 3 | import dagger.android.AndroidInjector 4 | import dagger.android.support.DaggerApplication 5 | import io.reactivex.plugins.RxJavaPlugins 6 | import javax.inject.Inject 7 | 8 | class App : DaggerApplication() { 9 | 10 | @Inject 11 | lateinit var debugMetricsHelper: DebugMetricsHelper 12 | 13 | @Inject 14 | lateinit var rxJavaErrorHandler: RxJavaErrorHandler 15 | 16 | override fun onCreate() { 17 | super.onCreate() 18 | debugMetricsHelper.init(this) 19 | RxJavaPlugins.setErrorHandler(rxJavaErrorHandler) 20 | } 21 | 22 | override fun applicationInjector(): AndroidInjector = 23 | DaggerApplicationComponent.builder().create(this) 24 | } 25 | -------------------------------------------------------------------------------- /app/src/main/kotlin/co/netguru/android/carrecognition/application/ApplicationComponent.kt: -------------------------------------------------------------------------------- 1 | package co.netguru.android.carrecognition.application 2 | 3 | import co.netguru.android.carrecognition.application.scope.AppScope 4 | import dagger.Component 5 | import dagger.android.AndroidInjector 6 | import dagger.android.support.AndroidSupportInjectionModule 7 | 8 | @AppScope 9 | @Component( 10 | modules = [ 11 | AndroidSupportInjectionModule::class, 12 | ApplicationModule::class, 13 | TFModule::class, 14 | ActivityBindingsModule::class, 15 | FragmentBindingsModule::class 16 | ] 17 | ) 18 | internal interface ApplicationComponent : AndroidInjector { 19 | @Component.Builder 20 | abstract class Builder : AndroidInjector.Builder() 21 | } 22 | -------------------------------------------------------------------------------- /app/src/main/kotlin/co/netguru/android/carrecognition/application/ApplicationModule.kt: -------------------------------------------------------------------------------- 1 | package co.netguru.android.carrecognition.application 2 | 3 | import android.arch.persistence.room.Room 4 | import android.content.Context 5 | import android.content.SharedPreferences 6 | import android.preference.PreferenceManager 7 | import co.netguru.android.carrecognition.application.scope.AppScope 8 | import co.netguru.android.carrecognition.data.db.AppDatabase 9 | import co.netguru.android.carrecognition.data.recognizer.TFWrapper 10 | import dagger.Module 11 | import dagger.Provides 12 | import org.tensorflow.contrib.android.TensorFlowInferenceInterface 13 | import java.io.BufferedReader 14 | import javax.inject.Named 15 | 16 | 17 | @Module 18 | class ApplicationModule { 19 | 20 | companion object { 21 | const val DATABASE_NAME = "cars.db" 22 | } 23 | 24 | @Provides 25 | @AppScope 26 | fun rxJavaErrorHandler(): RxJavaErrorHandler = RxJavaErrorHandlerImpl() 27 | 28 | @Provides 29 | @AppScope 30 | fun bindContext(application: App): Context = application 31 | 32 | 33 | @Provides 34 | @AppScope 35 | fun provideDatabase(context: Context): AppDatabase { 36 | return Room.databaseBuilder(context, AppDatabase::class.java, DATABASE_NAME).build() 37 | } 38 | 39 | @Provides 40 | @AppScope 41 | internal fun providesSharedPreferences(context: Context): SharedPreferences { 42 | return PreferenceManager.getDefaultSharedPreferences(context) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /app/src/main/kotlin/co/netguru/android/carrecognition/application/FragmentBindingModule.kt: -------------------------------------------------------------------------------- 1 | package co.netguru.android.carrecognition.application 2 | 3 | import co.netguru.android.carrecognition.application.scope.FragmentScope 4 | import co.netguru.android.carrecognition.feature.onboarding.OnboardingFragment 5 | import dagger.Module 6 | import dagger.android.AndroidInjectionModule 7 | import dagger.android.ContributesAndroidInjector 8 | 9 | 10 | @Module(includes = [AndroidInjectionModule::class]) 11 | internal abstract class FragmentBindingsModule { 12 | 13 | @FragmentScope 14 | @ContributesAndroidInjector() 15 | internal abstract fun onboardingFragmentInjector(): OnboardingFragment 16 | } 17 | -------------------------------------------------------------------------------- /app/src/main/kotlin/co/netguru/android/carrecognition/application/RxJavaErrorHandler.kt: -------------------------------------------------------------------------------- 1 | package co.netguru.android.carrecognition.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/android/carrecognition/application/TFModule.kt: -------------------------------------------------------------------------------- 1 | package co.netguru.android.carrecognition.application 2 | 3 | import android.content.Context 4 | import co.netguru.android.carrecognition.application.scope.AppScope 5 | import co.netguru.android.carrecognition.data.recognizer.TFWrapper 6 | import dagger.Module 7 | import dagger.Provides 8 | import org.tensorflow.contrib.android.TensorFlowInferenceInterface 9 | import java.io.BufferedReader 10 | import javax.inject.Named 11 | 12 | @Module 13 | class TFModule { 14 | 15 | companion object { 16 | 17 | const val DETECTOR = "detector" 18 | const val DETECTOR_MODEL_PATH = "CarDetectorModel_181130_1111.pb" 19 | const val DETECTOR_INPUT_LAYER_NAME = "input_0" 20 | const val DETECTOR_OUTPUT_LAYER_NAME = "final_result_0/Softmax" 21 | 22 | const val RECOGNIZER = "recognizer" 23 | const val RECOGNIZER_MODEL_PATH = "CarClassifierModel_190125_1802.pb" 24 | const val RECOGNIZER_INPUT_LAYER_NAME = "input_0" 25 | const val RECOGNIZER_OUTPUT_LAYER_NAME = "final_result_0/Softmax" 26 | 27 | const val LABELS_PATH = "cars_labels.txt" 28 | const val LABELS_BINDING = "labels" 29 | 30 | const val INPUT_SIZE = 224 31 | const val NR_OF_CHANNELS = 1 32 | } 33 | 34 | @Provides 35 | @AppScope 36 | @Named(DETECTOR) 37 | fun provideDetector(context: Context): TFWrapper { 38 | return TFWrapper( 39 | tf = TensorFlowInferenceInterface(context.assets, DETECTOR_MODEL_PATH), 40 | inputLayerName = DETECTOR_INPUT_LAYER_NAME, 41 | outputLayerName = DETECTOR_OUTPUT_LAYER_NAME, 42 | inputSize = INPUT_SIZE, 43 | outputSize = 2, 44 | nrOfChannels = NR_OF_CHANNELS 45 | ) 46 | } 47 | 48 | @Provides 49 | @AppScope 50 | @Named(RECOGNIZER) 51 | fun provideRecognizer(context: Context): TFWrapper { 52 | return TFWrapper( 53 | tf = TensorFlowInferenceInterface(context.assets, RECOGNIZER_MODEL_PATH), 54 | inputLayerName = RECOGNIZER_INPUT_LAYER_NAME, 55 | outputLayerName = RECOGNIZER_OUTPUT_LAYER_NAME, 56 | inputSize = INPUT_SIZE, 57 | outputSize = 6, 58 | nrOfChannels = NR_OF_CHANNELS 59 | ) 60 | } 61 | 62 | @Provides 63 | @AppScope 64 | @Named(LABELS_BINDING) 65 | fun provideLabels(context: Context): List { 66 | val stream = context.assets.open(LABELS_PATH) 67 | return stream.bufferedReader().use(BufferedReader::readText).split("\n") 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /app/src/main/kotlin/co/netguru/android/carrecognition/application/scope/ActivityScope.kt: -------------------------------------------------------------------------------- 1 | package co.netguru.android.carrecognition.application.scope 2 | 3 | import javax.inject.Scope 4 | 5 | @Scope 6 | annotation class ActivityScope 7 | -------------------------------------------------------------------------------- /app/src/main/kotlin/co/netguru/android/carrecognition/application/scope/AppScope.kt: -------------------------------------------------------------------------------- 1 | package co.netguru.android.carrecognition.application.scope 2 | 3 | import javax.inject.Scope 4 | 5 | @Scope 6 | annotation class AppScope 7 | -------------------------------------------------------------------------------- /app/src/main/kotlin/co/netguru/android/carrecognition/application/scope/FragmentScope.kt: -------------------------------------------------------------------------------- 1 | package co.netguru.android.carrecognition.application.scope 2 | 3 | import javax.inject.Scope 4 | 5 | @Scope 6 | annotation class FragmentScope 7 | -------------------------------------------------------------------------------- /app/src/main/kotlin/co/netguru/android/carrecognition/common/AnimationUtils.kt: -------------------------------------------------------------------------------- 1 | package co.netguru.android.carrecognition.common 2 | 3 | import android.animation.ValueAnimator 4 | 5 | object AnimationUtils { 6 | 7 | const val DEFAULT_ANIMATION_LENGTH = 300L 8 | 9 | @Suppress("UNCHECKED_CAST") 10 | fun createAnimator(topValue: T, onUpdate: (T) -> Unit, 11 | propertiesBlock: ValueAnimator.() -> Unit) = 12 | when (topValue) { 13 | is Float -> ValueAnimator.ofFloat(0f, 1f * topValue) 14 | is Int -> ValueAnimator.ofInt(0, topValue) 15 | else -> throw IllegalArgumentException("value must be Int or Float") 16 | }.apply { 17 | propertiesBlock() 18 | addUpdateListener { 19 | onUpdate(it.animatedValue as T) 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /app/src/main/kotlin/co/netguru/android/carrecognition/common/LimitedList.kt: -------------------------------------------------------------------------------- 1 | package co.netguru.android.carrecognition.common 2 | 3 | class LimitedList(private val limit: Int) : Iterable { 4 | 5 | private val list = ArrayList(limit) 6 | 7 | fun add(element: T) { 8 | list.add(element) 9 | if (list.size > limit) list.removeAt(0) 10 | } 11 | 12 | fun addAll(toAdd: List) { 13 | list.addAll(toAdd) 14 | while (list.size > limit) list.removeAt(0) 15 | } 16 | 17 | fun size() = list.size 18 | fun clear() = list.clear() 19 | fun isFull() = list.size == limit 20 | 21 | operator fun get(index: Int) = list[index] 22 | override fun iterator() = list.iterator() 23 | } 24 | -------------------------------------------------------------------------------- /app/src/main/kotlin/co/netguru/android/carrecognition/common/MetricsUtils.kt: -------------------------------------------------------------------------------- 1 | package co.netguru.android.carrecognition.common 2 | 3 | import android.content.res.Resources 4 | import co.netguru.android.carrecognition.R 5 | import java.util.* 6 | 7 | object MetricsUtils { 8 | const val MILE_TO_KM_FACTOR = 1.60934 9 | private val IMPERIAL_SYSTEM_COUNTRIES = arrayListOf("us", "gb", "bs", "bz", "dm", "gd", "ws", "lc", "vc", "kn", "ag") 10 | 11 | fun getConvertedMetric(locale: Locale, resources: Resources, miles: Int): String = 12 | if (isImperialMetricSystem(locale)) { 13 | resources.getString(R.string.mph_format, miles) 14 | } else { 15 | resources.getString(R.string.kmph_format, (miles * MILE_TO_KM_FACTOR).toInt()) 16 | } 17 | 18 | fun getAccelerationLabel(locale: Locale) = 19 | if (isImperialMetricSystem(locale)) { 20 | R.string.zero_to_sixty 21 | } else { 22 | R.string.zero_to_hundred 23 | } 24 | 25 | private fun isImperialMetricSystem(locale: Locale) = IMPERIAL_SYSTEM_COUNTRIES.contains(locale.country.toLowerCase()) 26 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/co/netguru/android/carrecognition/common/MultimapExt.kt: -------------------------------------------------------------------------------- 1 | typealias Multimap = Map> 2 | 3 | fun Multimap.partition( 4 | predicate: (V) -> Boolean 5 | ): Pair, Multimap> { 6 | val first = mutableMapOf>() 7 | val second = mutableMapOf>() 8 | for (k in this.keys) { 9 | val (firstValuePartition, secondValuePartition) = this[k]!!.partition(predicate) 10 | first[k] = firstValuePartition 11 | second[k] = secondValuePartition 12 | } 13 | return Pair(first, second) 14 | } 15 | 16 | fun Multimap.toPairsList(): List> = this.flatMap { (k, v) -> 17 | v.map { k to it } 18 | } 19 | 20 | fun Multimap.flattenValues() = this.values.flatten() 21 | 22 | operator fun Multimap.plus(pair: Pair): Multimap = if (this.isEmpty()) { 23 | mapOf(pair.first to listOf(pair.second)) 24 | } else { 25 | val mutableMap = this.toMutableMap() 26 | val keyValues = mutableMap[pair.first] 27 | if (keyValues == null) { 28 | mutableMap[pair.first] = listOf(pair.second) 29 | } else { 30 | mutableMap[pair.first] = keyValues + listOf(pair.second) 31 | } 32 | mutableMap.toMap() 33 | } 34 | 35 | operator fun Multimap.minus(pair: Pair): Multimap = if (this.isEmpty()) { 36 | emptyMap() 37 | } else { 38 | val valuesForKey = this[pair.first] 39 | ?.filter { it != pair.second } 40 | ?.let { if (it.isEmpty()) null else it } 41 | if (valuesForKey == null) { 42 | this.filterKeys { it == pair.first } 43 | } else { 44 | this.toMutableMap().apply { 45 | this[pair.first] = valuesForKey - pair.second 46 | } 47 | .toMap() 48 | } 49 | } 50 | 51 | fun List>.toMultiMap(): Multimap { 52 | var result = mapOf>() 53 | forEach { 54 | result += it 55 | } 56 | return result 57 | } 58 | 59 | fun List.toMultiMap(mapper: (I) -> Pair): Multimap { 60 | var result = mapOf>() 61 | forEach { 62 | result += mapper(it) 63 | } 64 | return result 65 | } 66 | -------------------------------------------------------------------------------- /app/src/main/kotlin/co/netguru/android/carrecognition/common/Optional.kt: -------------------------------------------------------------------------------- 1 | package co.netguru.android.carrecognition.common 2 | 3 | import io.reactivex.Maybe 4 | import io.reactivex.Observable 5 | import io.reactivex.Single 6 | import io.reactivex.rxkotlin.ofType 7 | 8 | /** 9 | * Optional implementation intended to be used together with Rx2 which doesn't accept nulls. 10 | */ 11 | sealed class Optional { 12 | 13 | fun toNullable(): T? = when (this) { 14 | is Some -> value 15 | is None -> null 16 | } 17 | 18 | data class Some(val value: T) : Optional() 19 | object None : Optional() 20 | 21 | /** 22 | * If a value is present, apply the provided mapping function to it, 23 | * and if the result is non-null, return an [Optional.Some] describing the 24 | * result. Otherwise return an empty [Optional.None]. 25 | */ 26 | inline fun map(f: (T) -> U?): Optional = when (this) { 27 | is Optional.Some -> f(value).toOptional() 28 | Optional.None -> None 29 | } 30 | 31 | /** 32 | * If a value is present, apply the provided [Optional]-bearing 33 | * mapping function to it, return that result, otherwise return an empty 34 | * [Optional.None]. This method is similar to [Optional.map], 35 | * but the provided mapper is one whose result is already an [Optional]. 36 | */ 37 | inline fun flatMap(f: (T) -> Optional): Optional = when (this) { 38 | is Optional.Some -> f(value) 39 | Optional.None -> None 40 | } 41 | } 42 | 43 | fun T?.toOptional(): Optional = if (this == null) Optional.None else Optional.Some( 44 | this 45 | ) 46 | 47 | fun Observable>.filterOptionalNone(): Observable = 48 | this.ofType>() 49 | .map { it.toNullable() } 50 | 51 | fun Single>.filterOptionalNone(): Maybe = 52 | this.filter { it is Optional.Some } 53 | .map { it.toNullable() } 54 | 55 | fun Maybe>.filterOptionalNone(): Maybe = 56 | this.filter { it is Optional.Some } 57 | .map { it.toNullable() } 58 | -------------------------------------------------------------------------------- /app/src/main/kotlin/co/netguru/android/carrecognition/common/extensions/ActivityExtensions.kt: -------------------------------------------------------------------------------- 1 | package co.netguru.android.carrecognition.common.extensions 2 | 3 | import android.support.annotation.IdRes 4 | import android.support.v4.app.Fragment 5 | import android.support.v7.app.AppCompatActivity 6 | 7 | fun AppCompatActivity.replaceFragment(@IdRes containerViewId: Int, fragment: Fragment, TAG: String) { 8 | supportFragmentManager 9 | .beginTransaction() 10 | .replace(containerViewId, fragment, TAG) 11 | .commit() 12 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/co/netguru/android/carrecognition/common/extensions/ContextExtensions.kt: -------------------------------------------------------------------------------- 1 | package co.netguru.android.carrecognition.common.extensions 2 | 3 | import android.app.Activity 4 | import android.content.Context 5 | import android.content.Intent 6 | import android.support.annotation.AttrRes 7 | import android.support.annotation.ColorRes 8 | import android.support.annotation.DrawableRes 9 | import android.support.v4.content.ContextCompat 10 | import android.util.TypedValue 11 | 12 | inline fun Context.startActivity() { 13 | val intent = Intent(this, T::class.java) 14 | startActivity(intent) 15 | } 16 | 17 | fun Context.getColorCompat(@ColorRes color: Int) = ContextCompat.getColor(this, color) 18 | 19 | fun Context.getDrawableCompat(@DrawableRes drawable: Int) = 20 | ContextCompat.getDrawable(this, drawable) 21 | 22 | fun Context.getAttributeColor(@AttrRes attrColor: Int): Int { 23 | val typedValue = TypedValue() 24 | theme.resolveAttribute(attrColor, typedValue, true) 25 | return typedValue.data 26 | } 27 | 28 | fun Context.getAttributeDrawable(@AttrRes attrDrawableRes: Int): Int { 29 | val typedValue = TypedValue() 30 | theme.resolveAttribute(attrDrawableRes, typedValue, true) 31 | return typedValue.resourceId 32 | } 33 | 34 | fun Context.getResourceIdentifier(name: String, defType: String) = resources.getIdentifier(name, 35 | defType, packageName) 36 | 37 | fun Context.getDrawableIdentifier(name: String) = getResourceIdentifier(name.toLowerCase(), "drawable") 38 | -------------------------------------------------------------------------------- /app/src/main/kotlin/co/netguru/android/carrecognition/common/extensions/FuctionalExtentions.kt: -------------------------------------------------------------------------------- 1 | package co.netguru.android.carrecognition.common.extensions 2 | 3 | inline fun Pair.map(f: (Pair) -> OUT): OUT { 4 | return f(this) 5 | } 6 | -------------------------------------------------------------------------------- /app/src/main/kotlin/co/netguru/android/carrecognition/common/extensions/RxExtensions.kt: -------------------------------------------------------------------------------- 1 | package co.netguru.android.carrecognition.common.extensions 2 | 3 | import io.reactivex.* 4 | 5 | import io.reactivex.android.schedulers.AndroidSchedulers 6 | import io.reactivex.schedulers.Schedulers 7 | 8 | fun Completable.applyIoSchedulers() = this.subscribeOn(Schedulers.io()) 9 | .observeOn(AndroidSchedulers.mainThread()) 10 | 11 | fun Completable.applyComputationSchedulers() = this.subscribeOn(Schedulers.computation()) 12 | .observeOn(AndroidSchedulers.mainThread()) 13 | 14 | fun Maybe.applyIoSchedulers() = this.subscribeOn(Schedulers.io()) 15 | .observeOn(AndroidSchedulers.mainThread()) 16 | 17 | fun Maybe.applyComputationSchedulers() = this.subscribeOn(Schedulers.computation()) 18 | .observeOn(AndroidSchedulers.mainThread()) 19 | 20 | fun Single.applyIoSchedulers() = this.subscribeOn(Schedulers.io()) 21 | .observeOn(AndroidSchedulers.mainThread()) 22 | 23 | fun Single.applyComputationSchedulers() = this.subscribeOn(Schedulers.computation()) 24 | .observeOn(AndroidSchedulers.mainThread()) 25 | 26 | fun Observable.applyIoSchedulers() = this.subscribeOn(Schedulers.io()) 27 | .observeOn(AndroidSchedulers.mainThread()) 28 | 29 | fun Observable.applyComputationSchedulers() = this.subscribeOn(Schedulers.computation()) 30 | .observeOn(AndroidSchedulers.mainThread()) 31 | 32 | fun Flowable.applyIoSchedulers(): Flowable = this.subscribeOn(Schedulers.io()) 33 | .observeOn(AndroidSchedulers.mainThread()) 34 | 35 | fun Flowable.applyComputationSchedulers(): Flowable = 36 | this.subscribeOn(Schedulers.computation()) 37 | .observeOn(AndroidSchedulers.mainThread()) 38 | -------------------------------------------------------------------------------- /app/src/main/kotlin/co/netguru/android/carrecognition/common/extensions/SharedPreferencesExtensions.kt: -------------------------------------------------------------------------------- 1 | package co.netguru.android.carrecognition.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/android/carrecognition/common/extensions/TensorFlowInferenceInterfaceExtensions.kt: -------------------------------------------------------------------------------- 1 | package co.netguru.android.carrecognition.common.extensions 2 | 3 | import org.tensorflow.contrib.android.TensorFlowInferenceInterface 4 | 5 | fun TensorFlowInferenceInterface.getOutputSize(outputLayer: String): Int { 6 | return graphOperation(outputLayer).output(0).shape().size(0).toInt() 7 | } 8 | -------------------------------------------------------------------------------- /app/src/main/kotlin/co/netguru/android/carrecognition/common/extensions/ViewExtensions.kt: -------------------------------------------------------------------------------- 1 | package co.netguru.android.carrecognition.common.extensions 2 | 3 | import android.app.Activity 4 | import android.support.v4.view.ViewPager 5 | import android.util.DisplayMetrics 6 | import android.view.View 7 | import android.view.ViewTreeObserver 8 | import android.view.animation.AccelerateDecelerateInterpolator 9 | import co.netguru.android.carrecognition.common.AnimationUtils 10 | 11 | 12 | fun onGlobalLayout(view: View, block: () -> Unit) { 13 | val viewTreeObserver = view.viewTreeObserver 14 | if (viewTreeObserver.isAlive) { 15 | viewTreeObserver.addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener { 16 | override fun onGlobalLayout() { 17 | block() 18 | view.viewTreeObserver.removeOnGlobalLayoutListener(this) 19 | } 20 | }) 21 | } 22 | } 23 | 24 | fun Activity.getDisplayMetrics() = DisplayMetrics().also { 25 | windowManager.defaultDisplay.getMetrics(it) 26 | } 27 | 28 | fun ViewPager.onPageSelected(onPosition: (Int) -> Unit) { 29 | addOnPageChangeListener(object : ViewPager.OnPageChangeListener { 30 | override fun onPageScrollStateChanged(state: Int) {} 31 | override fun onPageScrolled(position: Int, positionOffset: Float, 32 | positionOffsetPixels: Int) { 33 | } 34 | 35 | override fun onPageSelected(position: Int) { 36 | onPosition(position) 37 | } 38 | }) 39 | } 40 | 41 | fun View.fadeIn() { 42 | AnimationUtils.createAnimator(1f, { 43 | alpha = it 44 | }) { 45 | duration = AnimationUtils.DEFAULT_ANIMATION_LENGTH 46 | interpolator = AccelerateDecelerateInterpolator() 47 | }.start() 48 | } 49 | 50 | fun View.fadeOut() { 51 | AnimationUtils.createAnimator(1f, { 52 | alpha = 1 - it 53 | }) { 54 | duration = AnimationUtils.DEFAULT_ANIMATION_LENGTH 55 | interpolator = AccelerateDecelerateInterpolator() 56 | }.start() 57 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/co/netguru/android/carrecognition/data/SharedPreferencesController.kt: -------------------------------------------------------------------------------- 1 | package co.netguru.android.carrecognition.data 2 | 3 | import android.content.SharedPreferences 4 | import co.netguru.android.carrecognition.application.scope.AppScope 5 | import co.netguru.android.carrecognition.common.extensions.edit 6 | import javax.inject.Inject 7 | 8 | @AppScope 9 | class SharedPreferencesController @Inject constructor( 10 | private val sharedPreferences: SharedPreferences) { 11 | 12 | fun setOnboardingCompleted(isCompleted: Boolean) { 13 | sharedPreferences.edit { this.putBoolean(KEY_IS_ONBOARDING_COMPLETED, isCompleted) } 14 | } 15 | 16 | fun isOnboardingCompleted(): Boolean { 17 | return sharedPreferences.getBoolean(KEY_IS_ONBOARDING_COMPLETED, false) 18 | } 19 | 20 | companion object { 21 | private const val KEY_IS_ONBOARDING_COMPLETED = "isOnboardingCompleted" 22 | } 23 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/co/netguru/android/carrecognition/data/ar/StickerRenderable.kt: -------------------------------------------------------------------------------- 1 | package co.netguru.android.carrecognition.data.ar 2 | 3 | import android.content.Context 4 | import android.widget.ImageView 5 | import android.widget.TextView 6 | import co.netguru.android.carrecognition.R 7 | import co.netguru.android.carrecognition.common.extensions.getDrawableIdentifier 8 | import co.netguru.android.carrecognition.data.db.Cars 9 | import com.google.ar.sceneform.AnchorNode 10 | import com.google.ar.sceneform.FrameTime 11 | import com.google.ar.sceneform.math.Quaternion 12 | import com.google.ar.sceneform.math.Vector3 13 | import com.google.ar.sceneform.rendering.ViewRenderable 14 | 15 | class StickerNode( 16 | private val car: Cars, 17 | private val context: Context, 18 | private val onClickCallback: () -> Unit 19 | ) : AnchorNode() { 20 | 21 | override fun onActivate() { 22 | ViewRenderable.builder() 23 | .setView(context, R.layout.sticker).build() 24 | .thenAccept { 25 | val view = it.view 26 | view.setOnClickListener { onClickCallback() } 27 | 28 | val textView = view.findViewById(R.id.model_label) 29 | textView.text = car.model 30 | 31 | val logoImage = view.findViewById(R.id.model_maker_logo_image) 32 | logoImage.setImageResource(context.getDrawableIdentifier(car.brand_logo_image)) 33 | renderable = it 34 | } 35 | 36 | localPosition = Vector3(0f, 1f, -1.5f) 37 | localScale = Vector3(2f, 2f, 2f) 38 | } 39 | 40 | override fun onUpdate(frameTime: FrameTime?) { 41 | if (scene == null) { 42 | return 43 | } 44 | 45 | //rotate note so it always faces camera 46 | val direction = Vector3.subtract(scene.camera.worldPosition, worldPosition) 47 | worldRotation = Quaternion.lookRotation(direction, Vector3.up()) 48 | } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /app/src/main/kotlin/co/netguru/android/carrecognition/data/db/AppDatabase.kt: -------------------------------------------------------------------------------- 1 | package co.netguru.android.carrecognition.data.db 2 | 3 | import android.arch.persistence.room.Database 4 | import android.arch.persistence.room.RoomDatabase 5 | 6 | @Database(entities = [(Cars::class)], version = 1) 7 | abstract class AppDatabase : RoomDatabase() { 8 | abstract fun carDao(): CarsDao 9 | } 10 | -------------------------------------------------------------------------------- /app/src/main/kotlin/co/netguru/android/carrecognition/data/db/Cars.kt: -------------------------------------------------------------------------------- 1 | package co.netguru.android.carrecognition.data.db 2 | 3 | import android.arch.persistence.room.Entity 4 | import android.arch.persistence.room.PrimaryKey 5 | import com.google.gson.annotations.Expose 6 | 7 | @Entity(tableName = "cars") 8 | data class Cars( 9 | @PrimaryKey 10 | val id: String, 11 | val brand: String, 12 | val model: String, 13 | val description: String, 14 | val stars: Int, 15 | val acceleration_mph: Double, 16 | val speed_mph: Int, 17 | val power: Int, 18 | val engine: Int, 19 | val brand_logo_image: String, 20 | val brand_logo_image_locked: String, 21 | val image: String, 22 | val image_locked: String) { 23 | 24 | @Expose(deserialize = false, serialize = false) 25 | var seen: Boolean = false 26 | } 27 | -------------------------------------------------------------------------------- /app/src/main/kotlin/co/netguru/android/carrecognition/data/db/CarsDao.kt: -------------------------------------------------------------------------------- 1 | package co.netguru.android.carrecognition.data.db 2 | 3 | import android.arch.persistence.room.* 4 | import io.reactivex.Maybe 5 | import io.reactivex.Single 6 | 7 | @Dao 8 | interface CarsDao { 9 | @Query("SELECT * FROM cars") 10 | fun getAll(): Single> 11 | 12 | @Query("SELECT * FROM cars WHERE id LIKE :carId") 13 | fun findById(carId: String): Maybe 14 | 15 | @Insert(onConflict = OnConflictStrategy.REPLACE) 16 | fun insertAll(vararg cars: Cars) 17 | 18 | @Update 19 | fun update(vararg cars: Cars) 20 | 21 | @Delete 22 | fun delete(car: Cars) 23 | } 24 | -------------------------------------------------------------------------------- /app/src/main/kotlin/co/netguru/android/carrecognition/data/db/CarsList.kt: -------------------------------------------------------------------------------- 1 | package co.netguru.android.carrecognition.data.db 2 | 3 | import com.google.gson.annotations.SerializedName 4 | import java.util.* 5 | 6 | data class CarsList( 7 | @SerializedName("cars") 8 | val cars: Array) { 9 | 10 | override fun equals(other: Any?): Boolean { 11 | if (this === other) return true 12 | if (javaClass != other?.javaClass) return false 13 | 14 | other as CarsList 15 | 16 | if (!Arrays.equals(cars, other.cars)) return false 17 | 18 | return true 19 | } 20 | 21 | override fun hashCode(): Int { 22 | return Arrays.hashCode(cars) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /app/src/main/kotlin/co/netguru/android/carrecognition/data/db/DatabaseInitializer.kt: -------------------------------------------------------------------------------- 1 | package co.netguru.android.carrecognition.data.db 2 | 3 | import android.content.Context 4 | import co.netguru.android.carrecognition.common.extensions.applyIoSchedulers 5 | import com.google.gson.Gson 6 | import io.reactivex.Completable 7 | import io.reactivex.CompletableSource 8 | 9 | 10 | object DatabaseInitializer { 11 | 12 | fun checkIfInit(context: Context, database: AppDatabase): Completable = 13 | database.carDao().getAll().flatMapCompletable { 14 | if (it.isEmpty()) populateDatabase(context, database) 15 | else CompletableSource { it.onComplete() } 16 | }.applyIoSchedulers() 17 | 18 | private fun populateDatabase(context: Context, database: AppDatabase) = Completable.create { 19 | val jsonString = loadJSONFromCarsAsset(context) 20 | val carsList = Gson().fromJson(jsonString, CarsList::class.java) 21 | carsList.cars.forEach { database.carDao().insertAll(it) } 22 | it.onComplete() 23 | } 24 | 25 | private fun loadJSONFromCarsAsset(context: Context): String? { 26 | val inputStream = context.assets.open("cars.json") 27 | return inputStream.bufferedReader().use { it.readText() } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /app/src/main/kotlin/co/netguru/android/carrecognition/data/recognizer/Car.kt: -------------------------------------------------------------------------------- 1 | package co.netguru.android.carrecognition.data.recognizer 2 | 3 | enum class Car( 4 | val id: String 5 | ) { 6 | 7 | OTHER_CAR("OtherCar"), 8 | NOT_A_CAR("NotCar"), 9 | FORD_FIESTA("FordFiesta"), 10 | HONDA_CIVIC("HondaCivic"), 11 | NISSAN_QASHQAI("NissanQashqai"), 12 | VOLKSWAGEN_PASSAT("VolkswagenPassat"); 13 | 14 | companion object { 15 | const val TOP_SPEED_MAX = 200 16 | const val ZERO_TO_SIXTY_MIN = 2.9f 17 | const val ZERO_TO_SIXTY_MAX = 20f 18 | const val HORSEPOWER_MAX = 320 19 | const val ENGINE_MAX = 2000 20 | fun of(text: String) = valueOf(text.replace(" ", "_").toUpperCase()) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /app/src/main/kotlin/co/netguru/android/carrecognition/data/recognizer/Recognition.kt: -------------------------------------------------------------------------------- 1 | package co.netguru.android.carrecognition.data.recognizer 2 | 3 | data class Recognition(val title: Car, val confidence: Float) { 4 | override fun toString() = 5 | "$title (${(confidence * 100).toInt()}%)" 6 | } 7 | -------------------------------------------------------------------------------- /app/src/main/kotlin/co/netguru/android/carrecognition/data/recognizer/TFlowRecognizer.kt: -------------------------------------------------------------------------------- 1 | package co.netguru.android.carrecognition.data.recognizer 2 | 3 | import android.graphics.Bitmap 4 | import android.graphics.Color 5 | import co.netguru.android.carrecognition.application.TFModule 6 | import co.netguru.android.carrecognition.application.scope.AppScope 7 | import co.netguru.android.carrecognition.common.extensions.map 8 | import io.reactivex.Single 9 | import javax.inject.Inject 10 | import javax.inject.Named 11 | 12 | @AppScope 13 | class TFlowRecognizer @Inject constructor( 14 | @Named(TFModule.DETECTOR) private val detector: TFWrapper, 15 | @Named(TFModule.RECOGNIZER) private val recognizer: TFWrapper, 16 | @Named(TFModule.LABELS_BINDING) private val labels: List 17 | ) { 18 | 19 | companion object { 20 | private const val IMAGE_MEAN = 128 21 | private const val IMAGE_STD = 128f 22 | private const val IMAGE_MAX = 255f 23 | } 24 | 25 | private val grayScaleFloatValues = FloatArray(TFModule.INPUT_SIZE * TFModule.INPUT_SIZE) 26 | 27 | fun classify(bitmap: Bitmap): Single { 28 | return Single.fromCallable { 29 | prepareFrameGrayscaleValues(bitmap, TFModule.INPUT_SIZE) 30 | return@fromCallable detector 31 | .run(grayScaleFloatValues) 32 | .map { (index, confidence) -> 33 | if (index == 1) { 34 | Recognition(Car.NOT_A_CAR, confidence) 35 | } else { 36 | recognizer.run(grayScaleFloatValues) 37 | .map { 38 | Recognition(Car.of(labels[it.first]), it.second) 39 | } 40 | } 41 | } 42 | } 43 | } 44 | 45 | private fun prepareFrameGrayscaleValues(bitmap: Bitmap, inputSize: Int): FloatArray { 46 | bitmap.getPixels(inputSize * inputSize).forEachIndexed { index, intValue -> 47 | val red = Color.red(intValue) 48 | val green = Color.green(intValue) 49 | val blue = Color.blue(intValue) 50 | val grayScale = 51 | (red * 299f / 1000f + green * 587f / 1000f + blue * 114f / 1000f) / IMAGE_MAX 52 | grayScaleFloatValues[index] = grayScale 53 | } 54 | return grayScaleFloatValues 55 | } 56 | 57 | private fun Bitmap.getPixels(size: Int): IntArray { 58 | val intArray = IntArray(size) 59 | getPixels(intArray, 0, width, 0, 0, width, height) 60 | return intArray 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /app/src/main/kotlin/co/netguru/android/carrecognition/data/recognizer/Tfwrapper.kt: -------------------------------------------------------------------------------- 1 | package co.netguru.android.carrecognition.data.recognizer 2 | 3 | import org.tensorflow.contrib.android.TensorFlowInferenceInterface 4 | 5 | class TFWrapper( 6 | private val tf: TensorFlowInferenceInterface, 7 | private val inputLayerName: String, 8 | private val outputLayerName: String, 9 | private val inputSize: Int, 10 | private val outputSize: Int, 11 | private val nrOfChannels: Int 12 | ) { 13 | 14 | companion object { 15 | private const val NUMBER_OF_IMAGES = 1L 16 | } 17 | 18 | private val output = FloatArray(outputSize) 19 | 20 | fun run(colorFloatValues: FloatArray): Pair { 21 | tf.feed( 22 | inputLayerName, 23 | colorFloatValues, 24 | NUMBER_OF_IMAGES, 25 | inputSize.toLong(), 26 | inputSize.toLong(), 27 | nrOfChannels.toLong() 28 | ) 29 | tf.run(arrayOf(outputLayerName)) 30 | tf.fetch(outputLayerName, output) 31 | 32 | return output 33 | .mapIndexed { index, confidence -> Pair(index, confidence) } 34 | .sortedByDescending { it.second } 35 | .first() 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /app/src/main/kotlin/co/netguru/android/carrecognition/feature/camera/CameraActivityModule.kt: -------------------------------------------------------------------------------- 1 | package co.netguru.android.carrecognition.feature.camera 2 | 3 | import co.netguru.android.carrecognition.application.scope.ActivityScope 4 | import dagger.Binds 5 | import dagger.Module 6 | 7 | @Module 8 | abstract class CameraActivityModule { 9 | 10 | @Binds 11 | @ActivityScope 12 | abstract fun bindPresenter(presenter: CameraPresenter): CameraContract.Presenter 13 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/co/netguru/android/carrecognition/feature/camera/CameraContract.kt: -------------------------------------------------------------------------------- 1 | package co.netguru.android.carrecognition.feature.camera 2 | 3 | import android.media.Image 4 | import co.netguru.android.carrecognition.data.db.Cars 5 | import co.netguru.android.carrecognition.data.recognizer.Recognition 6 | import com.google.ar.core.Anchor 7 | import com.google.ar.core.HitResult 8 | import com.hannesdorfmann.mosby3.mvp.MvpPresenter 9 | import com.hannesdorfmann.mosby3.mvp.MvpView 10 | 11 | 12 | interface CameraContract { 13 | interface View : MvpView { 14 | fun createAnchor(hitPoint: HitResult, car: Cars): Anchor 15 | fun acquireFrame(): Image? 16 | fun updateViewFinder(viewfinderSize: Float) 17 | fun showViewFinder() 18 | fun hideViewFinder() 19 | fun frameStreamEnabled(enabled: Boolean) 20 | fun showDetails(car: Cars) 21 | fun tryAttachPin(randomFieldPercentage: Int) 22 | fun updateRecognitionIndicatorLabel(status: CameraPresenter.RecognitionLabel) 23 | fun showCouldNotAttachPinError() 24 | fun showRecognitionUi() 25 | fun showPermissionUi() 26 | fun showExplorationMode() 27 | fun showDebugResult(result: Recognition) 28 | } 29 | interface Presenter: MvpPresenter { 30 | fun processHitResult(hitPoint: HitResult?) 31 | fun frameUpdated() 32 | fun bottomSheetHidden() 33 | fun onPermissionGranted() 34 | fun onPermissionDeclined() 35 | fun onCloseRecognitionClicked() 36 | fun onScanButtonClicked() 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /app/src/main/kotlin/co/netguru/android/carrecognition/feature/camera/RecognitionTextSwitcher.kt: -------------------------------------------------------------------------------- 1 | package co.netguru.android.carrecognition.feature.camera 2 | 3 | import android.content.Context 4 | import android.support.v7.view.ContextThemeWrapper 5 | import android.util.AttributeSet 6 | import android.widget.TextSwitcher 7 | import android.widget.TextView 8 | import co.netguru.android.carrecognition.R 9 | 10 | class RecognitionTextSwitcher(context: Context?, attrs: AttributeSet?) : TextSwitcher(context, attrs) { 11 | 12 | init { 13 | setupTextView() 14 | setupAnimations() 15 | setupInitialText(attrs) 16 | } 17 | 18 | private fun setupInitialText(attrs: AttributeSet?) { 19 | val arr = context.obtainStyledAttributes(attrs, R.styleable.RecognitionTextSwitcher) 20 | setText(arr.getString(R.styleable.RecognitionTextSwitcher_initText)) 21 | arr.recycle() 22 | } 23 | 24 | private fun setupTextView() { 25 | setFactory { TextView(ContextThemeWrapper(context, R.style.RecognitionTextViewStyle), null, 0) } 26 | } 27 | 28 | private fun setupAnimations() { 29 | val animationDuration = resources.getInteger(R.integer.recognition_animation_duration).toLong() 30 | val inAnim = android.view.animation.AnimationUtils.loadAnimation(context, R.anim.car_label_in_animation) 31 | val outAnim = android.view.animation.AnimationUtils.loadAnimation(context, R.anim.car_label_out_animation) 32 | inAnim.duration = animationDuration 33 | outAnim.duration = animationDuration 34 | inAnimation = inAnim 35 | outAnimation = outAnim 36 | } 37 | 38 | fun getText() = (currentView as? TextView)?.text.toString() 39 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/co/netguru/android/carrecognition/feature/cars/CarListContract.kt: -------------------------------------------------------------------------------- 1 | package co.netguru.android.carrecognition.feature.cars 2 | 3 | import co.netguru.android.carrecognition.data.db.Cars 4 | import com.hannesdorfmann.mosby3.mvp.MvpPresenter 5 | import com.hannesdorfmann.mosby3.mvp.MvpView 6 | 7 | interface CarListContract { 8 | interface View : MvpView { 9 | fun showLoading(show: Boolean = true) 10 | fun populate(cars: List) 11 | fun onError(throwable: Throwable) 12 | 13 | } 14 | interface Presenter : MvpPresenter { 15 | fun getCars() 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /app/src/main/kotlin/co/netguru/android/carrecognition/feature/cars/CarListModule.kt: -------------------------------------------------------------------------------- 1 | package co.netguru.android.carrecognition.feature.cars 2 | 3 | import co.netguru.android.carrecognition.application.scope.ActivityScope 4 | import dagger.Binds 5 | import dagger.Module 6 | 7 | @Module 8 | abstract class CarListModule { 9 | @Binds 10 | @ActivityScope 11 | abstract fun bindPresenter(presenter: CarListPresenter): CarListContract.Presenter 12 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/co/netguru/android/carrecognition/feature/cars/CarListPageTransformer.kt: -------------------------------------------------------------------------------- 1 | package co.netguru.android.carrecognition.feature.cars 2 | 3 | import android.support.v4.view.ViewPager 4 | import android.view.View 5 | import kotlinx.android.synthetic.main.car_list_item_view.view.* 6 | 7 | class CarListPageTransformer : ViewPager.PageTransformer { 8 | 9 | private val CARD_INITIAL_OFFSET = 0.9f 10 | private var counter = 0 11 | 12 | override fun transformPage(page: View, position: Float) { 13 | var pos = position 14 | if (counter == 1) { 15 | pos = CARD_INITIAL_OFFSET 16 | } 17 | if (counter <= 2) counter++ 18 | if ((pos in -1..1).not()) return 19 | val multiplier = Math.abs(pos) * -1 20 | page.bottomContainer.post { 21 | val delta = page.height - page.card.height 22 | page.bottomContainer.translationY = delta * multiplier 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /app/src/main/kotlin/co/netguru/android/carrecognition/feature/cars/CarListPresenter.kt: -------------------------------------------------------------------------------- 1 | package co.netguru.android.carrecognition.feature.cars 2 | 3 | import co.netguru.android.carrecognition.application.scope.ActivityScope 4 | import co.netguru.android.carrecognition.common.extensions.applyIoSchedulers 5 | import co.netguru.android.carrecognition.data.db.AppDatabase 6 | import com.hannesdorfmann.mosby3.mvp.MvpBasePresenter 7 | import io.reactivex.disposables.Disposable 8 | import io.reactivex.rxkotlin.subscribeBy 9 | import javax.inject.Inject 10 | 11 | @ActivityScope 12 | class CarListPresenter @Inject constructor(private val database: AppDatabase) 13 | : MvpBasePresenter(), CarListContract.Presenter { 14 | 15 | private var disposable: Disposable? = null 16 | 17 | override fun getCars() { 18 | ifViewAttached { view -> 19 | view.showLoading() 20 | disposable = database.carDao().getAll() 21 | .applyIoSchedulers() 22 | .subscribeBy( 23 | onSuccess = { 24 | view.populate(it) 25 | }, 26 | onError = { view.onError(it) } 27 | ) 28 | } 29 | } 30 | 31 | override fun destroy() { 32 | super.destroy() 33 | if (disposable?.isDisposed != false) return 34 | disposable?.dispose() 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /app/src/main/kotlin/co/netguru/android/carrecognition/feature/cars/CircleProgressView.kt: -------------------------------------------------------------------------------- 1 | package co.netguru.android.carrecognition.feature.cars 2 | 3 | import android.content.Context 4 | import android.util.AttributeSet 5 | import android.util.TypedValue 6 | import android.view.LayoutInflater 7 | import android.widget.FrameLayout 8 | import co.netguru.android.carrecognition.R 9 | import kotlinx.android.synthetic.main.circle_progress_view.view.* 10 | 11 | class CircleProgressView : FrameLayout { 12 | private val view = LayoutInflater.from(context).inflate(R.layout.circle_progress_view, this) 13 | 14 | constructor(context: Context) : super(context) 15 | 16 | constructor(context: Context, attrs: AttributeSet) : super(context, attrs) { 17 | applyAttributes(context, attrs) 18 | } 19 | 20 | constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super( 21 | context, 22 | attrs, 23 | defStyleAttr 24 | ) { 25 | applyAttributes(context, attrs) 26 | } 27 | 28 | constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int, defStyleRes: Int) : super( 29 | context, 30 | attrs, 31 | defStyleAttr, 32 | defStyleRes 33 | ) { 34 | applyAttributes(context, attrs) 35 | } 36 | 37 | private fun applyAttributes(context: Context, attrs: AttributeSet) { 38 | val typedArray = context.obtainStyledAttributes(attrs, R.styleable.CircleProgress) 39 | val label = typedArray.getString(R.styleable.CircleProgress_circleLabel) 40 | val valueTextSize = typedArray.getDimension(R.styleable.CircleProgress_circleValueTextSize, context.resources.getDimension(R.dimen.car_details_progress_value_text_size)) 41 | val progressSize = typedArray.getDimension(R.styleable.CircleProgress_circleProgressSize, context.resources.getDimension(R.dimen.bottom_sheet_gradient_progress_size)) 42 | val valueBottomMargin = typedArray.getDimension(R.styleable.CircleProgress_circleValueBottomMargin, 0f) 43 | val labelBottomMargin = typedArray.getDimension(R.styleable.CircleProgress_circleLabelBottomMargin, 0f) 44 | val progressBottomPadding = typedArray.getDimension(R.styleable.CircleProgress_circleProgressBottomPadding, 0f) 45 | typedArray.recycle() 46 | 47 | view.label.text = label 48 | (view.label.layoutParams as MarginLayoutParams).bottomMargin = labelBottomMargin.toInt() 49 | 50 | view.value.setTextSize(TypedValue.COMPLEX_UNIT_PX, valueTextSize) 51 | (view.value.layoutParams as MarginLayoutParams).bottomMargin = valueBottomMargin.toInt() 52 | 53 | view.bar.layoutParams.height = progressSize.toInt() 54 | view.bar.layoutParams.width = progressSize.toInt() 55 | with(view.bar) { 56 | setPadding(paddingLeft, paddingTop, paddingRight, progressBottomPadding.toInt()) 57 | } 58 | } 59 | 60 | 61 | fun setAsUnseen() { 62 | view.value.setText(R.string.questionMark) 63 | view.value.setTextColor(context.getColor(R.color.car_list_item_background)) 64 | view.bar.progress = 0f 65 | } 66 | 67 | fun setProgress(progress: Float) { 68 | view.bar.progress = progress 69 | } 70 | 71 | fun setValue(text: String) { 72 | view.value.text = text 73 | } 74 | 75 | fun setLabel(text: String) { 76 | view.label.text = text 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /app/src/main/kotlin/co/netguru/android/carrecognition/feature/cars/HorizontalProgressView.kt: -------------------------------------------------------------------------------- 1 | package co.netguru.android.carrecognition.feature.cars 2 | 3 | import android.content.Context 4 | import android.support.annotation.StringRes 5 | import android.util.AttributeSet 6 | import android.util.TypedValue 7 | import android.view.LayoutInflater 8 | import android.widget.FrameLayout 9 | import co.netguru.android.carrecognition.R 10 | import kotlinx.android.synthetic.main.horizontal_progress_view.view.* 11 | 12 | class HorizontalProgressView : FrameLayout { 13 | 14 | private val view = LayoutInflater.from(context).inflate(R.layout.horizontal_progress_view, this) 15 | 16 | constructor(context: Context) : super(context) 17 | 18 | constructor(context: Context, attrs: AttributeSet) : super(context, attrs) { 19 | applyAttributes(context, attrs) 20 | } 21 | 22 | constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super( 23 | context, 24 | attrs, 25 | defStyleAttr 26 | ) { 27 | applyAttributes(context, attrs) 28 | } 29 | 30 | constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int, defStyleRes: Int) : super( 31 | context, 32 | attrs, 33 | defStyleAttr, 34 | defStyleRes 35 | ) { 36 | applyAttributes(context, attrs) 37 | } 38 | 39 | private fun applyAttributes(context: Context, attrs: AttributeSet) { 40 | clipChildren = false 41 | clipToPadding = false 42 | val typedArray = context.obtainStyledAttributes(attrs, R.styleable.HorizontalProgress) 43 | val labelTextSize = typedArray.getDimension(R.styleable.HorizontalProgress_horizontalLabelTextSize, context.resources.getDimension(R.dimen.car_details_progress_label_text_size)) 44 | val label = typedArray.getString(R.styleable.HorizontalProgress_horizontalLabel) 45 | val valueTextSize = typedArray.getDimension(R.styleable.HorizontalProgress_horizontalValueTextSize, context.resources.getDimension(R.dimen.car_details_progress_value_text_size)) 46 | val progressHeight = typedArray.getDimension(R.styleable.HorizontalProgress_horizontalProgressHeight, context.resources.getDimension(R.dimen.car_details_progress_height)) 47 | val progressWidth = typedArray.getDimension(R.styleable.HorizontalProgress_horizontalProgressWidth, 0f) 48 | val progressMargin = typedArray.getDimension(R.styleable.HorizontalProgress_horizontalProgressMargin, 0f) 49 | typedArray.recycle() 50 | 51 | view.label.setTextSize(TypedValue.COMPLEX_UNIT_PX, labelTextSize) 52 | view.label.text = label 53 | 54 | view.value.setTextSize(TypedValue.COMPLEX_UNIT_PX, valueTextSize) 55 | 56 | view.bar.layoutParams.height = progressHeight.toInt() 57 | 58 | view.bar.layoutParams.width = progressWidth.toInt() 59 | 60 | (view.value.layoutParams as MarginLayoutParams).bottomMargin = progressMargin.toInt() 61 | } 62 | 63 | fun setAsUnseen() { 64 | view.value.setText(R.string.questionMark) 65 | view.value.setTextColor(context.getColor(R.color.car_list_item_background)) 66 | view.bar.progress = 0f 67 | } 68 | 69 | fun setProgress(progress: Float) { 70 | view.bar.progress = progress 71 | } 72 | 73 | fun setValue(@StringRes format: Int, value: Int) { 74 | view.value.text = context.getString(format, value) 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /app/src/main/kotlin/co/netguru/android/carrecognition/feature/onboarding/OnboardingActivity.kt: -------------------------------------------------------------------------------- 1 | package co.netguru.android.carrecognition.feature.onboarding 2 | 3 | import android.os.Bundle 4 | import android.support.v7.app.AppCompatActivity 5 | import co.netguru.android.carrecognition.R 6 | import co.netguru.android.carrecognition.common.extensions.replaceFragment 7 | 8 | 9 | class OnboardingActivity : AppCompatActivity() { 10 | 11 | override fun onCreate(savedInstanceState: Bundle?) { 12 | super.onCreate(savedInstanceState) 13 | setContentView(R.layout.base_activity) 14 | if (savedInstanceState == null) { 15 | replaceFragment(R.id.fragment_container, OnboardingFragment.newInstance(), 16 | OnboardingFragment.TAG) 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /app/src/main/kotlin/co/netguru/android/carrecognition/feature/onboarding/OnboardingAdapter.kt: -------------------------------------------------------------------------------- 1 | package co.netguru.android.carrecognition.feature.onboarding 2 | 3 | import android.support.v4.app.Fragment 4 | import android.support.v4.app.FragmentManager 5 | import android.support.v4.app.FragmentPagerAdapter 6 | import android.util.SparseArray 7 | import android.view.ViewGroup 8 | 9 | class OnboardingAdapter(fragmentManager: FragmentManager) : FragmentPagerAdapter(fragmentManager) { 10 | 11 | private var progressPhotoFragments: SparseArray 12 | 13 | init { 14 | progressPhotoFragments = SparseArray(PAGE_COUNT) 15 | } 16 | 17 | override fun getItem(position: Int): Fragment { 18 | return when (position) { 19 | 0 -> OnboardingRecognizeFragment.newInstance() 20 | 1 -> OnboardingDiscoverFragment.newInstance() 21 | 2 -> OnboardingStayUpdatedFragment.newInstance() 22 | else -> OnboardingRecognizeFragment.newInstance() 23 | } 24 | } 25 | 26 | override fun getCount(): Int { 27 | return PAGE_COUNT 28 | } 29 | 30 | override fun instantiateItem(container: ViewGroup, position: Int): Any { 31 | val fragment = super.instantiateItem(container, position) as Fragment 32 | progressPhotoFragments.put(position, fragment) 33 | return fragment 34 | } 35 | 36 | override fun destroyItem(container: ViewGroup, position: Int, `object`: Any) { 37 | super.destroyItem(container, position, `object`) 38 | progressPhotoFragments.remove(position) 39 | } 40 | 41 | companion object { 42 | const val PAGE_COUNT = 3 43 | } 44 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/co/netguru/android/carrecognition/feature/onboarding/OnboardingDiscoverFragment.kt: -------------------------------------------------------------------------------- 1 | package co.netguru.android.carrecognition.feature.onboarding 2 | 3 | import android.net.Uri 4 | import android.os.Bundle 5 | import android.view.View 6 | import co.netguru.android.carrecognition.R 7 | import kotlinx.android.synthetic.main.onboarding_inside_fragment.* 8 | 9 | class OnboardingDiscoverFragment : PageFragment() { 10 | 11 | override fun getResourceUri(): Uri = 12 | Uri.parse("android.resource://${context?.packageName}/${R.raw.onboarding_2}") 13 | 14 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 15 | super.onViewCreated(view, savedInstanceState) 16 | titleTxt.setText(R.string.discover_cars) 17 | descriptionTxt.setText(R.string.search_and_unlock) 18 | } 19 | 20 | companion object { 21 | val TAG = OnboardingDiscoverFragment::class.java.simpleName!! 22 | 23 | fun newInstance(): OnboardingDiscoverFragment { 24 | return OnboardingDiscoverFragment() 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /app/src/main/kotlin/co/netguru/android/carrecognition/feature/onboarding/OnboardingFragment.kt: -------------------------------------------------------------------------------- 1 | package co.netguru.android.carrecognition.feature.onboarding 2 | 3 | import android.content.Context 4 | import android.os.Bundle 5 | import android.support.v4.app.Fragment 6 | import android.support.v4.view.ViewPager 7 | import android.view.LayoutInflater 8 | import android.view.View 9 | import android.view.ViewGroup 10 | import co.netguru.android.carrecognition.R 11 | import co.netguru.android.carrecognition.data.SharedPreferencesController 12 | import co.netguru.android.carrecognition.feature.camera.CameraActivity 13 | import dagger.android.support.AndroidSupportInjection 14 | import kotlinx.android.synthetic.main.onboarding_fragment.* 15 | import org.jetbrains.anko.support.v4.startActivity 16 | import javax.inject.Inject 17 | 18 | class OnboardingFragment : Fragment() { 19 | 20 | @Inject 21 | lateinit var sharedPreferencesController: SharedPreferencesController 22 | 23 | override fun onAttach(context: Context?) { 24 | super.onAttach(context) 25 | AndroidSupportInjection.inject(this) 26 | } 27 | 28 | override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, 29 | savedInstanceState: Bundle?): View? { 30 | return inflater.inflate(R.layout.onboarding_fragment, container, false) 31 | } 32 | 33 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 34 | super.onViewCreated(view, savedInstanceState) 35 | setupViewPager() 36 | } 37 | 38 | private fun setupViewPager() { 39 | viewPager.adapter = OnboardingAdapter(childFragmentManager) 40 | viewPager.offscreenPageLimit = 3 41 | nextImg.setOnClickListener { viewPager.currentItem = 1 } 42 | viewPagerIndicator.setViewPager(viewPager) 43 | 44 | viewPager.addOnPageChangeListener(object : ViewPager.OnPageChangeListener { 45 | override fun onPageScrollStateChanged(state: Int) { 46 | //no-op 47 | } 48 | 49 | override fun onPageScrolled(position: Int, positionOffset: Float, 50 | positionOffsetPixels: Int) { 51 | //no-op 52 | } 53 | 54 | override fun onPageSelected(position: Int) { 55 | when (position) { 56 | 0 -> { 57 | nextImg.setImageResource(R.drawable.next_button_background) 58 | nextImg.setOnClickListener { viewPager.currentItem = 1 } 59 | } 60 | 1 -> { 61 | nextImg.setImageResource(R.drawable.next_button_background) 62 | nextImg.setOnClickListener { viewPager.currentItem = 2 } 63 | } 64 | 2 -> { 65 | nextImg.setImageResource(R.drawable.scan_button_background) 66 | nextImg.setOnClickListener { 67 | sharedPreferencesController.setOnboardingCompleted(true) 68 | startActivity() 69 | activity?.finish() 70 | } 71 | } 72 | } 73 | } 74 | }) 75 | } 76 | 77 | companion object { 78 | val TAG = OnboardingFragment::class.java.simpleName!! 79 | 80 | fun newInstance(): OnboardingFragment { 81 | return OnboardingFragment() 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /app/src/main/kotlin/co/netguru/android/carrecognition/feature/onboarding/OnboardingRecognizeFragment.kt: -------------------------------------------------------------------------------- 1 | package co.netguru.android.carrecognition.feature.onboarding 2 | 3 | import android.net.Uri 4 | import android.os.Bundle 5 | import android.view.View 6 | import co.netguru.android.carrecognition.R 7 | import kotlinx.android.synthetic.main.onboarding_inside_fragment.* 8 | 9 | class OnboardingRecognizeFragment : PageFragment() { 10 | 11 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 12 | super.onViewCreated(view, savedInstanceState) 13 | titleTxt.setText(R.string.recognize_cars) 14 | descriptionTxt.setText(R.string.point_the_camera) 15 | } 16 | 17 | override fun getResourceUri(): Uri = 18 | Uri.parse("android.resource://${context?.packageName}/${R.raw.onboarding_1}") 19 | 20 | companion object { 21 | val TAG = OnboardingRecognizeFragment::class.java.simpleName!! 22 | 23 | fun newInstance(): OnboardingRecognizeFragment { 24 | return OnboardingRecognizeFragment() 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /app/src/main/kotlin/co/netguru/android/carrecognition/feature/onboarding/OnboardingStayUpdatedFragment.kt: -------------------------------------------------------------------------------- 1 | package co.netguru.android.carrecognition.feature.onboarding 2 | 3 | import android.net.Uri 4 | import android.os.Bundle 5 | import android.view.View 6 | import co.netguru.android.carrecognition.R 7 | import kotlinx.android.synthetic.main.onboarding_inside_fragment.* 8 | 9 | class OnboardingStayUpdatedFragment : PageFragment() { 10 | 11 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 12 | super.onViewCreated(view, savedInstanceState) 13 | titleTxt.setText(R.string.stay_updated) 14 | descriptionTxt.setText(R.string.expect_updates) 15 | } 16 | 17 | override fun getResourceUri(): Uri = 18 | Uri.parse("android.resource://${context?.packageName}/${R.raw.onboarding_3}") 19 | 20 | companion object { 21 | val TAG = OnboardingStayUpdatedFragment::class.java.simpleName!! 22 | 23 | fun newInstance(): OnboardingStayUpdatedFragment { 24 | return OnboardingStayUpdatedFragment() 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /app/src/main/kotlin/co/netguru/android/carrecognition/feature/onboarding/PageFragment.kt: -------------------------------------------------------------------------------- 1 | package co.netguru.android.carrecognition.feature.onboarding 2 | 3 | import android.net.Uri 4 | import android.os.Bundle 5 | import android.support.v4.app.Fragment 6 | import android.view.LayoutInflater 7 | import android.view.View 8 | import android.view.ViewGroup 9 | import co.netguru.android.carrecognition.R 10 | import kotlinx.android.synthetic.main.onboarding_inside_fragment.* 11 | 12 | abstract class PageFragment : Fragment() { 13 | 14 | private var fragmentResume = false 15 | private var fragmentVisible = false 16 | private var fragmentOnCreated = false 17 | 18 | abstract fun getResourceUri(): Uri 19 | 20 | override fun onCreateView( 21 | inflater: LayoutInflater, 22 | container: ViewGroup?, 23 | savedInstanceState: Bundle? 24 | ): View? { 25 | return inflater.inflate(R.layout.onboarding_inside_fragment, container, false) 26 | } 27 | 28 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 29 | super.onViewCreated(view, savedInstanceState) 30 | 31 | if (!fragmentResume && fragmentVisible) { //only when first time fragment is created 32 | updatePlayer() 33 | } 34 | 35 | carImg.setVideoURI(getResourceUri()) 36 | carImg.seekTo(1) 37 | } 38 | 39 | override fun setUserVisibleHint(visible: Boolean) { 40 | super.setUserVisibleHint(visible) 41 | if (visible && isResumed) { // only at fragment screen is resumed 42 | fragmentResume = true 43 | fragmentVisible = false 44 | fragmentOnCreated = true 45 | updatePlayer() 46 | } else if (visible) { // only at fragment onCreated 47 | fragmentResume = false 48 | fragmentVisible = true 49 | fragmentOnCreated = true 50 | } else if (!visible && fragmentOnCreated) {// only when you go out of fragment screen 51 | fragmentVisible = false 52 | fragmentResume = false 53 | rewind() 54 | } 55 | } 56 | 57 | private fun rewind() { 58 | carImg.pause() 59 | carImg.seekTo(1) 60 | } 61 | 62 | private fun updatePlayer() { 63 | carImg.requestFocus() 64 | carImg.start() 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /app/src/main/kotlin/co/netguru/android/carrecognition/feature/splash/SplashActivity.kt: -------------------------------------------------------------------------------- 1 | package co.netguru.android.carrecognition.feature.splash 2 | 3 | import android.os.Bundle 4 | import android.support.v7.app.AppCompatActivity 5 | import co.netguru.android.carrecognition.common.extensions.startActivity 6 | import co.netguru.android.carrecognition.data.SharedPreferencesController 7 | import co.netguru.android.carrecognition.data.db.AppDatabase 8 | import co.netguru.android.carrecognition.data.db.DatabaseInitializer 9 | import co.netguru.android.carrecognition.feature.camera.CameraActivity 10 | import co.netguru.android.carrecognition.feature.onboarding.OnboardingActivity 11 | import dagger.android.AndroidInjection 12 | import javax.inject.Inject 13 | 14 | class SplashActivity : AppCompatActivity() { 15 | 16 | @Inject 17 | lateinit var database: AppDatabase 18 | @Inject 19 | lateinit var sharedPreferencesController: SharedPreferencesController 20 | 21 | override fun onCreate(savedInstanceState: Bundle?) { 22 | AndroidInjection.inject(this) 23 | super.onCreate(savedInstanceState) 24 | DatabaseInitializer.checkIfInit(this, database) 25 | .subscribe { 26 | if (!sharedPreferencesController.isOnboardingCompleted()) { 27 | startActivity() 28 | } else { 29 | startActivity() 30 | } 31 | finish() 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /app/src/main/res/anim/car_label_in_animation.xml: -------------------------------------------------------------------------------- 1 | 3 | 7 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/anim/car_label_out_animation.xml: -------------------------------------------------------------------------------- 1 | 3 | 7 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/color/permission_button_color_selector.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/img_blue_car.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netguru/CarLens-Android/321fa52c5cc0a339b27737b626163f8f72348cd9/app/src/main/res/drawable-hdpi/img_blue_car.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/img_next.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netguru/CarLens-Android/321fa52c5cc0a339b27737b626163f8f72348cd9/app/src/main/res/drawable-hdpi/img_next.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/img_scan.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netguru/CarLens-Android/321fa52c5cc0a339b27737b626163f8f72348cd9/app/src/main/res/drawable-hdpi/img_scan.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/img_blue_car.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netguru/CarLens-Android/321fa52c5cc0a339b27737b626163f8f72348cd9/app/src/main/res/drawable-mdpi/img_blue_car.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/img_next.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netguru/CarLens-Android/321fa52c5cc0a339b27737b626163f8f72348cd9/app/src/main/res/drawable-mdpi/img_next.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/img_scan.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netguru/CarLens-Android/321fa52c5cc0a339b27737b626163f8f72348cd9/app/src/main/res/drawable-mdpi/img_scan.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-nodpi/car_list_button_background_translucent.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-nodpi/close_recognition_button_background_translucent.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-nodpi/ford.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netguru/CarLens-Android/321fa52c5cc0a339b27737b626163f8f72348cd9/app/src/main/res/drawable-nodpi/ford.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-nodpi/ford_locked.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netguru/CarLens-Android/321fa52c5cc0a339b27737b626163f8f72348cd9/app/src/main/res/drawable-nodpi/ford_locked.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-nodpi/fordfiesta.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netguru/CarLens-Android/321fa52c5cc0a339b27737b626163f8f72348cd9/app/src/main/res/drawable-nodpi/fordfiesta.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-nodpi/fordfiesta_locked.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netguru/CarLens-Android/321fa52c5cc0a339b27737b626163f8f72348cd9/app/src/main/res/drawable-nodpi/fordfiesta_locked.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-nodpi/honda.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netguru/CarLens-Android/321fa52c5cc0a339b27737b626163f8f72348cd9/app/src/main/res/drawable-nodpi/honda.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-nodpi/honda_locked.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netguru/CarLens-Android/321fa52c5cc0a339b27737b626163f8f72348cd9/app/src/main/res/drawable-nodpi/honda_locked.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-nodpi/hondacivic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netguru/CarLens-Android/321fa52c5cc0a339b27737b626163f8f72348cd9/app/src/main/res/drawable-nodpi/hondacivic.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-nodpi/hondacivic_locked.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netguru/CarLens-Android/321fa52c5cc0a339b27737b626163f8f72348cd9/app/src/main/res/drawable-nodpi/hondacivic_locked.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-nodpi/nissan.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netguru/CarLens-Android/321fa52c5cc0a339b27737b626163f8f72348cd9/app/src/main/res/drawable-nodpi/nissan.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-nodpi/nissan_locked.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netguru/CarLens-Android/321fa52c5cc0a339b27737b626163f8f72348cd9/app/src/main/res/drawable-nodpi/nissan_locked.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-nodpi/nissanqashqai.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netguru/CarLens-Android/321fa52c5cc0a339b27737b626163f8f72348cd9/app/src/main/res/drawable-nodpi/nissanqashqai.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-nodpi/nissanqashqai_locked.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netguru/CarLens-Android/321fa52c5cc0a339b27737b626163f8f72348cd9/app/src/main/res/drawable-nodpi/nissanqashqai_locked.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-nodpi/toyota.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netguru/CarLens-Android/321fa52c5cc0a339b27737b626163f8f72348cd9/app/src/main/res/drawable-nodpi/toyota.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-nodpi/toyota_locked.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netguru/CarLens-Android/321fa52c5cc0a339b27737b626163f8f72348cd9/app/src/main/res/drawable-nodpi/toyota_locked.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-nodpi/toyotacamry.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netguru/CarLens-Android/321fa52c5cc0a339b27737b626163f8f72348cd9/app/src/main/res/drawable-nodpi/toyotacamry.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-nodpi/toyotacamry_locked.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netguru/CarLens-Android/321fa52c5cc0a339b27737b626163f8f72348cd9/app/src/main/res/drawable-nodpi/toyotacamry_locked.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-nodpi/toyotacorolla.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netguru/CarLens-Android/321fa52c5cc0a339b27737b626163f8f72348cd9/app/src/main/res/drawable-nodpi/toyotacorolla.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-nodpi/toyotacorolla_locked.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netguru/CarLens-Android/321fa52c5cc0a339b27737b626163f8f72348cd9/app/src/main/res/drawable-nodpi/toyotacorolla_locked.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-nodpi/volkswagen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netguru/CarLens-Android/321fa52c5cc0a339b27737b626163f8f72348cd9/app/src/main/res/drawable-nodpi/volkswagen.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-nodpi/volkswagen_locked.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netguru/CarLens-Android/321fa52c5cc0a339b27737b626163f8f72348cd9/app/src/main/res/drawable-nodpi/volkswagen_locked.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-nodpi/volkswagengolf.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netguru/CarLens-Android/321fa52c5cc0a339b27737b626163f8f72348cd9/app/src/main/res/drawable-nodpi/volkswagengolf.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-nodpi/volkswagengolf_locked.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netguru/CarLens-Android/321fa52c5cc0a339b27737b626163f8f72348cd9/app/src/main/res/drawable-nodpi/volkswagengolf_locked.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-nodpi/volkswagenpassat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netguru/CarLens-Android/321fa52c5cc0a339b27737b626163f8f72348cd9/app/src/main/res/drawable-nodpi/volkswagenpassat.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-nodpi/volkswagenpassat_locked.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netguru/CarLens-Android/321fa52c5cc0a339b27737b626163f8f72348cd9/app/src/main/res/drawable-nodpi/volkswagenpassat_locked.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-nodpi/volkswagentiguan.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netguru/CarLens-Android/321fa52c5cc0a339b27737b626163f8f72348cd9/app/src/main/res/drawable-nodpi/volkswagentiguan.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-nodpi/volkswagentiguan_locked.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netguru/CarLens-Android/321fa52c5cc0a339b27737b626163f8f72348cd9/app/src/main/res/drawable-nodpi/volkswagentiguan_locked.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-nodpi/volskwagen_locked.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netguru/CarLens-Android/321fa52c5cc0a339b27737b626163f8f72348cd9/app/src/main/res/drawable-nodpi/volskwagen_locked.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/img_blue_car.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netguru/CarLens-Android/321fa52c5cc0a339b27737b626163f8f72348cd9/app/src/main/res/drawable-xhdpi/img_blue_car.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/img_next.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netguru/CarLens-Android/321fa52c5cc0a339b27737b626163f8f72348cd9/app/src/main/res/drawable-xhdpi/img_next.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/img_scan.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netguru/CarLens-Android/321fa52c5cc0a339b27737b626163f8f72348cd9/app/src/main/res/drawable-xhdpi/img_scan.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/img_blue_car.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netguru/CarLens-Android/321fa52c5cc0a339b27737b626163f8f72348cd9/app/src/main/res/drawable-xxhdpi/img_blue_car.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/img_next.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netguru/CarLens-Android/321fa52c5cc0a339b27737b626163f8f72348cd9/app/src/main/res/drawable-xxhdpi/img_next.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/img_scan.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netguru/CarLens-Android/321fa52c5cc0a339b27737b626163f8f72348cd9/app/src/main/res/drawable-xxhdpi/img_scan.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/img_blue_car.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netguru/CarLens-Android/321fa52c5cc0a339b27737b626163f8f72348cd9/app/src/main/res/drawable-xxxhdpi/img_blue_car.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/img_next.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netguru/CarLens-Android/321fa52c5cc0a339b27737b626163f8f72348cd9/app/src/main/res/drawable-xxxhdpi/img_next.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/img_scan.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netguru/CarLens-Android/321fa52c5cc0a339b27737b626163f8f72348cd9/app/src/main/res/drawable-xxxhdpi/img_scan.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/camera_activity_bottom_sheet_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/car_list_button.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 13 | 15 | 20 | 25 | 28 | 31 | 34 | 37 | 40 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/car_list_button_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/car_list_button_translucent.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 11 | 14 | 16 | 21 | 26 | 29 | 32 | 35 | 38 | 41 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/circular_progress.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/circular_progress_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/close_recognition_button_translucent.xml: -------------------------------------------------------------------------------- 1 | 6 | 13 | 21 | 29 | 30 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/dot_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/extend_animator.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/google_button.xml: -------------------------------------------------------------------------------- 1 | 6 | 13 | 19 | 27 | 28 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/google_button_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_arrow.xml: -------------------------------------------------------------------------------- 1 | 6 | 13 | 20 | 21 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/next_button_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/onboarding_progress_circle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/permission_camera_image.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 11 | 14 | 20 | 26 | 32 | 38 | 41 | 45 | 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/pin.xml: -------------------------------------------------------------------------------- 1 | 4 | 7 | 11 | 15 | 19 | 23 | 27 | 31 | 34 | 35 | 37 | 38 | 39 | 40 | 41 | 42 | 46 | 47 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/pin_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/rounded_bar.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 8 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/scan_button_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/splash_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | 10 | 11 | 12 | 15 | 16 | 20 | 21 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/white_google_button.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 13 | 19 | 20 | -------------------------------------------------------------------------------- /app/src/main/res/font/blokk.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netguru/CarLens-Android/321fa52c5cc0a339b27737b626163f8f72348cd9/app/src/main/res/font/blokk.ttf -------------------------------------------------------------------------------- /app/src/main/res/font/gligoth.TTF: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netguru/CarLens-Android/321fa52c5cc0a339b27737b626163f8f72348cd9/app/src/main/res/font/gligoth.TTF -------------------------------------------------------------------------------- /app/src/main/res/layout/base_activity.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/layout/camera_activity_bottom_sheet.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/layout/camera_activity_container.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | 12 | 13 | 14 | 18 | 19 | 20 | 23 | 24 | -------------------------------------------------------------------------------- /app/src/main/res/layout/camera_activity_permission.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 15 | 16 | 23 | 24 | 35 | 36 | 52 | 53 | 68 | 69 | -------------------------------------------------------------------------------- /app/src/main/res/layout/car_list_view.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 13 | 14 | 27 | 28 | 41 | 42 | 50 | 51 | 59 | 60 | 71 | 72 | 81 | 82 | 83 | 84 | 85 | -------------------------------------------------------------------------------- /app/src/main/res/layout/circle_progress_bar_with_label.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 19 | 20 | 31 | 32 | -------------------------------------------------------------------------------- /app/src/main/res/layout/circle_progress_view.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 14 | 15 | 26 | 27 | 38 | 39 | -------------------------------------------------------------------------------- /app/src/main/res/layout/dot_layout.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | 10 | 16 | 17 | -------------------------------------------------------------------------------- /app/src/main/res/layout/horizontal_progress_view.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 16 | 17 | 25 | 26 | 33 | -------------------------------------------------------------------------------- /app/src/main/res/layout/keep.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 11 | 16 | 21 | 26 | 31 | 36 | 41 | 46 | 51 | 56 | 61 | 66 | 71 | 72 | -------------------------------------------------------------------------------- /app/src/main/res/layout/onboarding_fragment.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 15 | 16 | 31 | 32 | 41 | -------------------------------------------------------------------------------- /app/src/main/res/layout/onboarding_inside_fragment.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 16 | 17 | 28 | 29 | 38 | 39 | -------------------------------------------------------------------------------- /app/src/main/res/layout/sticker.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 25 | 26 | 35 | 36 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /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/CarLens-Android/321fa52c5cc0a339b27737b626163f8f72348cd9/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netguru/CarLens-Android/321fa52c5cc0a339b27737b626163f8f72348cd9/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netguru/CarLens-Android/321fa52c5cc0a339b27737b626163f8f72348cd9/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netguru/CarLens-Android/321fa52c5cc0a339b27737b626163f8f72348cd9/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netguru/CarLens-Android/321fa52c5cc0a339b27737b626163f8f72348cd9/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netguru/CarLens-Android/321fa52c5cc0a339b27737b626163f8f72348cd9/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netguru/CarLens-Android/321fa52c5cc0a339b27737b626163f8f72348cd9/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netguru/CarLens-Android/321fa52c5cc0a339b27737b626163f8f72348cd9/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netguru/CarLens-Android/321fa52c5cc0a339b27737b626163f8f72348cd9/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netguru/CarLens-Android/321fa52c5cc0a339b27737b626163f8f72348cd9/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/raw/keep.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | -------------------------------------------------------------------------------- /app/src/main/res/raw/onboarding_1.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netguru/CarLens-Android/321fa52c5cc0a339b27737b626163f8f72348cd9/app/src/main/res/raw/onboarding_1.mp4 -------------------------------------------------------------------------------- /app/src/main/res/raw/onboarding_2.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netguru/CarLens-Android/321fa52c5cc0a339b27737b626163f8f72348cd9/app/src/main/res/raw/onboarding_2.mp4 -------------------------------------------------------------------------------- /app/src/main/res/raw/onboarding_3.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netguru/CarLens-Android/321fa52c5cc0a339b27737b626163f8f72348cd9/app/src/main/res/raw/onboarding_3.mp4 -------------------------------------------------------------------------------- /app/src/main/res/values/attrs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #ffffff 4 | #00000000 5 | #F0F4F5 6 | #5FCF6F 7 | #5fa4cf 8 | 9 | #FF5969 10 | #FF8D3D 11 | #ffac71 12 | 13 | #D7E2E6 14 | #9BB3C6 15 | #000 16 | #c0ffffff 17 | 18 | #E9EEF1 19 | 20 | #C4D0D6 21 | #EEF3F5 22 | #FF8199AC 23 | #000 24 | #8199AC 25 | 26 | #2F3031 27 | #FFFFFF 28 | #FF6163 29 | 30 | #80000000 31 | 32 | -------------------------------------------------------------------------------- /app/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 32dp 4 | 24dp 5 | 20dp 6 | 16dp 7 | 8dp 8 | 4dp 9 | 2dp 10 | 11 | 72dp 12 | 32dp 13 | 24dp 14 | 20dp 15 | 16dp 16 | 8dp 17 | 4dp 18 | 2dp 19 | 20 | 20sp 21 | 22 | 23sp 23 | 24 | 20dp 25 | 26 | 45dp 27 | 68dp 28 | 110dp 29 | 90dp 30 | -40dp 31 | 8dp 32 | 100dp 33 | 34 | 35 | -0.0127 36 | 100sp 37 | 38 | 160dp 39 | 49dp 40 | 41 | 5dp 42 | 10dp 43 | 44 | 20.0 45 | 20sp 46 | 47 | 38dp 48 | -0.0465 49 | 50 | 1dp 51 | 80dp 52 | 15dp 53 | 54 | 1.2 55 | 12sp 56 | 57 | 12sp 58 | 20sp 59 | 60 | 50dp 61 | -25dp 62 | 63 | 64 | 39dp 65 | 37dp 66 | 120dp 67 | 68 | 60dp 69 | 3dp 70 | 71 | 126dp 72 | 20sp 73 | 74 | 32sp 75 | 138dp 76 | 77dp 77 | 78 | 45dp 79 | 90dp 80 | 10dp 81 | 45dp 82 | 83 | 90dp 84 | 10dp 85 | 86 | 125dp 87 | 26dp 88 | 18sp 89 | 12sp 90 | 91 | 92 | 20sp 93 | 94 | 95 | -------------------------------------------------------------------------------- /app/src/main/res/values/integers.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 300 4 | 150 5 | 300 6 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | CarLens 5 | viewfinder 6 | Searching 7 | Please install ARCore 8 | Please update ARCore 9 | Please update this app 10 | This device does not support AR 11 | Failed to create AR session 12 | top speed 13 | %d sec 14 | %d mph 15 | %1$s %2$s 16 | http://www.google.com/#q=%s 17 | 18 | not a car 19 | Ford 20 | Fiesta 21 | Honda 22 | Civic 23 | Nissan 24 | Qashqai 25 | Toyota 26 | Camry 27 | Corolla 28 | Volkswagen 29 | Golf 30 | Passat 31 | Tiguan 32 | engine 33 | power 34 | %d hp 35 | %d cc 36 | \? 37 | 38 | %d mph 39 | %d km/h 40 | 0 to 60 MPH 41 | 0 to 100 KM/H 42 | 43 | 44 | Recognize Cars 45 | Point the camera at the front of the car for the best results in image recognition. 46 | Discover Cars 47 | Search and unlock the models available in the gallery. Catch them all. 48 | Stay updated 49 | Expect updates with more cars and improved recognition algorithm. 50 | 51 | 52 | Car has been detected but does not belong to our data base 53 | Car has been found 54 | Recognizing 55 | Point the camera at a car 56 | Could not attach pin, please try again 57 | 58 | The Volkswagen Tiguan is a compact crossover vehicle (CUV). Introduced in 2007, it uses the PQ35 platform of the Volkswagen Golf. 59 | Discovered 60 | 61 | Allow Camera Access 62 | Recognize cars and unlock\nthem in the Gallery 63 | 64 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | 18 | 19 | 25 | 26 | 33 | 34 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /app/src/main/res/xml/anim_drawable.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 10 | 11 | 12 | 13 | 14 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /app/src/main/rs/yuv420888.rs: -------------------------------------------------------------------------------- 1 | #pragma version(1) 2 | #pragma rs java_package_name(co.netguru.android.carrecognition); 3 | #pragma rs_fp_relaxed 4 | 5 | int32_t width; 6 | int32_t height; 7 | 8 | uint picWidth, uvPixelStride, uvRowStride ; 9 | rs_allocation ypsIn,uIn,vIn; 10 | 11 | // The LaunchOptions ensure that the Kernel does not enter the padding zone of Y, so yRowStride can be ignored WITHIN the Kernel. 12 | uchar4 __attribute__((kernel)) doConvert(uint32_t x, uint32_t y) { 13 | 14 | // index for accessing the uIn's and vIn's 15 | uint uvIndex= uvPixelStride * (x/2) + uvRowStride*(y/2); 16 | 17 | // get the y,u,v values 18 | uchar yps= rsGetElementAt_uchar(ypsIn, x, y); 19 | uchar u= rsGetElementAt_uchar(uIn, uvIndex); 20 | uchar v= rsGetElementAt_uchar(vIn, uvIndex); 21 | 22 | // calc argb 23 | int4 argb; 24 | argb.r = yps + v * 1436 / 1024 - 179; 25 | argb.g = yps -u * 46549 / 131072 + 44 -v * 93604 / 131072 + 91; 26 | argb.b = yps +u * 1814 / 1024 - 227; 27 | argb.a = 255; 28 | 29 | uchar4 out = convert_uchar4(clamp(argb, 0, 255)); 30 | return out; 31 | } -------------------------------------------------------------------------------- /app/src/release/kotlin/co/netguru/android/carrecognition/application/DebugMetricsHelper.kt: -------------------------------------------------------------------------------- 1 | package co.netguru.android.carrecognition.application 2 | 3 | import android.content.Context 4 | import co.netguru.android.carrecognition.application.scope.AppScope 5 | import javax.inject.Inject 6 | import co.netguru.android.carrecognition.BuildConfig 7 | import com.microsoft.appcenter.AppCenter 8 | import com.microsoft.appcenter.crashes.Crashes 9 | import android.app.Application 10 | 11 | /** 12 | * Helper class that initializes a set of debugging tools 13 | * for the debug build type and register crash manager for release type. 14 | * ## Debug type tools: 15 | * - AndroidDevMetrics 16 | * - Stetho 17 | * - StrictMode 18 | * - LeakCanary 19 | * - Timber 20 | * 21 | * ## Release type tools: 22 | * - CrashManager 23 | * 24 | * ## Staging type tools: 25 | * - CrashManager 26 | */ 27 | @AppScope 28 | class DebugMetricsHelper @Inject constructor() { 29 | 30 | internal fun init(application: Application) { 31 | AppCenter.start(application, BuildConfig.APP_CENTER_ID, Crashes::class.java) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /app/src/release/kotlin/co/netguru/android/carrecognition/application/RxJavaErrorHandlerImpl.kt: -------------------------------------------------------------------------------- 1 | package co.netguru.android.carrecognition.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/staging/kotlin/co/netguru/android/carrecognition/application/DebugMetricsHelper.kt: -------------------------------------------------------------------------------- 1 | package co.netguru.android.carrecognition.application 2 | 3 | import android.content.Context 4 | import co.netguru.android.carrecognition.application.scope.AppScope 5 | import javax.inject.Inject 6 | import co.netguru.android.carrecognition.BuildConfig 7 | import com.microsoft.appcenter.AppCenter 8 | import com.microsoft.appcenter.crashes.Crashes 9 | import android.app.Application 10 | 11 | /** 12 | * Helper class that initializes a set of debugging tools 13 | * for the debug build type and register crash manager for release type. 14 | * ## Debug type tools: 15 | * - AndroidDevMetrics 16 | * - Stetho 17 | * - StrictMode 18 | * - LeakCanary 19 | * - Timber 20 | * 21 | * ## Release type tools: 22 | * - CrashManager 23 | * 24 | * ## Staging type tools: 25 | * - CrashManager 26 | */ 27 | @AppScope 28 | class DebugMetricsHelper @Inject constructor() { 29 | 30 | internal fun init(application: Application) { 31 | AppCenter.start(application, BuildConfig.APP_CENTER_ID, Crashes::class.java) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /app/src/staging/kotlin/co/netguru/android/carrecognition/application/RxJavaErrorHandlerImpl.kt: -------------------------------------------------------------------------------- 1 | package co.netguru.android.carrecognition.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/android/carrecognition/RxSchedulersOverrideRule.kt: -------------------------------------------------------------------------------- 1 | package co.netguru.android.carrecognition 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/android/carrecognition/RxTestSchedulerOverrideRule.kt: -------------------------------------------------------------------------------- 1 | package co.netguru.android.carrecognition 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/android/carrecognition/common/LimitedListTest.kt: -------------------------------------------------------------------------------- 1 | package co.netguru.android.carrecognition.common 2 | 3 | import junit.framework.Assert.assertEquals 4 | import org.junit.Test 5 | 6 | class LimitedListTest { 7 | @Test 8 | fun `Should add to limit and not more`() { 9 | val limitedList = LimitedList(2) 10 | limitedList.add("first") 11 | limitedList.add("second") 12 | limitedList.add("third") 13 | 14 | assertEquals(limitedList[0], "second") 15 | assertEquals(limitedList[1], "third") 16 | } 17 | 18 | @Test 19 | fun `Should keep limit on add all`() { 20 | val limitedList = LimitedList(2) 21 | limitedList.addAll(listOf("first", "second", "third")) 22 | 23 | assertEquals(limitedList[0], "second") 24 | assertEquals(limitedList[1], "third") 25 | } 26 | } -------------------------------------------------------------------------------- /app/src/test/kotlin/co/netguru/android/carrecognition/common/MetricsUtilsTest.kt: -------------------------------------------------------------------------------- 1 | package co.netguru.android.carrecognition.common 2 | 3 | import android.content.res.Resources 4 | import co.netguru.android.carrecognition.R 5 | import com.nhaarman.mockito_kotlin.doReturn 6 | import com.nhaarman.mockito_kotlin.mock 7 | import com.nhaarman.mockito_kotlin.reset 8 | import com.nhaarman.mockito_kotlin.stub 9 | import org.junit.Assert.assertEquals 10 | import org.junit.Before 11 | import org.junit.Test 12 | import java.util.* 13 | 14 | class MetricsUtilsTest { 15 | 16 | private val resources = mock() 17 | private val metricLocale = Locale.GERMANY 18 | private val imperialLocale = Locale.US 19 | 20 | @Before 21 | fun setup() { 22 | reset(resources) 23 | } 24 | 25 | @Test 26 | fun `Should return metric label when country has metric system`() { 27 | val label = MetricsUtils.getAccelerationLabel(metricLocale) 28 | assertEquals(R.string.zero_to_hundred, label) 29 | } 30 | 31 | @Test 32 | fun `Should return metric value when country has metric system`() { 33 | resources.stub { 34 | on { getString(R.string.kmph_format, (20 * MetricsUtils.MILE_TO_KM_FACTOR).toInt()) } doReturn "32 km/h" 35 | } 36 | val label = MetricsUtils.getConvertedMetric(metricLocale, resources, 20) 37 | assertEquals("32 km/h", label) 38 | } 39 | 40 | @Test 41 | fun `Should return imperial label when country has imperial system`() { 42 | val label = MetricsUtils.getAccelerationLabel(imperialLocale) 43 | assertEquals(R.string.zero_to_sixty, label) 44 | } 45 | 46 | @Test 47 | fun `Should return imperial value when country has imperial system`() { 48 | resources.stub { 49 | on { getString(R.string.mph_format, 20) } doReturn "20 mph" 50 | } 51 | val label = MetricsUtils.getConvertedMetric(imperialLocale, resources, 20) 52 | assertEquals("20 mph", label) 53 | } 54 | } -------------------------------------------------------------------------------- /app/src/test/kotlin/co/netguru/android/carrecognition/common/OptionalTest.kt: -------------------------------------------------------------------------------- 1 | package co.netguru.android.carrecognition.common 2 | 3 | import io.reactivex.Maybe 4 | import io.reactivex.Observable 5 | import io.reactivex.Single 6 | import org.junit.Assert.assertEquals 7 | import org.junit.Assert.assertTrue 8 | import org.junit.Test 9 | 10 | class OptionalTest { 11 | 12 | @Test 13 | fun `Should transform object to optional Some when toOptional called`() { 14 | val testObject = "Test" 15 | val optionalTestObject = testObject.toOptional() 16 | 17 | assertTrue(optionalTestObject is Optional.Some) 18 | assertEquals(testObject, optionalTestObject.toNullable()) 19 | } 20 | 21 | @Test 22 | fun `Should transform null to optional None when toOptional called`() { 23 | val testObject: String? = null 24 | val optionalTestObject = testObject.toOptional() 25 | 26 | assertTrue(optionalTestObject == Optional.None) 27 | assertEquals(testObject, optionalTestObject.toNullable()) 28 | } 29 | 30 | @Test 31 | fun `Should return underlying object when toNullable called`() { 32 | val underlyingString = "Test" 33 | val nonNullOptionalWithUnderlyingString: Optional = Optional.Some( 34 | underlyingString 35 | ) 36 | assertEquals(underlyingString, nonNullOptionalWithUnderlyingString.toNullable()) 37 | 38 | val nullStringOptional: Optional = Optional.None 39 | assertEquals(null, nullStringOptional.toNullable()) 40 | } 41 | 42 | @Test 43 | fun `Map should transform underlying object if it is not null else return None`() { 44 | val f: (Int) -> Int = { it + 1 } 45 | val absentVal: Optional = Optional.None 46 | val presentVal: Optional = Optional.Some(1) 47 | 48 | assertEquals(absentVal.map(f), Optional.None) 49 | assertEquals(presentVal.map(f), Optional.Some(2)) 50 | } 51 | 52 | @Test 53 | fun `Map transformation can return null`() { 54 | val f: (Int) -> Int? = { null } 55 | val absentVal: Optional = Optional.None 56 | val presentVal: Optional = Optional.Some(1) 57 | 58 | assertEquals(absentVal.map(f), Optional.None) 59 | assertEquals(presentVal.map(f), Optional.None) 60 | } 61 | 62 | @Test 63 | fun `FlatMap should transform underlying object if it is not null else return None`() { 64 | val f: (Int) -> Optional = { (it + 1).toOptional() } 65 | val absentVal: Optional = Optional.None 66 | val presentVal: Optional = Optional.Some(1) 67 | 68 | assertEquals(absentVal.flatMap(f), Optional.None) 69 | assertEquals(presentVal.flatMap(f), Optional.Some(2)) 70 | } 71 | 72 | @Test 73 | fun `FlatMap transformation can return None Optional`() { 74 | val f: (Int) -> Optional = { Optional.None } 75 | val absentVal: Optional = Optional.None 76 | val presentVal: Optional = Optional.Some(1) 77 | 78 | assertEquals(absentVal.flatMap(f), Optional.None) 79 | assertEquals(presentVal.flatMap(f), Optional.None) 80 | } 81 | 82 | @Test 83 | fun `Filter optional on observable should filter none and pass value into stream`() { 84 | val firstValue = "test" 85 | val secondValue = "test2" 86 | val test = Observable.fromArray( 87 | "test".toOptional(), 88 | Optional.None, 89 | "test2".toOptional() 90 | ) 91 | .filterOptionalNone() 92 | .test() 93 | 94 | test.assertValues(firstValue, secondValue) 95 | } 96 | 97 | @Test 98 | fun `Filter optional on single should filter none and pass value into stream`() { 99 | val firstValue = "test" 100 | val testOptionalSome = Single.just(firstValue.toOptional()) 101 | .filterOptionalNone() 102 | .test() 103 | 104 | testOptionalSome.assertValues(firstValue) 105 | 106 | val testOptionalNone = Single.just(Optional.None as Optional) 107 | .filterOptionalNone() 108 | .test() 109 | 110 | testOptionalNone.assertNoValues() 111 | } 112 | 113 | @Test 114 | fun `Filter optional on maybe should filter none and pass value into stream`() { 115 | val firstValue = "test" 116 | val testOptionalSome = Maybe.just(firstValue.toOptional()) 117 | .filterOptionalNone() 118 | .test() 119 | 120 | testOptionalSome.assertValues(firstValue) 121 | 122 | val testOptionalNone = Maybe.just(Optional.None as Optional) 123 | .filterOptionalNone() 124 | .test() 125 | 126 | testOptionalNone.assertNoValues() 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /app/src/test/kotlin/co/netguru/android/carrecognition/feature/cars/CarListPresenterTest.kt: -------------------------------------------------------------------------------- 1 | package co.netguru.android.carrecognition.feature.cars 2 | 3 | import co.netguru.android.carrecognition.RxSchedulersOverrideRule 4 | import co.netguru.android.carrecognition.data.db.AppDatabase 5 | import co.netguru.android.carrecognition.data.db.Cars 6 | import co.netguru.android.carrecognition.data.db.CarsDao 7 | import com.nhaarman.mockito_kotlin.* 8 | import io.reactivex.Single 9 | import org.junit.Before 10 | import org.junit.Rule 11 | import org.junit.Test 12 | 13 | class CarListPresenterTest { 14 | 15 | private val view = mock() 16 | private val database = mock() 17 | private val carsDao = mock() 18 | private val car = mock() 19 | private val listOfCars = listOf(car) 20 | 21 | private lateinit var presenter: CarListPresenter 22 | 23 | @Rule 24 | @JvmField 25 | val overrideSchedulersRule = RxSchedulersOverrideRule() 26 | 27 | @Before 28 | fun init() { 29 | reset(database, carsDao, car) 30 | database.stub { on { carDao() } doReturn carsDao } 31 | presenter = CarListPresenter(database) 32 | presenter.attachView(view) 33 | } 34 | 35 | @Test 36 | fun `should populate when called getCars`() { 37 | carsDao.stub { on { getAll() } doReturn Single.create { it.onSuccess(listOfCars) } } 38 | 39 | presenter.getCars() 40 | 41 | verify(view).showLoading() 42 | verify(view).populate(listOfCars) 43 | } 44 | 45 | @Test 46 | fun `should show error when called getCars`() { 47 | val t = Throwable() 48 | carsDao.stub { on { getAll() } doReturn Single.create { it.onError(t) } } 49 | 50 | presenter.getCars() 51 | 52 | verify(view).showLoading() 53 | verify(view).onError(t) 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /app/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker: -------------------------------------------------------------------------------- 1 | mock-maker-inline -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | buildscript { 3 | 4 | ext { 5 | kotlinVersion = '1.2.60' 6 | } 7 | repositories { 8 | google() 9 | jcenter() 10 | mavenCentral() 11 | mavenLocal() 12 | maven { url "https://plugins.gradle.org/m2/" } 13 | } 14 | 15 | dependencies { 16 | classpath 'com.android.tools.build:gradle:3.1.4' 17 | classpath "com.getkeepsafe.dexcount:dexcount-gradle-plugin:0.8.2" 18 | classpath "com.frogermcs.androiddevmetrics:androiddevmetrics-plugin:0.6" 19 | classpath 'com.vanniktech:gradle-android-junit-jacoco-plugin:0.11.0' 20 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion" 21 | classpath 'com.github.ben-manes:gradle-versions-plugin:0.18.0' 22 | // There is a problem with this plugin when using Gradle Plugin >= 3.0.0-alpha3 23 | // https://jira.sonarsource.com/browse/SONARGRADL-41 24 | classpath "org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:2.6.1" 25 | } 26 | } 27 | 28 | plugins { 29 | id "io.gitlab.arturbosch.detekt" version "1.0.0.RC6-4" 30 | } 31 | 32 | detekt { 33 | profile("main") { 34 | input = "$projectDir/app/src/main/kotlin" 35 | config = "$projectDir/default-detekt-config.yml" 36 | filters = ".*test.*,.*/resources/.*,.*/tmp/.*" 37 | } 38 | } 39 | 40 | allprojects { 41 | repositories { 42 | google() 43 | jcenter() 44 | mavenCentral() 45 | maven { url "https://jitpack.io" } 46 | maven { url "https://clojars.org/repo/" } 47 | maven { url "https://maven.fabric.io/public" } 48 | } 49 | } 50 | 51 | task clean(type: Delete) { 52 | delete rootProject.buildDir 53 | } 54 | 55 | apply from: 'buildsystem/dependencies.gradle' 56 | apply from: 'buildsystem/bitrise.gradle' 57 | apply from: 'buildsystem/secrets.gradle' 58 | -------------------------------------------------------------------------------- /buildsystem/bitrise.gradle: -------------------------------------------------------------------------------- 1 | ext { 2 | bitrise = [ 3 | // Bitrise command line interface specific properties 4 | cli: [ 5 | workflowId : System.getenv('BITRISE_TRIGGERED_WORKFLOW_ID'), 6 | workflowTitle: System.getenv('BITRISE_TRIGGERED_WORKFLOW_TITLE'), 7 | buildStatus : System.getenv('BITRISE_BUILD_STATUS'), 8 | sourceDir : System.getenv('BITRISE_SOURCE_DIR'), 9 | deployDir : System.getenv('BITRISE_DEPLOY_DIR'), 10 | isCi : "true" == System.getenv('CI'), 11 | isPr : "true" == System.getenv('PR'), 12 | ], 13 | // Bitrise.io - only 14 | io : [ 15 | buildNumber : System.getenv('BITRISE_BUILD_NUMBER'), 16 | gitUrl : System.getenv('GIT_REPOSITORY_URL'), 17 | appTitle : System.getenv('BITRISE_APP_TITLE'), 18 | appUrl : System.getenv('BITRISE_APP_URL'), 19 | appSlug : System.getenv('BITRISE_APP_SLUG'), 20 | buildUrl : System.getenv('BITRISE_BUILD_URL'), 21 | buildSlug : System.getenv('BITRISE_BUILD_SLUG'), 22 | buildTriggerTimestamp: System.getenv('BITRISE_BUILD_TRIGGER_TIMESTAMP'), 23 | gitBranch : System.getenv('BITRISE_GIT_BRANCH'), 24 | gitTag : System.getenv('BITRISE_GIT_TAG'), 25 | gitCommit : System.getenv('BITRISE_GIT_COMMIT'), 26 | gitMessage : System.getenv('BITRISE_GIT_MESSAGE'), 27 | pullRequest : System.getenv('BITRISE_PULL_REQUEST'), 28 | provisionUrl : System.getenv('BITRISE_PROVISION_URL'), 29 | certificateUrl : System.getenv('BITRISE_CERTIFICATE_URL'), 30 | certificatePassphrase: System.getenv('BITRISE_CERTIFICATE_PASSPHRASE'), 31 | ] 32 | ] 33 | 34 | isBitrise = bitrise.cli.isCi || bitrise.cli.isPr 35 | } -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /buildsystem/sonarqube.gradle: -------------------------------------------------------------------------------- 1 | android.applicationVariants.all { variant -> 2 | task("sonarqube${variant.getName().capitalize()}").doLast { 3 | sonarqube.ext.sonarCurBuildType = variant.getBuildType().name 4 | sonarqube.ext.sonarCurFlavor = variant.getFlavorName() 5 | sonarqube.ext.sonarConfig.versionName = variant.getVersionName() 6 | sonarqube.ext.sonarConfig.applicationId = android.defaultConfig.applicationId 7 | //if you want separate projects for every variant replace this value with variant.getApplicationId() 8 | 9 | //get source and test directories 10 | sonarqube.ext.sonarConfig.sources = variant.getSourceSets().collect { 11 | it.getJavaDirectories() + it.getResDirectories() 12 | }.flatten().findAll { 13 | it.exists() 14 | }.collect { 15 | it.path.substring(projectDir.path.length() + 1) 16 | }.join(",") 17 | 18 | def testDirectories = android.sourceSets.test.java.srcDirs + android.sourceSets.test.res.srcDirs + 19 | android.sourceSets.androidTest.java.srcDirs + android.sourceSets.androidTest.res.srcDirs 20 | if (variant.getTestVariant() != null) 21 | sonarqube.ext.sonarConfig.tests = variant.getTestVariant().getSourceSets().collect { 22 | it.getJavaDirectories() + it.getResDirectories() 23 | }.plus(testDirectories).flatten().findAll { 24 | it.exists() 25 | }.collect { 26 | it.path.substring(projectDir.path.length() + 1) 27 | }.unique().join(",") 28 | else 29 | sonarqube.ext.sonarConfig.tests = testDirectories.findAll { it.exists() }.collect { 30 | it.path.substring(projectDir.path.length() + 1) 31 | }.join(",") 32 | 33 | //run sonarqube with prepopulated fields 34 | tasks["sonarqube"].execute() 35 | } 36 | } 37 | 38 | sonarqube { 39 | 40 | ext { 41 | sonarConfig = [ 42 | projectName : "Car recognition ", //project name as displayed in SonarQube 43 | analysisMode: (bitrise.cli.isPr) ? "preview" : "publish", //preview is required for github plugin 44 | githubRepo : "netguru/car-recognition-android" //optional - github plugin repo 45 | ] 46 | } 47 | 48 | properties { 49 | def libraries = project.android.sdkDirectory.getPath() + "/platforms/android-25/android.jar," 50 | // required metadata for sonarqube 51 | property "sonar.login", keyProperty('SONAR_ACCESS_TOKEN') //access token as login 52 | property "sonar.projectKey", sonarConfig.applicationId 53 | property "sonar.projectName", sonarConfig.projectName 54 | property "sonar.projectVersion", sonarConfig.versionName 55 | property "sonar.host.url", "https://sonar.devguru.co:443" //https - port 443 56 | 57 | //project config - directories 58 | property "sonar.java.source", "1.7" 59 | property "sonar.sources", sonarConfig.sources 60 | property "sonar.binaries", "build/intermediates/classes/${sonarCurFlavor}/${sonarCurBuildType}" 61 | property "sonar.java.binaries", "build/intermediates/classes/${sonarCurFlavor}/${sonarCurBuildType}" 62 | property "sonar.tests", sonarConfig.tests 63 | property "sonar.java.test.binaries", "build/intermediates/classes/${sonarCurFlavor}/${sonarCurBuildType}" 64 | property "sonar.libraries", libraries 65 | property "sonar.java.libraries", libraries 66 | property "sonar.java.test.libraries", libraries 67 | // path to coverage reports 68 | property "sonar.jacoco.reportPath", "build/jacoco/test${sonarCurFlavor.capitalize()}${sonarCurBuildType.capitalize()}UnitTest.exec" 69 | //coverage plugin 70 | property "sonar.java.coveragePlugin", "jacoco" 71 | //path to lint reports 72 | property "sonar.android.lint.report", "build/outputs/lint-results-${sonarCurFlavor}${sonarCurBuildType.capitalize()}.xml" 73 | 74 | property "sonar.analysis.mode", sonarConfig.analysisMode //analysis mode 75 | 76 | property "sonar.scm.provider", "git" //scm provider 77 | 78 | //Github plugin 79 | //github oauth access token 80 | property "sonar.github.oauth", (bitrise.cli.isPr) ? keyProperty('GITHUB_OAUTH_TOKEN') : null 81 | //project repository 82 | property "sonar.github.repository", (bitrise.cli.isPr) ? sonarConfig.githubRepo : null 83 | //pull request number 84 | property "sonar.github.pullRequest", (bitrise.cli.isPr) ? bitrise.io.pullRequest : null 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | codecov: 2 | ci: 3 | - bitrise 4 | 5 | coverage: 6 | precision: 2 7 | round: down 8 | range: "0...100" 9 | 10 | status: 11 | project: 12 | unit: 13 | target: auto 14 | threshold: 1% 15 | flag: unit 16 | flags: 17 | - unit 18 | 19 | instrumentation: 20 | target: auto 21 | threshold: 1% 22 | flag: instrumentation 23 | flags: 24 | - instrumentation 25 | patch: 26 | unit: 27 | flag: unit 28 | flags: 29 | - unit 30 | 31 | instrumentation: 32 | flag: instrumentation 33 | flags: 34 | - instrumentation 35 | 36 | changes: 37 | unit: 38 | flag: unit 39 | flags: 40 | - unit 41 | 42 | instrumentation: 43 | flag: instrumentation 44 | flags: 45 | - instrumentation 46 | 47 | ignore: 48 | - mock/* 49 | - production/* 50 | - androidTest/* 51 | - androidTestMock/* 52 | - androidTestProduction/* 53 | 54 | comment: 55 | layout: "header, diff, changes, sunburst, uncovered" 56 | behavior: default 57 | flags: 58 | - unit 59 | - instrumentation -------------------------------------------------------------------------------- /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 | org.gradle.jvmargs=-Xmx1536m 10 | # When configured, Gradle will run in incubating parallel mode. 11 | # This option should only be used with decoupled projects. More details, visit 12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 13 | # org.gradle.parallel=true 14 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netguru/CarLens-Android/321fa52c5cc0a339b27737b626163f8f72348cd9/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Wed Apr 11 10:02:25 CEST 2018 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-4.7-all.zip 7 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | --------------------------------------------------------------------------------