├── .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 |
4 |
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)| [](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 |
--------------------------------------------------------------------------------