├── .gitattributes
├── .github
└── PULL_REQUEST_TEMPLATE
│ └── pull_request_template.md
├── .gitignore
├── LICENSE
├── README.md
├── app
├── .DS_Store
├── .gitignore
├── build.gradle.kts
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── in
│ │ └── surajsau
│ │ └── jisho
│ │ └── ExampleInstrumentedTest.kt
│ ├── main
│ ├── AndroidManifest.xml
│ ├── assets
│ │ ├── emojis.json
│ │ ├── style0.jpg
│ │ ├── style1.jpg
│ │ ├── style2.jpg
│ │ ├── style3.jpg
│ │ ├── style4.jpg
│ │ └── style5.jpg
│ ├── java
│ │ └── in
│ │ │ └── surajsau
│ │ │ └── jisho
│ │ │ ├── App.kt
│ │ │ ├── MainActivity.kt
│ │ │ ├── base
│ │ │ ├── BitmapCache.kt
│ │ │ ├── BitmapUtils.kt
│ │ │ ├── BoolUtils.kt
│ │ │ ├── ContextUtils.kt
│ │ │ ├── FileName.kt
│ │ │ ├── ListUtils.kt
│ │ │ ├── NormalizeOp.kt
│ │ │ ├── ObserveLifecycle.kt
│ │ │ ├── Optional.kt
│ │ │ └── SingleFlowViewModel.kt
│ │ │ ├── data
│ │ │ ├── CardDataProvider.kt
│ │ │ ├── ChatDataProvider.kt
│ │ │ ├── DigitalInkProvider.kt
│ │ │ ├── FileProvider.kt
│ │ │ ├── ScreensDataProvider.kt
│ │ │ ├── StyleTransferProvider.kt
│ │ │ ├── TranslatorProvider.kt
│ │ │ ├── chat
│ │ │ │ ├── EmojiLabellingDataProvider.kt
│ │ │ │ ├── EntityExtractionProvider.kt
│ │ │ │ └── ReplySuggestionProvider.kt
│ │ │ ├── db
│ │ │ │ ├── AppDb.kt
│ │ │ │ ├── FaceImage.kt
│ │ │ │ └── FacenetDAO.kt
│ │ │ ├── facenet
│ │ │ │ ├── FaceDetectionProvider.kt
│ │ │ │ ├── FaceRecognitionProvider.kt
│ │ │ │ └── FacesDataProvider.kt
│ │ │ └── model
│ │ │ │ ├── ChatMessageModel.kt
│ │ │ │ ├── Emoji.kt
│ │ │ │ ├── Screen.kt
│ │ │ │ └── Suggestion.kt
│ │ │ ├── di
│ │ │ ├── DataModule.kt
│ │ │ └── cardreader
│ │ │ │ └── OnboardingScreenModule.kt
│ │ │ ├── domain
│ │ │ ├── Cleanup.kt
│ │ │ ├── cardreader
│ │ │ │ ├── GetCardDetails.kt
│ │ │ │ ├── IdentifyText.kt
│ │ │ │ └── processor
│ │ │ │ │ ├── CCFrontProcessor.kt
│ │ │ │ │ ├── IDCardBackProcessor.kt
│ │ │ │ │ ├── IDCardFrontProcessor.kt
│ │ │ │ │ └── TextProcessor.kt
│ │ │ ├── chat
│ │ │ │ ├── CheckEntityExtractorAvailability.kt
│ │ │ │ ├── FetchChatDetails.kt
│ │ │ │ ├── FetchLatestMessage.kt
│ │ │ │ ├── FetchSuggestions.kt
│ │ │ │ ├── GetEmojiSuggestions.kt
│ │ │ │ ├── LoadEmojis.kt
│ │ │ │ └── SendMessage.kt
│ │ │ ├── facenet
│ │ │ │ ├── DetectFaces.kt
│ │ │ │ ├── FetchAllFaces.kt
│ │ │ │ ├── FetchAllImages.kt
│ │ │ │ ├── FetchAllImagesForFace.kt
│ │ │ │ ├── FetchFaceNames.kt
│ │ │ │ ├── Initiate.kt
│ │ │ │ ├── LoadEmbeddings.kt
│ │ │ │ ├── SaveFaceEmbedding.kt
│ │ │ │ ├── SaveImage.kt
│ │ │ │ └── SaveNewFace.kt
│ │ │ └── models
│ │ │ │ ├── CreditCard.kt
│ │ │ │ ├── FaceModel.kt
│ │ │ │ ├── FaceRecognitionResult.kt
│ │ │ │ ├── GalleryModel.kt
│ │ │ │ ├── IDCard.kt
│ │ │ │ ├── User.kt
│ │ │ │ └── chat
│ │ │ │ ├── ChatAnnotation.kt
│ │ │ │ ├── ChatDetails.kt
│ │ │ │ ├── ChatRowModel.kt
│ │ │ │ └── EntityModel.kt
│ │ │ └── ui
│ │ │ ├── Destinations.kt
│ │ │ ├── MLApp.kt
│ │ │ ├── base
│ │ │ ├── AskPermissionScreen.kt
│ │ │ ├── ExternalIntentHandler.kt
│ │ │ ├── ForEachRow.kt
│ │ │ ├── Line.kt
│ │ │ └── camera
│ │ │ │ ├── Camera.kt
│ │ │ │ └── CameraControls.kt
│ │ │ ├── cardreader
│ │ │ ├── CameraScreen.kt
│ │ │ ├── CardReaderScreen.kt
│ │ │ ├── DetailsScreen.kt
│ │ │ ├── OnBoardingScreen.kt
│ │ │ └── OnBoardingViewModel.kt
│ │ │ ├── chat
│ │ │ ├── ChatBubble.kt
│ │ │ ├── ChatClickableText.kt
│ │ │ ├── SmartChatScreen.kt
│ │ │ └── SmartChatViewModel.kt
│ │ │ ├── digitalink
│ │ │ ├── DigitalInkScreen.kt
│ │ │ ├── DigitalInkViewModel.kt
│ │ │ ├── DrawSpace.kt
│ │ │ └── MLKitModelStatus.kt
│ │ │ ├── facenet
│ │ │ ├── CameraScreen.kt
│ │ │ ├── CheckFaceDialog.kt
│ │ │ ├── FacenetScreen.kt
│ │ │ ├── FacenetViewModel.kt
│ │ │ └── SavedFaceImage.kt
│ │ │ ├── home
│ │ │ ├── HomeScreen.kt
│ │ │ └── HomeViewModel.kt
│ │ │ ├── styletransfer
│ │ │ ├── CameraScreen.kt
│ │ │ ├── StylePreviewScreen.kt
│ │ │ ├── StyleTransferScreen.kt
│ │ │ └── StyleTransferViewModel.kt
│ │ │ └── theme
│ │ │ ├── Color.kt
│ │ │ ├── Shape.kt
│ │ │ ├── Theme.kt
│ │ │ └── Type.kt
│ ├── ml
│ │ ├── facenet.tflite
│ │ ├── magenta_style_predictor.tflite
│ │ ├── magenta_style_transfer.tflite
│ │ ├── style_predictor_model.tflite
│ │ └── style_transfer_model.tflite
│ └── res
│ │ ├── drawable-v24
│ │ └── ic_launcher_foreground.xml
│ │ ├── drawable
│ │ ├── ic_launcher_background.xml
│ │ └── ic_switch_camera.png
│ │ ├── mipmap-hdpi
│ │ └── ic_launcher.png
│ │ ├── mipmap-mdpi
│ │ └── ic_launcher.png
│ │ ├── mipmap-xhdpi
│ │ └── ic_launcher.png
│ │ ├── mipmap-xxhdpi
│ │ └── ic_launcher.png
│ │ ├── mipmap-xxxhdpi
│ │ └── ic_launcher.png
│ │ ├── values
│ │ ├── colors.xml
│ │ ├── strings.xml
│ │ └── themes.xml
│ │ └── xml
│ │ ├── backup_rules.xml
│ │ ├── data_extraction_rules.xml
│ │ └── file_paths.xml
│ └── test
│ └── java
│ └── in
│ └── surajsau
│ └── jisho
│ └── ExampleUnitTest.kt
├── build.gradle.kts
├── buildSrc
├── build.gradle.kts
└── src
│ └── main
│ └── java
│ └── Versions.kt
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── screenshots
├── card_reader.gif
├── face.gif
├── gpt2.gif
└── translate_app.gif
└── settings.gradle
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Auto detect text files and perform LF normalization
2 | * text=auto
3 |
--------------------------------------------------------------------------------
/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md:
--------------------------------------------------------------------------------
1 | ## Description
2 |
3 | ## Screenshots
4 |
5 | ## Checklist:
6 | - [ ] Test
7 | - [ ] Generate Screenshot
8 | - [ ] Add entry in `ScreensProvider`
9 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Built application files
2 | *.apk
3 | *.ap_
4 | *.aab
5 |
6 | # Files for the ART/Dalvik VM
7 | *.dex
8 |
9 | # Java class files
10 | *.class
11 |
12 | # Generated files
13 | bin/
14 | gen/
15 | out/
16 |
17 | # Gradle files
18 | .gradle/
19 | build/
20 |
21 | # Local configuration file (sdk path, etc)
22 | local.properties
23 |
24 | # Proguard folder generated by Eclipse
25 | proguard/
26 |
27 | # Log Files
28 | *.log
29 |
30 | # Android Studio Navigation editor temp files
31 | .navigation/
32 |
33 | # Android Studio captures folder
34 | captures/
35 |
36 | # IntelliJ
37 | *.iml
38 | .idea/workspace.xml
39 | .idea/tasks.xml
40 | .idea/gradle.xml
41 | .idea/assetWizardSettings.xml
42 | .idea/dictionaries
43 | .idea/libraries
44 | .idea/caches
45 |
46 | # Keystore files
47 | # Uncomment the following lines if you do not want to check your keystore files in.
48 | #*.jks
49 | #*.keystore
50 |
51 | # External native build folder generated in Android Studio 2.2 and later
52 | .externalNativeBuild
53 |
54 | # Google Services (e.g. APIs or Firebase)
55 | google-services.json
56 |
57 | # Freeline
58 | freeline.py
59 | freeline/
60 | freeline_project_description.json
61 |
62 | # fastlane
63 | fastlane/report.xml
64 | fastlane/Preview.html
65 | fastlane/screenshots
66 | fastlane/test_output
67 | fastlane/readme.md
68 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # ML Android
2 | Implementing various ML usecases in Android using strictly Jetpack Compose
3 |
4 | | **Demo** | **About** |
5 | |--|--|
6 | |
|
On-device Google Translate
- Character Recognition and Translation using Google MLKit
- Drawable Canvas implemented in Jetpack Compose
- [Code walkthrough](https://proandroiddev.com/on-device-google-translate-with-jetpack-compose-mlkit-7a48f5b11948) |
7 | |
| Google Photos like Face recognition
- Face Detection using MLKit
- Face recognition using [Sirius AI's Mobile Facenet](https://github.com/sirius-ai/MobileFaceNet_TF)|
8 | |
| Simplified Onboarding with Text Recognition
- Text Recognition using MLKit's Text Recognition|
9 |
10 | # License
11 | ```
12 | Copyright 2022 Suraj Sau
13 |
14 | Licensed under the Apache License, Version 2.0 (the "License");
15 | you may not use this file except in compliance with the License.
16 | You may obtain a copy of the License at
17 |
18 | https://www.apache.org/licenses/LICENSE-2.0
19 |
20 | Unless required by applicable law or agreed to in writing, software
21 | distributed under the License is distributed on an "AS IS" BASIS,
22 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
23 | See the License for the specific language governing permissions and
24 | limitations under the License.
25 | ```
26 |
--------------------------------------------------------------------------------
/app/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/surajsau/ML-Android/66ca1373067c57257e7d26f9b922d2d0915b0f4a/app/.DS_Store
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
3 | # tflite
4 | /src/main/assets/gpt/
5 | /src/main/assets/facenet/
6 |
--------------------------------------------------------------------------------
/app/build.gradle.kts:
--------------------------------------------------------------------------------
1 | import `in`.surajsau.jisho.Dep
2 |
3 | plugins {
4 | id("com.android.application")
5 | id("dagger.hilt.android.plugin")
6 | id("kotlin-android")
7 | id("kotlin-android-extensions")
8 | id("kotlin-kapt")
9 | }
10 |
11 | android {
12 | compileSdk = 31
13 |
14 | defaultConfig {
15 | applicationId = "in.surajsau.jisho"
16 | minSdk = 24
17 | targetSdk = 31
18 | versionCode = 1
19 | versionName = "1.0"
20 |
21 | testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
22 | vectorDrawables {
23 | useSupportLibrary = true
24 | }
25 | }
26 |
27 | buildTypes {
28 | release {
29 | proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
30 | }
31 | }
32 | compileOptions {
33 | sourceCompatibility = JavaVersion.VERSION_1_8
34 | targetCompatibility = JavaVersion.VERSION_1_8
35 | }
36 | kotlinOptions {
37 | jvmTarget = "1.8"
38 | }
39 | buildFeatures {
40 | compose = true
41 | mlModelBinding = true
42 | }
43 | composeOptions {
44 | kotlinCompilerExtensionVersion = "1.0.4"
45 | }
46 | androidResources {
47 | noCompress("tflite")
48 | }
49 | packagingOptions {
50 | resources {
51 | excludes += "/META-INF/{AL2.0,LGPL2.1}"
52 | }
53 | }
54 | }
55 |
56 | dependencies {
57 |
58 | implementation(Dep.AndroidX.Core)
59 | implementation(Dep.AndroidX.AppCompat)
60 | implementation(Dep.AndroidX.Material)
61 | implementation(Dep.AndroidX.Lifeycycle)
62 |
63 | // Temporary Fix For apps targeting Android S+, add the following
64 | constraints {
65 | implementation("androidx.work:work-runtime:2.7.0-alpha04")
66 | }
67 |
68 | implementation(Dep.Compose.Ui)
69 | implementation(Dep.Compose.Material)
70 | implementation(Dep.Compose.Tooling)
71 | implementation(Dep.Compose.LiveData)
72 | implementation(Dep.Compose.ViewModel)
73 | implementation(Dep.Compose.Activity)
74 | implementation(Dep.Compose.Navigation)
75 |
76 | implementation(Dep.CameraX.Core)
77 | implementation(Dep.CameraX.Lifecycle)
78 | implementation(Dep.CameraX.View)
79 |
80 | implementation(Dep.Accompanist.SwipeRefresh)
81 | implementation(Dep.Accompanist.Permissions)
82 |
83 | implementation(Dep.Hilt.Core)
84 | implementation(Dep.TensorFlow.Support)
85 | implementation(Dep.TensorFlow.MetaData)
86 | kapt(Dep.Hilt.Compiler)
87 | implementation(Dep.Hilt.Compose)
88 | implementation(Dep.Hilt.ViewModel)
89 | kapt(Dep.Hilt.AndroidCompiler)
90 |
91 | implementation(Dep.MLKit.DigitalInk)
92 | implementation(Dep.MLKit.Translation)
93 | implementation(Dep.MLKit.FaceDetection)
94 | implementation(Dep.MLKit.TextRecognition)
95 | implementation(Dep.MLKit.EntityExtraction)
96 | implementation(Dep.MLKit.SmartReplies)
97 | implementation(Dep.MLKit.ImageLabeling)
98 |
99 | implementation(Dep.Emoji.Core)
100 | implementation(Dep.Emoji.Views)
101 | implementation(Dep.Emoji.Helper)
102 |
103 | implementation(Dep.Coil.Compose)
104 | implementation(Dep.Coil.Gif)
105 |
106 | implementation(Dep.Klock)
107 |
108 | implementation(Dep.Gson)
109 |
110 | implementation(Dep.Room.Core)
111 | implementation(Dep.Room.Ktx)
112 | kapt(Dep.Room.Compiler)
113 |
114 | testImplementation("junit:junit:4.13.2")
115 | androidTestImplementation("androidx.test.ext:junit:1.1.3")
116 | androidTestImplementation("androidx.test.espresso:espresso-core:3.4.0")
117 | androidTestImplementation(Dep.Compose.Test)
118 | debugImplementation(Dep.Compose.ToolingTest)
119 | }
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/app/src/androidTest/java/in/surajsau/jisho/ExampleInstrumentedTest.kt:
--------------------------------------------------------------------------------
1 | package `in`.surajsau.jisho
2 |
3 | import androidx.test.platform.app.InstrumentationRegistry
4 | import androidx.test.ext.junit.runners.AndroidJUnit4
5 |
6 | import org.junit.Test
7 | import org.junit.runner.RunWith
8 |
9 | import org.junit.Assert.*
10 |
11 | /**
12 | * Instrumented test, which will execute on an Android device.
13 | *
14 | * See [testing documentation](http://d.android.com/tools/testing).
15 | */
16 | @RunWith(AndroidJUnit4::class)
17 | class ExampleInstrumentedTest {
18 | @Test
19 | fun useAppContext() {
20 | // Context of the app under test.
21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext
22 | assertEquals("in.surajsau.jisho", appContext.packageName)
23 | }
24 | }
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
22 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
39 |
42 |
43 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/app/src/main/assets/style0.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/surajsau/ML-Android/66ca1373067c57257e7d26f9b922d2d0915b0f4a/app/src/main/assets/style0.jpg
--------------------------------------------------------------------------------
/app/src/main/assets/style1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/surajsau/ML-Android/66ca1373067c57257e7d26f9b922d2d0915b0f4a/app/src/main/assets/style1.jpg
--------------------------------------------------------------------------------
/app/src/main/assets/style2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/surajsau/ML-Android/66ca1373067c57257e7d26f9b922d2d0915b0f4a/app/src/main/assets/style2.jpg
--------------------------------------------------------------------------------
/app/src/main/assets/style3.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/surajsau/ML-Android/66ca1373067c57257e7d26f9b922d2d0915b0f4a/app/src/main/assets/style3.jpg
--------------------------------------------------------------------------------
/app/src/main/assets/style4.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/surajsau/ML-Android/66ca1373067c57257e7d26f9b922d2d0915b0f4a/app/src/main/assets/style4.jpg
--------------------------------------------------------------------------------
/app/src/main/assets/style5.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/surajsau/ML-Android/66ca1373067c57257e7d26f9b922d2d0915b0f4a/app/src/main/assets/style5.jpg
--------------------------------------------------------------------------------
/app/src/main/java/in/surajsau/jisho/App.kt:
--------------------------------------------------------------------------------
1 | package `in`.surajsau.jisho
2 |
3 | import android.app.Application
4 | import dagger.hilt.android.HiltAndroidApp
5 |
6 | @HiltAndroidApp
7 | class App: Application()
--------------------------------------------------------------------------------
/app/src/main/java/in/surajsau/jisho/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package `in`.surajsau.jisho
2 |
3 | import android.os.Bundle
4 | import androidx.activity.ComponentActivity
5 | import androidx.activity.compose.setContent
6 | import dagger.hilt.android.AndroidEntryPoint
7 | import `in`.surajsau.jisho.ui.MLApp
8 | import android.content.Intent
9 | import android.net.Uri
10 | import android.provider.Settings
11 |
12 | @AndroidEntryPoint
13 | class MainActivity : ComponentActivity() {
14 |
15 | override fun onCreate(savedInstanceState: Bundle?) {
16 | super.onCreate(savedInstanceState)
17 | setContent { MLApp(
18 | navigateToSettings = {
19 | startActivity(Intent().apply {
20 | action = Settings.ACTION_APPLICATION_DETAILS_SETTINGS
21 | data = Uri.fromParts("package", BuildConfig.APPLICATION_ID, null)
22 | addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
23 | })
24 | }
25 | ) }
26 | }
27 | }
--------------------------------------------------------------------------------
/app/src/main/java/in/surajsau/jisho/base/BitmapCache.kt:
--------------------------------------------------------------------------------
1 | package `in`.surajsau.jisho.base
2 |
3 | import android.graphics.Bitmap
4 | import androidx.collection.LruCache
5 | import androidx.compose.runtime.compositionLocalOf
6 |
7 | class BitmapCache {
8 |
9 | private val maxMemory = (Runtime.getRuntime().maxMemory() / 1024).toInt()
10 |
11 | private val cache: LruCache = LruCache(maxMemory/8)
12 |
13 | fun save(fileName: String, bitmap: Bitmap) {
14 | this.cache.put(fileName, bitmap)
15 | }
16 |
17 | fun has(fileName: String): Boolean = this.cache.get(fileName) != null
18 |
19 | fun get(fileName: String) = this.cache.get(fileName) ?: throw Exception("Couldn't find $fileName")
20 |
21 | fun clear() {
22 | this.cache.evictAll()
23 | }
24 |
25 | }
26 |
27 | val LocalBitmapCache = compositionLocalOf { error("BitmapCache instance not provided") }
--------------------------------------------------------------------------------
/app/src/main/java/in/surajsau/jisho/base/BitmapUtils.kt:
--------------------------------------------------------------------------------
1 | package `in`.surajsau.jisho.base
2 |
3 | import android.graphics.Bitmap
4 | import android.graphics.Matrix
5 | import android.graphics.Rect
6 |
7 | fun Bitmap.rotate(degree: Int): Bitmap {
8 | val matrix = Matrix()
9 | matrix.postRotate(degree.toFloat())
10 | return Bitmap.createBitmap(this, 0, 0, width, height, matrix, true)
11 | }
12 |
13 | fun Rect.scale(factor: Float): Rect {
14 | val left = (this.left * factor).toInt()
15 | val right = (left + (this.width() * factor).toInt())
16 | val top = (this.top * factor).toInt()
17 | val bottom = (top + (this.height() * factor).toInt())
18 |
19 | return Rect(left, top, right, bottom)
20 | }
--------------------------------------------------------------------------------
/app/src/main/java/in/surajsau/jisho/base/BoolUtils.kt:
--------------------------------------------------------------------------------
1 | package `in`.surajsau.jisho.base
2 |
3 | fun Boolean.toInt() = if (this) 1 else 0
--------------------------------------------------------------------------------
/app/src/main/java/in/surajsau/jisho/base/ContextUtils.kt:
--------------------------------------------------------------------------------
1 | package `in`.surajsau.jisho.base
2 |
3 | import android.content.Context
4 | import android.net.Uri
5 | import androidx.core.content.FileProvider
6 | import java.io.File
7 |
8 | fun Context.getUriForFile(file: File) = FileProvider.getUriForFile(this, "$packageName.fileprovider", file)
9 |
10 | fun Context.getUriForImage(folderName: String, fileName: String): Uri {
11 | val file = File(folderName, fileName)
12 | return getUriForFile(file = file)
13 | }
--------------------------------------------------------------------------------
/app/src/main/java/in/surajsau/jisho/base/FileName.kt:
--------------------------------------------------------------------------------
1 | package `in`.surajsau.jisho.base
2 |
3 | @JvmInline
4 | value class FileName(val value: String)
--------------------------------------------------------------------------------
/app/src/main/java/in/surajsau/jisho/base/ListUtils.kt:
--------------------------------------------------------------------------------
1 | package `in`.surajsau.jisho.base
2 |
3 | fun List.subListFrom(startIndex: Int) = this.subList(startIndex, this.size - 1)
4 |
5 | fun Map.reverseMap(): Map = this.entries.associateBy({it.value}, {it.key})
--------------------------------------------------------------------------------
/app/src/main/java/in/surajsau/jisho/base/NormalizeOp.kt:
--------------------------------------------------------------------------------
1 | package `in`.surajsau.jisho.base
2 |
3 | import org.tensorflow.lite.DataType
4 | import org.tensorflow.lite.support.common.TensorOperator
5 | import org.tensorflow.lite.support.tensorbuffer.TensorBuffer
6 | import org.tensorflow.lite.support.tensorbuffer.TensorBufferFloat
7 | import kotlin.math.max
8 | import kotlin.math.pow
9 | import kotlin.math.sqrt
10 |
11 | class NormalizeOp: TensorOperator {
12 |
13 | override fun apply(input: TensorBuffer): TensorBuffer {
14 | val pixels = input.floatArray
15 | val mean = pixels.average().toFloat()
16 | val standardDeviation = sqrt(pixels.map { pixel -> (pixel - mean).pow(2) }.sum() / pixels.size.toFloat()).let {
17 | max(it, 1f/ sqrt(pixels.size.toFloat()))
18 | }
19 |
20 | val normalizedPixels = pixels.map { (it - mean)/standardDeviation }.toFloatArray()
21 | val output = TensorBufferFloat.createFixedSize(input.shape, DataType.FLOAT32)
22 | output.loadArray(normalizedPixels)
23 |
24 | return output
25 | }
26 |
27 | }
--------------------------------------------------------------------------------
/app/src/main/java/in/surajsau/jisho/base/ObserveLifecycle.kt:
--------------------------------------------------------------------------------
1 | package `in`.surajsau.jisho.base
2 |
3 | import androidx.compose.runtime.Composable
4 | import androidx.compose.runtime.DisposableEffect
5 | import androidx.compose.ui.platform.LocalLifecycleOwner
6 | import androidx.lifecycle.Lifecycle
7 | import androidx.lifecycle.LifecycleEventObserver
8 |
9 | @Composable
10 | fun ObserveLifecycle(
11 | onEvent: (Lifecycle.Event) -> Unit
12 | ) {
13 | val lifecycleOwner = LocalLifecycleOwner.current
14 |
15 | DisposableEffect(Unit) {
16 | val observer = LifecycleEventObserver { _, event -> onEvent.invoke(event)}
17 | lifecycleOwner.lifecycle.addObserver(observer)
18 |
19 | onDispose { lifecycleOwner.lifecycle.removeObserver(observer) }
20 | }
21 | }
--------------------------------------------------------------------------------
/app/src/main/java/in/surajsau/jisho/base/Optional.kt:
--------------------------------------------------------------------------------
1 | package `in`.surajsau.jisho.base
2 |
3 | sealed class Optional {
4 |
5 | data class Some(val data: T): Optional()
6 | object Empty: Optional()
7 |
8 | fun getValue(): T {
9 | if (this is Empty) {
10 | throw Exception("$this is empty")
11 | }
12 | return (this as? Some)?.data!!
13 | }
14 |
15 | companion object {
16 |
17 | fun of(value: T?): Optional {
18 | if (value == null)
19 | return Empty
20 | return Some(data = value)
21 | }
22 | }
23 | }
--------------------------------------------------------------------------------
/app/src/main/java/in/surajsau/jisho/base/SingleFlowViewModel.kt:
--------------------------------------------------------------------------------
1 | package `in`.surajsau.jisho.base
2 |
3 | import androidx.compose.runtime.Composable
4 | import androidx.compose.runtime.collectAsState
5 | import androidx.compose.runtime.getValue
6 | import androidx.compose.runtime.remember
7 | import androidx.compose.ui.platform.LocalLifecycleOwner
8 | import androidx.lifecycle.Lifecycle
9 | import androidx.lifecycle.flowWithLifecycle
10 | import kotlinx.coroutines.flow.StateFlow
11 |
12 | interface SingleFlowViewModel {
13 | val state: StateFlow
14 | fun onEvent(event: Event)
15 | }
16 |
17 | data class ViewModelComponents(
18 | val state: State,
19 | val onEvent: (Event) -> Unit
20 | )
21 |
22 | @Composable
23 | fun use(
24 | viewModel: SingleFlowViewModel,
25 | initialStateValue: State,
26 | ): ViewModelComponents {
27 |
28 | val lifecycleOwner = LocalLifecycleOwner.current
29 |
30 | val lifecycleAwareState = remember(viewModel.state, lifecycleOwner) {
31 | viewModel.state.flowWithLifecycle(lifecycleOwner.lifecycle, Lifecycle.State.STARTED)
32 | }
33 |
34 | val state = lifecycleAwareState.collectAsState(initialStateValue)
35 |
36 | return ViewModelComponents(
37 | state = state.value,
38 | onEvent = { viewModel.onEvent(it) }
39 | )
40 | }
--------------------------------------------------------------------------------
/app/src/main/java/in/surajsau/jisho/data/CardDataProvider.kt:
--------------------------------------------------------------------------------
1 | package `in`.surajsau.jisho.data
2 |
3 | import android.graphics.Bitmap
4 | import android.util.Log
5 | import com.google.mlkit.vision.common.InputImage
6 | import com.google.mlkit.vision.text.Text
7 | import com.google.mlkit.vision.text.TextRecognition
8 | import com.google.mlkit.vision.text.latin.TextRecognizerOptions
9 | import kotlinx.coroutines.channels.awaitClose
10 | import kotlinx.coroutines.flow.Flow
11 | import kotlinx.coroutines.flow.callbackFlow
12 | import javax.inject.Inject
13 |
14 | class CardDataProviderImpl @Inject constructor(): CardDataProvider {
15 |
16 | private val recognizer by lazy {
17 | TextRecognition.getClient(TextRecognizerOptions.Builder().build())
18 | }
19 |
20 | override fun identifyTexts(bitmap: Bitmap, language: CardDataProvider.Language): Flow = callbackFlow {
21 | val image = InputImage.fromBitmap(bitmap, 0)
22 | recognizer.process(image)
23 | .addOnSuccessListener {
24 | Log.e("Card", it.text)
25 | trySend(it)
26 | }
27 | .addOnFailureListener {
28 | it.printStackTrace()
29 | throw it
30 | }
31 | .addOnCompleteListener { close() }
32 |
33 | awaitClose { close() }
34 | }
35 | }
36 |
37 | interface CardDataProvider {
38 |
39 | fun identifyTexts(bitmap: Bitmap, language: Language = Language.EN): Flow
40 |
41 | enum class Language { EN, JP, HI }
42 | }
--------------------------------------------------------------------------------
/app/src/main/java/in/surajsau/jisho/data/ChatDataProvider.kt:
--------------------------------------------------------------------------------
1 | package `in`.surajsau.jisho.data
2 |
3 | import `in`.surajsau.jisho.data.model.ChatMessageModel
4 | import `in`.surajsau.jisho.domain.models.chat.ChatDetails
5 | import android.util.Log
6 | import kotlinx.coroutines.channels.Channel
7 | import kotlinx.coroutines.delay
8 | import kotlinx.coroutines.flow.Flow
9 | import kotlinx.coroutines.flow.flow
10 | import javax.inject.Inject
11 | import kotlin.random.Random
12 |
13 | class ChatDataProviderImpl @Inject constructor(): ChatDataProvider {
14 |
15 | private val randomDelay = Random(300L)
16 |
17 | override val latestMessage: Channel = Channel()
18 |
19 | override suspend fun sendMessage(
20 | message: String,
21 | imageUrl: String?,
22 | isMe: Boolean,
23 | ) {
24 | val ts = System.currentTimeMillis()
25 | val id = (ts/1000).toInt()
26 | val model = ChatMessageModel.Message(
27 | id = id,
28 | text = message,
29 | isMe = isMe,
30 | ts = ts,
31 | imageUrl = imageUrl,
32 | )
33 |
34 | // add random delay to act as an api
35 | if (!isMe) {
36 | latestMessage.trySend(ChatMessageModel.Typing(ts = ts))
37 | delay(randomDelay.nextLong(300L, 2000L))
38 | }
39 |
40 | latestMessage.trySend(model)
41 | }
42 |
43 | override fun fetchChatDetails(): Flow = flow {
44 | emit(
45 | ChatDetails(
46 | chatName = "レオレウス",
47 | chatIconUrl = "https://i.pinimg.com/originals/76/99/4b/76994b84e7e7f53a4cf1a5a4c52736d4.jpg"
48 | )
49 | )
50 | }
51 | }
52 |
53 | interface ChatDataProvider {
54 |
55 | val latestMessage: Channel
56 |
57 | suspend fun sendMessage(
58 | message: String,
59 | imageUrl: String? = null,
60 | isMe: Boolean
61 | )
62 |
63 | fun fetchChatDetails(): Flow
64 | }
--------------------------------------------------------------------------------
/app/src/main/java/in/surajsau/jisho/data/DigitalInkProvider.kt:
--------------------------------------------------------------------------------
1 | package `in`.surajsau.jisho.data
2 |
3 | import `in`.surajsau.jisho.ui.digitalink.MLKitModelStatus
4 | import com.google.mlkit.common.model.DownloadConditions
5 | import com.google.mlkit.common.model.RemoteModelManager
6 | import com.google.mlkit.vision.digitalink.*
7 | import kotlinx.coroutines.cancel
8 | import kotlinx.coroutines.channels.Channel
9 | import kotlinx.coroutines.channels.awaitClose
10 | import kotlinx.coroutines.flow.Flow
11 | import kotlinx.coroutines.flow.callbackFlow
12 | import javax.inject.Inject
13 |
14 | class DigitalInkProviderImpl @Inject constructor(): DigitalInkProvider {
15 |
16 | override val predictions = Channel>(4)
17 |
18 | private var strokeBuilder: Ink.Stroke.Builder = Ink.Stroke.builder()
19 |
20 | private val recognitionModel = DigitalInkRecognitionModel
21 | .builder(DigitalInkRecognitionModelIdentifier.JA)
22 | .build()
23 |
24 | private val remoteModelManager = RemoteModelManager.getInstance()
25 |
26 | private val recognizer = DigitalInkRecognition.getClient(
27 | DigitalInkRecognizerOptions
28 | .builder(this.recognitionModel)
29 | .build()
30 | )
31 |
32 | override fun checkIfModelIsDownlaoded(): Flow = callbackFlow {
33 | trySend(MLKitModelStatus.CheckingDownload)
34 |
35 | this@DigitalInkProviderImpl.remoteModelManager
36 | .isModelDownloaded(this@DigitalInkProviderImpl.recognitionModel)
37 | .addOnSuccessListener { isDownloaded ->
38 | if (isDownloaded)
39 | trySend(MLKitModelStatus.Downloaded)
40 | else
41 | trySend(MLKitModelStatus.NotDownloaded)
42 | }
43 | .addOnCompleteListener { close() }
44 | .addOnFailureListener {
45 | it.printStackTrace()
46 | close(it)
47 | }
48 |
49 | awaitClose { cancel() }
50 | }
51 |
52 | override fun downloadModel(): Flow = callbackFlow {
53 | val downloadConditions = DownloadConditions.Builder()
54 | .build()
55 |
56 | trySend(MLKitModelStatus.Downloading)
57 | this@DigitalInkProviderImpl.remoteModelManager
58 | .download(this@DigitalInkProviderImpl.recognitionModel, downloadConditions)
59 | .addOnSuccessListener {
60 | trySend(MLKitModelStatus.Downloaded)
61 | }
62 | .addOnCompleteListener { close() }
63 | .addOnFailureListener {
64 | it.printStackTrace()
65 | close(it)
66 | }
67 |
68 | awaitClose { cancel() }
69 | }
70 |
71 | override fun record(x: Float, y: Float) {
72 | val point = Ink.Point.create(x, y)
73 | this.strokeBuilder.addPoint(point)
74 | }
75 |
76 | override fun finishRecording() {
77 | val stroke = this.strokeBuilder.build()
78 |
79 | val inkBuilder = Ink.builder()
80 | inkBuilder.addStroke(stroke)
81 |
82 | this.recognizer.recognize(inkBuilder.build())
83 | .addOnCompleteListener {
84 | this.strokeBuilder = Ink.Stroke.builder()
85 | }
86 | .addOnSuccessListener { result -> this.predictions.trySend(result.candidates.map { it.text }) }
87 | .addOnFailureListener { it.printStackTrace() }
88 | }
89 |
90 | override fun close() {
91 | this.recognizer.close()
92 | }
93 | }
94 |
95 | interface DigitalInkProvider {
96 |
97 | val predictions: Channel>
98 |
99 | fun finishRecording()
100 | fun record(x: Float, y: Float)
101 |
102 | fun downloadModel(): Flow
103 | fun checkIfModelIsDownlaoded(): Flow
104 |
105 | fun close()
106 | }
--------------------------------------------------------------------------------
/app/src/main/java/in/surajsau/jisho/data/FileProvider.kt:
--------------------------------------------------------------------------------
1 | package `in`.surajsau.jisho.data
2 |
3 | import `in`.surajsau.jisho.base.getUriForFile
4 | import `in`.surajsau.jisho.base.rotate
5 | import android.content.Context
6 | import android.graphics.Bitmap
7 | import android.graphics.BitmapFactory
8 | import android.media.ExifInterface
9 | import android.os.Environment
10 | import android.util.JsonReader
11 | import kotlinx.coroutines.flow.Flow
12 | import kotlinx.coroutines.flow.flow
13 | import org.tensorflow.lite.Interpreter
14 | import java.io.*
15 | import java.lang.StringBuilder
16 | import java.nio.channels.FileChannel
17 | import javax.inject.Inject
18 |
19 | class FileProviderImpl @Inject constructor(private val context: Context): FileProvider {
20 |
21 | override suspend fun fetchCachedBitmap(fileName: String): Bitmap {
22 | val cacheDir = context.externalCacheDir ?: throw Exception("external cache not found")
23 | val bitmap = getBitmapForFile(File(cacheDir, fileName))
24 | return bitmap.getOrElse { throw it }
25 | }
26 |
27 | override suspend fun cacheBitmap(bitmap: Bitmap): String {
28 | val cacheDir = context.externalCacheDir ?: throw Exception("external cache not found")
29 | val fileName = "${System.currentTimeMillis()}.jpg"
30 |
31 | val imageFile = File(cacheDir, fileName)
32 |
33 | runCatching {
34 | val os = imageFile.outputStream()
35 | bitmap.compress(Bitmap.CompressFormat.JPEG, 100, os)
36 | os.close()
37 | }.onFailure { throw it }
38 |
39 | return fileName
40 | }
41 |
42 | override suspend fun storeBitmap(folderName: String, fileName: String, bitmap: Bitmap) {
43 | val storageFolder = File(context.filesDir, folderName)
44 | if (!storageFolder.exists())
45 | storageFolder.mkdirs()
46 |
47 | val imageFile = File(storageFolder, fileName)
48 |
49 | val uri = context.getUriForFile(imageFile)
50 | runCatching {
51 | val os = context.contentResolver.openOutputStream(uri) ?: throw Exception("Couldn't open OutpuStream")
52 | bitmap.compress(Bitmap.CompressFormat.JPEG, 100, os)
53 | os.close()
54 | }.onFailure { throw it }
55 | }
56 |
57 | override fun getCacheFilePath(fileName: String): String {
58 | val cacheDir = context.externalCacheDir ?: throw Exception("external cache not found")
59 | return File(cacheDir, fileName).absolutePath
60 | }
61 |
62 | override fun getFilePath(folderName: String, fileName: String): String {
63 | val storageFolder = File(context.filesDir, folderName)
64 | return File(storageFolder, fileName).absolutePath
65 | }
66 |
67 | override fun fetchAssetBitmap(fileName: String): Flow = flow {
68 | val bitmap = runCatching {
69 | with(context.assets.open(fileName)) { BitmapFactory.decodeStream(this) }
70 | }
71 |
72 | emit(bitmap.getOrElse { throw it })
73 | }
74 |
75 | override fun fetchAssetInputStream(fileName: String) = context.assets.open(fileName)
76 |
77 | override fun fetchInterpreter(modelFileName: String): Interpreter {
78 | val assetFileDescriptor = context.assets.openFd(modelFileName)
79 |
80 | val fileChannel = FileInputStream(assetFileDescriptor.fileDescriptor).channel
81 | val fileByteBuffer = fileChannel.map(FileChannel.MapMode.READ_ONLY, assetFileDescriptor.startOffset, assetFileDescriptor.declaredLength)
82 |
83 | return Interpreter(fileByteBuffer, Interpreter.Options().apply { setNumThreads(4) })
84 | }
85 |
86 | override fun fetchFiles(folderName: String): Flow> = flow {
87 | val storageFolder = File(context.filesDir, folderName)
88 | if (!storageFolder.exists())
89 | storageFolder.mkdirs()
90 |
91 | val files = storageFolder.listFiles()
92 | if (files.isNullOrEmpty())
93 | emit(emptyList())
94 | else {
95 | emit(files.map { it.absolutePath })
96 | }
97 | }
98 |
99 | private fun getBitmapForFile(file: File): Result = runCatching {
100 | val exif = ExifInterface(file.absolutePath)
101 | val inputStream = FileInputStream(file)
102 | val fileBitmap = BitmapFactory.decodeStream(inputStream)
103 |
104 | return@runCatching when (exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL)) {
105 | ExifInterface.ORIENTATION_ROTATE_90 -> fileBitmap.rotate(90)
106 | ExifInterface.ORIENTATION_ROTATE_180 -> fileBitmap.rotate(180)
107 | ExifInterface.ORIENTATION_ROTATE_270 -> fileBitmap.rotate(270)
108 | else -> fileBitmap
109 | }
110 | }.onFailure { it.printStackTrace() }
111 |
112 | override suspend fun writeStringToFile(folderName: String, fileName: String, string: String) {
113 | val storageFolder = File(context.filesDir, folderName)
114 |
115 | if (!storageFolder.exists())
116 | storageFolder.mkdirs()
117 |
118 | val file = File(storageFolder, fileName)
119 |
120 | val uri = context.getUriForFile(file)
121 | runCatching {
122 | val os = context.contentResolver.openOutputStream(uri) ?: throw Exception("Couldn't open OutpuStream")
123 | val osw = OutputStreamWriter(os)
124 | val bw = BufferedWriter(osw)
125 | bw.newLine()
126 | bw.write(string)
127 |
128 | bw.close()
129 | osw.close()
130 | os.close()
131 | }.onFailure { throw it }
132 | }
133 |
134 | override suspend fun readStringFromFile(folderName: String, fileName: String): List {
135 | val storageFolder = File(context.filesDir, folderName)
136 |
137 | if (!storageFolder.exists())
138 | storageFolder.mkdirs()
139 |
140 | val file = File(storageFolder, fileName)
141 | val result = mutableListOf()
142 |
143 | runCatching {
144 | val br = BufferedReader(FileReader(file))
145 | var line = br.readLine()
146 | while(line != null) {
147 | result.add(line)
148 | line = br.readLine()
149 | }
150 | br.close()
151 | }.onFailure {
152 | it.printStackTrace()
153 | return emptyList()
154 | }
155 |
156 | return result
157 | }
158 | }
159 |
160 | interface FileProvider {
161 |
162 | fun fetchFiles(folderName: String): Flow>
163 |
164 | suspend fun fetchCachedBitmap(fileName: String): Bitmap
165 |
166 | fun fetchAssetBitmap(fileName: String): Flow
167 |
168 | fun fetchAssetInputStream(fileName: String): InputStream
169 |
170 | fun fetchInterpreter(modelFileName: String): Interpreter
171 |
172 | suspend fun cacheBitmap(bitmap: Bitmap): String
173 |
174 | suspend fun storeBitmap(folderName: String, fileName: String, bitmap: Bitmap)
175 |
176 | fun getCacheFilePath(fileName: String): String
177 |
178 | fun getFilePath(folderName: String, fileName: String): String
179 |
180 | suspend fun writeStringToFile(folderName: String, fileName: String, string: String)
181 |
182 | suspend fun readStringFromFile(folderName: String, fileName: String): List
183 |
184 | companion object {
185 | const val FACENET_IMAGE_FOLDER = "images/faces/"
186 | }
187 | }
--------------------------------------------------------------------------------
/app/src/main/java/in/surajsau/jisho/data/ScreensDataProvider.kt:
--------------------------------------------------------------------------------
1 | package `in`.surajsau.jisho.data
2 |
3 | import `in`.surajsau.jisho.data.model.Screen
4 | import `in`.surajsau.jisho.ui.Destinations
5 | import kotlinx.coroutines.flow.Flow
6 | import kotlinx.coroutines.flow.flow
7 | import javax.inject.Inject
8 |
9 | class ScreensDataProviderImpl @Inject constructor(): ScreensDataProvider {
10 |
11 | override fun fetchScreen(): Flow> = flow {
12 | emit(listOf(
13 | Screen(
14 | destinations = Destinations.DigitalInk,
15 | previewImage = "https://github.com/surajsau/ML-Android/raw/main/screenshots/translate_app.gif",
16 | title = "On-device Google Translate",
17 | description = "Character Recognition & Translation using Google MLKit",
18 | tags = listOf("MLKit")
19 | ),
20 |
21 | Screen(
22 | destinations = Destinations.StyleTransfer,
23 | previewImage = "",
24 | title = "Style Transfer",
25 | description = "Character Recognition & Translation using Google MLKit",
26 | tags = listOf("TFLite")
27 | ),
28 |
29 | Screen(
30 | destinations = Destinations.SmartChat,
31 | previewImage = "",
32 | title = "Smart Chat",
33 | description = "Using MLKit Smart Replies, MLKit Entity Extraction and Image Classification",
34 | tags = listOf("MLKit", "TorchVision")
35 | ),
36 |
37 | Screen(
38 | destinations = Destinations.Facenet,
39 | previewImage = "https://github.com/surajsau/ML-Android/raw/main/screenshots/face.gif",
40 | title = "On-device Face Recognition & Classification",
41 | description = "Using MLKit's FaceDetection to crop out face and using Sirius-AI's MobileFacenet tflite for recognition",
42 | tags = listOf("MLKit", "TFLite")
43 | ),
44 |
45 | Screen(
46 | destinations = Destinations.CardReader,
47 | previewImage = "https://github.com/surajsau/ML-Android/raw/main/screenshots/card_reader.gif",
48 | title = "Card Reader",
49 | description = "Using MLKit's Text Recognition",
50 | tags = listOf("MLKit")
51 | )
52 | )
53 | )
54 | }
55 | }
56 |
57 | interface ScreensDataProvider {
58 |
59 | fun fetchScreen(): Flow>
60 | }
61 |
--------------------------------------------------------------------------------
/app/src/main/java/in/surajsau/jisho/data/StyleTransferProvider.kt:
--------------------------------------------------------------------------------
1 | package `in`.surajsau.jisho.data
2 |
3 | import `in`.surajsau.jisho.ml.MagentaStylePredictor
4 | import `in`.surajsau.jisho.ml.MagentaStyleTransfer
5 | import `in`.surajsau.jisho.ml.StylePredictorModel
6 | import `in`.surajsau.jisho.ml.StyleTransferModel
7 | import android.content.Context
8 | import android.graphics.Bitmap
9 | import kotlinx.coroutines.channels.Channel
10 | import kotlinx.coroutines.flow.Flow
11 | import kotlinx.coroutines.flow.flow
12 | import org.tensorflow.lite.DataType
13 | import org.tensorflow.lite.support.image.TensorImage
14 | import org.tensorflow.lite.support.tensorbuffer.TensorBuffer
15 | import javax.inject.Inject
16 |
17 | class StyleTransferProviderImpl @Inject constructor(
18 | private val context: Context,
19 | ): StyleTransferProvider {
20 |
21 | private val stylePredictorModel = MagentaStylePredictor.newInstance(context)
22 | private val styleTransferModel = MagentaStyleTransfer.newInstance(context)
23 |
24 | override fun process(targetImage: Bitmap, styleImage: Bitmap): Flow = flow {
25 | emit(StyleTransferProvider.StyleTransferState.Started)
26 |
27 | val styleTensor = TensorImage.fromBitmap(styleImage)
28 | val targetTensor = TensorImage.fromBitmap(targetImage)
29 | val styleOutput = stylePredictorModel.process(styleTensor).styleBottleneckAsTensorBuffer
30 |
31 | val styleBottleneck = TensorBuffer.createFixedSize(intArrayOf(1, 1, 1, StyleTransferProvider.BottleNeckSize), DataType.FLOAT32).apply {
32 | loadBuffer(styleOutput.buffer)
33 | }
34 |
35 | val output = styleTransferModel.process(targetTensor, styleBottleneck).styledImageAsTensorImage
36 | emit(StyleTransferProvider.StyleTransferState.Finished(output.bitmap))
37 | }
38 | }
39 |
40 | interface StyleTransferProvider {
41 |
42 | fun process(targetImage: Bitmap, styleImage: Bitmap): Flow
43 |
44 | sealed class StyleTransferState {
45 | object Idle: StyleTransferState()
46 | object Started: StyleTransferState()
47 | data class Finished(val image: Bitmap): StyleTransferState()
48 | }
49 |
50 | companion object {
51 | const val BottleNeckSize = 100
52 | const val StyleImageSize = 256
53 | const val ContentImageSize = 384
54 |
55 | val Styles = (0..5).map { "style$it.jpg" }
56 | }
57 | }
--------------------------------------------------------------------------------
/app/src/main/java/in/surajsau/jisho/data/TranslatorProvider.kt:
--------------------------------------------------------------------------------
1 | package `in`.surajsau.jisho.data
2 |
3 | import `in`.surajsau.jisho.ui.digitalink.MLKitModelStatus
4 | import com.google.mlkit.common.model.DownloadConditions
5 | import com.google.mlkit.nl.translate.TranslateLanguage
6 | import com.google.mlkit.nl.translate.Translation
7 | import com.google.mlkit.nl.translate.TranslatorOptions
8 | import kotlinx.coroutines.cancel
9 | import kotlinx.coroutines.channels.Channel
10 | import kotlinx.coroutines.channels.awaitClose
11 | import kotlinx.coroutines.flow.Flow
12 | import kotlinx.coroutines.flow.callbackFlow
13 | import javax.inject.Inject
14 |
15 | class TranslatorProviderImpl @Inject constructor(): TranslatorProvider {
16 |
17 | override val translation = Channel(4)
18 |
19 | private val translatorOptions = TranslatorOptions.Builder()
20 | .setSourceLanguage(TranslateLanguage.JAPANESE)
21 | .setTargetLanguage(TranslateLanguage.ENGLISH)
22 | .build()
23 |
24 | private val translator = Translation.getClient(this.translatorOptions)
25 |
26 | override fun checkIfModelIsDownloaded(): Flow = callbackFlow {
27 | trySend(MLKitModelStatus.CheckingDownload)
28 | val downloadConditions = DownloadConditions.Builder()
29 | .build()
30 |
31 | this@TranslatorProviderImpl.translator.downloadModelIfNeeded(downloadConditions)
32 | .addOnSuccessListener {
33 | trySend(MLKitModelStatus.Downloaded)
34 | }
35 | .addOnCompleteListener { close() }
36 | .addOnFailureListener {
37 | it.printStackTrace()
38 | close(it)
39 | }
40 |
41 | awaitClose { cancel() }
42 | }
43 |
44 | override fun translate(text: String) {
45 | this.translator.translate(text)
46 | .addOnSuccessListener { this.translation.trySend(it) }
47 | .addOnFailureListener { it.printStackTrace() }
48 | }
49 |
50 | override fun close() {
51 | this.translator.close()
52 | }
53 | }
54 |
55 | interface TranslatorProvider {
56 |
57 | val translation: Channel
58 |
59 | fun checkIfModelIsDownloaded(): Flow
60 | fun translate(text: String)
61 |
62 | fun close()
63 | }
--------------------------------------------------------------------------------
/app/src/main/java/in/surajsau/jisho/data/chat/EmojiLabellingDataProvider.kt:
--------------------------------------------------------------------------------
1 | package `in`.surajsau.jisho.data.chat
2 |
3 | import `in`.surajsau.jisho.data.FileProvider
4 | import `in`.surajsau.jisho.data.model.Emoji
5 | import android.content.Context
6 | import android.graphics.Bitmap
7 | import android.util.Log
8 | import com.google.gson.Gson
9 | import com.google.gson.reflect.TypeToken
10 | import com.google.mlkit.vision.common.InputImage
11 | import com.google.mlkit.vision.label.ImageLabeling
12 | import com.google.mlkit.vision.label.defaults.ImageLabelerOptions
13 | import kotlinx.coroutines.cancel
14 | import kotlinx.coroutines.channels.awaitClose
15 | import kotlinx.coroutines.flow.Flow
16 | import kotlinx.coroutines.flow.callbackFlow
17 | import javax.inject.Inject
18 |
19 | class EmojiLabellingDataProviderImpl @Inject constructor(
20 | private val fileProvider: FileProvider
21 | ): EmojiLabellingDataProvider {
22 |
23 | private var emojis: List = emptyList()
24 |
25 | private val labeller by lazy {
26 | ImageLabeling.getClient(ImageLabelerOptions.Builder().build())
27 | }
28 |
29 | override suspend fun loadEmojis() {
30 | runCatching {
31 | val gson = Gson()
32 | val inputStream = fileProvider.fetchAssetInputStream("emojis.json")
33 | val jsonString = inputStream.bufferedReader().use { it.readText() }
34 |
35 | val listEmojiType = object: TypeToken>(){}.type
36 | emojis = gson.fromJson(jsonString, listEmojiType)
37 | }.onFailure { it.printStackTrace() }
38 | }
39 |
40 | override fun getEmojis(bitmap: Bitmap): Flow> = callbackFlow {
41 | val inputImage = InputImage.fromBitmap(bitmap, 0)
42 | labeller.process(inputImage)
43 | .addOnSuccessListener { labels ->
44 | val result = mutableListOf()
45 | labels
46 | .filter { label -> label.confidence > 0.4 }
47 | .forEach { label ->
48 | val emoji = emojis.firstOrNull { it.label == label.text && it.emojis.isNotEmpty() } ?: return@forEach
49 | result.add(emoji)
50 | }
51 |
52 | trySend(result)
53 | }
54 | .addOnFailureListener {
55 | it.printStackTrace()
56 | cancel("getEmojis() failed", it)
57 | }
58 | .addOnCompleteListener { close() }
59 |
60 | awaitClose { close() }
61 | }
62 |
63 | }
64 |
65 | interface EmojiLabellingDataProvider {
66 |
67 | suspend fun loadEmojis()
68 |
69 | fun getEmojis(bitmap: Bitmap): Flow>
70 | }
--------------------------------------------------------------------------------
/app/src/main/java/in/surajsau/jisho/data/chat/EntityExtractionProvider.kt:
--------------------------------------------------------------------------------
1 | package `in`.surajsau.jisho.data.chat
2 |
3 | import `in`.surajsau.jisho.ui.digitalink.MLKitModelStatus
4 | import com.google.android.gms.tasks.Tasks
5 | import com.google.mlkit.common.model.DownloadConditions
6 | import com.google.mlkit.nl.entityextraction.EntityAnnotation
7 | import com.google.mlkit.nl.entityextraction.EntityExtraction
8 | import com.google.mlkit.nl.entityextraction.EntityExtractionParams
9 | import com.google.mlkit.nl.entityextraction.EntityExtractorOptions
10 | import kotlinx.coroutines.channels.awaitClose
11 | import kotlinx.coroutines.flow.Flow
12 | import kotlinx.coroutines.flow.callbackFlow
13 | import java.util.*
14 | import javax.inject.Inject
15 |
16 | class EntityExtractionpProviderImpl @Inject constructor(): EntityExtractionProvider {
17 |
18 | private val extractor by lazy {
19 | EntityExtraction.getClient(EntityExtractorOptions.Builder(EntityExtractorOptions.ENGLISH).build())
20 | }
21 |
22 | override fun initModel(): Flow = callbackFlow {
23 | trySend(MLKitModelStatus.CheckingDownload)
24 |
25 | val downloadConditions = DownloadConditions.Builder()
26 | .requireWifi()
27 | .build()
28 |
29 | extractor.downloadModelIfNeeded(downloadConditions)
30 | .addOnSuccessListener { trySend(MLKitModelStatus.Downloaded) }
31 | .addOnFailureListener {
32 | it.printStackTrace()
33 | throw it
34 | }
35 | .addOnCompleteListener { close() }
36 |
37 | awaitClose { close() }
38 | }
39 |
40 | override suspend fun extractEntities(text: String): List {
41 | val extractionParams = EntityExtractionParams.Builder(text)
42 | .build()
43 |
44 | return Tasks.await(extractor.annotate(extractionParams))
45 | }
46 |
47 |
48 | }
49 |
50 | interface EntityExtractionProvider {
51 |
52 | fun initModel(): Flow
53 |
54 | suspend fun extractEntities(text: String): List
55 | }
--------------------------------------------------------------------------------
/app/src/main/java/in/surajsau/jisho/data/chat/ReplySuggestionProvider.kt:
--------------------------------------------------------------------------------
1 | package `in`.surajsau.jisho.data.chat
2 |
3 | import `in`.surajsau.jisho.data.model.ChatMessageModel
4 | import com.google.android.gms.tasks.Tasks
5 | import com.google.mlkit.nl.smartreply.*
6 | import kotlinx.coroutines.channels.Channel
7 | import javax.inject.Inject
8 |
9 | class ReplySuggestionProviderImpl @Inject constructor() : ReplySuggestionProvider {
10 |
11 | private val smartReplyGenerator by lazy {
12 | SmartReply.getClient(SmartReplyGeneratorOptions.Builder().build())
13 | }
14 |
15 | private val conversation = mutableListOf()
16 |
17 | override val suggestions: Channel> = Channel()
18 |
19 | override suspend fun addMessage(message: ChatMessageModel.Message) {
20 | conversation.add(
21 | if (message.isMe)
22 | TextMessage.createForLocalUser(message.text, message.timeStamp)
23 | else
24 | TextMessage.createForRemoteUser(message.text, message.timeStamp, "1")
25 | )
26 |
27 | if (!message.isMe) {
28 | val task = smartReplyGenerator.suggestReplies(conversation)
29 | val result = Tasks.await(task)
30 |
31 | suggestions.trySend(result.suggestions)
32 | }
33 | }
34 |
35 | override fun clearConversation() {
36 | conversation.clear()
37 | }
38 | }
39 |
40 | interface ReplySuggestionProvider {
41 |
42 | val suggestions: Channel>
43 |
44 | suspend fun addMessage(message: ChatMessageModel.Message)
45 |
46 | fun clearConversation()
47 | }
--------------------------------------------------------------------------------
/app/src/main/java/in/surajsau/jisho/data/db/AppDb.kt:
--------------------------------------------------------------------------------
1 | package `in`.surajsau.jisho.data.db
2 |
3 | import androidx.room.Database
4 | import androidx.room.RoomDatabase
5 |
6 | @Database(entities = [FaceImage::class], version = AppDb.VERSION)
7 | abstract class AppDb: RoomDatabase() {
8 |
9 | abstract fun faceDao(): FacenetDAO
10 |
11 | companion object {
12 | const val FILE_NAME = "_ml_android_"
13 | const val VERSION = 1
14 | }
15 | }
--------------------------------------------------------------------------------
/app/src/main/java/in/surajsau/jisho/data/db/FaceImage.kt:
--------------------------------------------------------------------------------
1 | package `in`.surajsau.jisho.data.db
2 |
3 | import androidx.room.ColumnInfo
4 | import androidx.room.Entity
5 | import androidx.room.PrimaryKey
6 |
7 | @Entity
8 | data class FaceImage(
9 | @PrimaryKey(autoGenerate = true)
10 | val uid: Int,
11 |
12 | @ColumnInfo(name = "isPrimary") val isPrimary: Boolean,
13 | @ColumnInfo(name = "fileName") val fileName: String,
14 | @ColumnInfo(name = "faceName") val faceName: String
15 | ) {
16 | constructor(isPrimary: Boolean, fileName: String, faceName: String): this(0, isPrimary, fileName, faceName)
17 | }
--------------------------------------------------------------------------------
/app/src/main/java/in/surajsau/jisho/data/db/FacenetDAO.kt:
--------------------------------------------------------------------------------
1 | package `in`.surajsau.jisho.data.db
2 |
3 | import androidx.room.Dao
4 | import androidx.room.Insert
5 | import androidx.room.OnConflictStrategy
6 | import androidx.room.Query
7 |
8 | @Dao
9 | interface FacenetDAO {
10 |
11 | @Query("SELECT * FROM faceimage WHERE isPrimary = 1")
12 | suspend fun fetchAllFaces(): List
13 |
14 | @Query("SELECT * FROM faceimage WHERE faceName = :name AND isPrimary = :isPrimary")
15 | suspend fun fetchImagesFor(name: String, isPrimary: Int): List
16 |
17 | @Query("SELECT * FROM faceimage WHERE faceName = :name")
18 | suspend fun fetchImagesFor(name: String): List
19 |
20 | @Query("SELECT * FROM faceimage WHERE isPrimary = :isPrimary")
21 | suspend fun fetchAllImages(isPrimary: Int): List
22 |
23 | @Query("SELECT DISTINCT faceName FROM faceimage")
24 | suspend fun getFaceNames(): List
25 |
26 | @Insert(onConflict = OnConflictStrategy.REPLACE)
27 | suspend fun saveFace(face: FaceImage)
28 | }
29 |
--------------------------------------------------------------------------------
/app/src/main/java/in/surajsau/jisho/data/facenet/FaceDetectionProvider.kt:
--------------------------------------------------------------------------------
1 | package `in`.surajsau.jisho.data.facenet
2 |
3 | import android.graphics.Bitmap
4 | import android.util.Log
5 | import androidx.camera.core.ExperimentalGetImage
6 | import androidx.camera.core.ImageProxy
7 | import com.google.mlkit.vision.common.InputImage
8 | import com.google.mlkit.vision.face.Face
9 | import com.google.mlkit.vision.face.FaceDetection
10 | import kotlinx.coroutines.cancel
11 | import kotlinx.coroutines.channels.awaitClose
12 | import kotlinx.coroutines.flow.Flow
13 | import kotlinx.coroutines.flow.callbackFlow
14 | import kotlinx.coroutines.flow.map
15 | import javax.inject.Inject
16 |
17 | class FaceDetectionProviderImpl @Inject constructor(): FaceDetectionProvider {
18 |
19 | val detector by lazy { FaceDetection.getClient() }
20 |
21 | override fun getFaces(bitmap: Bitmap): Flow> {
22 | val image = InputImage.fromBitmap(bitmap, 0)
23 | return detectFacesFlow(image)
24 | .map { faces ->
25 | faces.map { face ->
26 | Bitmap.createBitmap(
27 | bitmap,
28 | face.boundingBox.left,
29 | face.boundingBox.top,
30 | face.boundingBox.width(),
31 | face.boundingBox.height()
32 | )
33 | }
34 | }
35 | }
36 |
37 | private fun detectFacesFlow(image: InputImage?): Flow> = callbackFlow {
38 | if (image == null)
39 | close(Exception("Image not found"))
40 |
41 | detector.process(image!!)
42 | .addOnFailureListener {
43 | it.printStackTrace()
44 | throw it
45 | }
46 | .addOnSuccessListener { faces -> trySend(faces) }
47 | .addOnCompleteListener {
48 | Log.e("Facenet", "Image detection completed")
49 | close()
50 | }
51 |
52 | awaitClose { close() }
53 | }
54 |
55 | }
56 |
57 | interface FaceDetectionProvider {
58 |
59 | fun getFaces(bitmap: Bitmap): Flow>
60 | }
--------------------------------------------------------------------------------
/app/src/main/java/in/surajsau/jisho/data/facenet/FaceRecognitionProvider.kt:
--------------------------------------------------------------------------------
1 | package `in`.surajsau.jisho.data.facenet
2 |
3 | import `in`.surajsau.jisho.base.NormalizeOp
4 | import `in`.surajsau.jisho.data.FileProvider
5 | import `in`.surajsau.jisho.ml.Facenet
6 | import `in`.surajsau.jisho.ui.digitalink.MLKitModelStatus
7 | import android.content.Context
8 | import android.graphics.Bitmap
9 | import android.util.Log
10 | import android.view.ContextMenu
11 | import kotlinx.coroutines.flow.Flow
12 | import kotlinx.coroutines.flow.flow
13 | import org.tensorflow.lite.support.image.ImageProcessor
14 | import org.tensorflow.lite.support.image.TensorImage
15 | import org.tensorflow.lite.support.image.ops.ResizeOp
16 | import javax.inject.Inject
17 | import javax.inject.Singleton
18 | import kotlin.math.pow
19 | import kotlin.math.sqrt
20 |
21 | class FaceRecognitionProviderImpl @Inject constructor(
22 | private val context: Context,
23 | private val fileProvider: FileProvider,
24 | ): FaceRecognitionProvider {
25 |
26 | private lateinit var facenet: Facenet
27 |
28 | val embeddings = mutableMapOf>()
29 |
30 | override suspend fun loadEmbeddingFor(faceName: String) {
31 | val lines = fileProvider.readStringFromFile(
32 | folderName = FaceRecognitionProvider.FACENET_EMBEDDINGS_FOLDER,
33 | fileName = faceName
34 | )
35 | val embedding = lines
36 | .filter { it.isNotEmpty() }
37 | .map { line -> line.split(":").map { it.toFloat() }.toFloatArray() }
38 | embeddings[faceName] = embedding
39 | }
40 |
41 | private val imageProcessor = ImageProcessor.Builder()
42 | .add(ResizeOp(160, 160, ResizeOp.ResizeMethod.BILINEAR))
43 | .add(NormalizeOp())
44 | .build()
45 |
46 | override suspend fun generateEmbedding(bitmap: Bitmap): FloatArray {
47 | val input = imageProcessor.process(TensorImage.fromBitmap(bitmap))
48 | val output = facenet.process(input.tensorBuffer)
49 |
50 | return output.outputFeature0AsTensorBuffer.floatArray
51 | }
52 |
53 | override suspend fun saveEmbedding(faceName: String, embedding: FloatArray) {
54 | fileProvider.writeStringToFile(
55 | folderName = FaceRecognitionProvider.FACENET_EMBEDDINGS_FOLDER,
56 | fileName = faceName,
57 | string = embedding.joinToString(":")
58 | )
59 |
60 | if (embeddings.containsKey(faceName))
61 | embeddings[faceName]!!.toMutableList().add(embedding)
62 | else
63 | embeddings[faceName] = mutableListOf().apply { add(embedding) }
64 | }
65 |
66 | override suspend fun compareEmbedding(embedding: FloatArray): Map> {
67 | return embeddings.mapValues { entry -> entry.value.map { cosineSimilarity(embedding, it) }}
68 | }
69 |
70 | override fun close() {
71 | facenet.close()
72 | embeddings.clear()
73 | }
74 |
75 | override fun initiate() {
76 | facenet = Facenet.newInstance(context)
77 | }
78 |
79 | private fun l2Similarity(embedding1: FloatArray, embedding2: FloatArray): Float {
80 | return sqrt(embedding1.mapIndexed { i, e1 -> (e1 * embedding2[i]).pow(2) }.sum())
81 | }
82 |
83 | private fun cosineSimilarity(embedding1: FloatArray, embedding2: FloatArray): Float {
84 | val magnitude1 = sqrt(embedding1.map { it * it }.sum())
85 | val magnitude2 = sqrt(embedding2.map { it * it }.sum())
86 |
87 | val dot = embedding1.mapIndexed { i, e1 -> e1 * embedding2[i] }.sum()
88 |
89 | return (dot/magnitude1 * magnitude2)
90 | }
91 |
92 | }
93 |
94 | interface FaceRecognitionProvider {
95 |
96 | suspend fun loadEmbeddingFor(faceName: String)
97 |
98 | suspend fun saveEmbedding(faceName: String, embedding: FloatArray)
99 |
100 | suspend fun generateEmbedding(bitmap: Bitmap): FloatArray
101 |
102 | suspend fun compareEmbedding(embedding: FloatArray): Map>
103 |
104 | fun close()
105 |
106 | fun initiate()
107 |
108 | companion object {
109 | const val FACENET_EMBEDDINGS_FOLDER = "embeddings/faces/"
110 | }
111 | }
--------------------------------------------------------------------------------
/app/src/main/java/in/surajsau/jisho/data/facenet/FacesDataProvider.kt:
--------------------------------------------------------------------------------
1 | package `in`.surajsau.jisho.data.facenet
2 |
3 | import `in`.surajsau.jisho.base.toInt
4 | import `in`.surajsau.jisho.data.db.FaceImage
5 | import `in`.surajsau.jisho.data.db.FacenetDAO
6 | import kotlinx.coroutines.flow.Flow
7 | import kotlinx.coroutines.flow.flow
8 | import javax.inject.Inject
9 |
10 | class FacesDataProviderImpl @Inject constructor(private val dao: FacenetDAO) : FacesDataProvider {
11 |
12 | override fun getImagesForFace(faceName: String, onlyPrimary: Boolean): Flow> = flow {
13 | val faces = if (onlyPrimary)
14 | dao.fetchImagesFor(name = faceName, isPrimary = true.toInt())
15 | else
16 | dao.fetchImagesFor(name = faceName)
17 | emit(faces)
18 | }
19 |
20 | override fun getAllImages(isPrimary: Boolean): Flow> = flow {
21 | val faces = dao.fetchAllImages(isPrimary = isPrimary.toInt())
22 | emit(faces)
23 | }
24 |
25 | override suspend fun saveFace(faceName: String, fileName: String, isPrimary: Boolean) {
26 | val face = FaceImage(isPrimary = isPrimary, faceName = faceName, fileName = fileName)
27 | dao.saveFace(face)
28 | }
29 |
30 | override suspend fun getFaceNames(): List {
31 | return dao.getFaceNames()
32 | }
33 | }
34 |
35 | interface FacesDataProvider {
36 |
37 | suspend fun saveFace(faceName: String, fileName: String, isPrimary: Boolean)
38 |
39 | suspend fun getFaceNames(): List
40 |
41 | fun getAllImages(isPrimary: Boolean): Flow>
42 |
43 | fun getImagesForFace(faceName: String, onlyPrimary: Boolean = false): Flow>
44 | }
--------------------------------------------------------------------------------
/app/src/main/java/in/surajsau/jisho/data/model/ChatMessageModel.kt:
--------------------------------------------------------------------------------
1 | package `in`.surajsau.jisho.data.model
2 |
3 | sealed class ChatMessageModel(val timeStamp: Long) {
4 | data class Typing(private val ts: Long): ChatMessageModel(timeStamp = ts)
5 | data class Message(
6 | val id: Int,
7 | val text: String,
8 | val isMe: Boolean,
9 | private val ts: Long,
10 | val imageUrl: String?,
11 | ): ChatMessageModel(timeStamp = ts)
12 | }
--------------------------------------------------------------------------------
/app/src/main/java/in/surajsau/jisho/data/model/Emoji.kt:
--------------------------------------------------------------------------------
1 | package `in`.surajsau.jisho.data.model
2 |
3 | data class Emoji(
4 | val emojis: List,
5 | val label: String,
6 | )
7 |
--------------------------------------------------------------------------------
/app/src/main/java/in/surajsau/jisho/data/model/Screen.kt:
--------------------------------------------------------------------------------
1 | package `in`.surajsau.jisho.data.model
2 |
3 | import `in`.surajsau.jisho.ui.Destinations
4 |
5 | data class Screen(
6 | val destinations: Destinations,
7 | val previewImage: String = "",
8 | val title: String,
9 | val description: String,
10 | val tags: List = emptyList()
11 | )
--------------------------------------------------------------------------------
/app/src/main/java/in/surajsau/jisho/data/model/Suggestion.kt:
--------------------------------------------------------------------------------
1 | package `in`.surajsau.jisho.data.model
2 |
3 | sealed class Suggestion {
4 | object Interpreting: Suggestion()
5 | data class Message(val value: String): Suggestion()
6 | }
7 |
--------------------------------------------------------------------------------
/app/src/main/java/in/surajsau/jisho/di/DataModule.kt:
--------------------------------------------------------------------------------
1 | package `in`.surajsau.jisho.di
2 |
3 | import `in`.surajsau.jisho.data.*
4 | import `in`.surajsau.jisho.data.chat.*
5 | import `in`.surajsau.jisho.data.db.AppDb
6 | import `in`.surajsau.jisho.data.facenet.*
7 | import android.content.Context
8 | import androidx.room.Room
9 | import dagger.Binds
10 | import dagger.Module
11 | import dagger.Provides
12 | import dagger.hilt.InstallIn
13 | import dagger.hilt.android.qualifiers.ApplicationContext
14 | import dagger.hilt.components.SingletonComponent
15 | import javax.inject.Singleton
16 |
17 | @Module
18 | @InstallIn(SingletonComponent::class)
19 | abstract class DataModule {
20 |
21 | @Binds
22 | @Singleton
23 | abstract fun bindTranslatorProvider(provider: TranslatorProviderImpl): TranslatorProvider
24 |
25 | @Binds
26 | @Singleton
27 | abstract fun bindDigitalInkProvider(provider: DigitalInkProviderImpl): DigitalInkProvider
28 |
29 | @Binds
30 | @Singleton
31 | abstract fun bindScreenDataProvider(provider: ScreensDataProviderImpl): ScreensDataProvider
32 |
33 | @Binds
34 | @Singleton
35 | abstract fun bindChatProvider(provider: ChatDataProviderImpl): ChatDataProvider
36 |
37 | @Binds
38 | @Singleton
39 | abstract fun bindFaceDetectionProvider(provider: FaceDetectionProviderImpl): FaceDetectionProvider
40 |
41 | @Binds
42 | @Singleton
43 | abstract fun bindCardDataProvider(provider: CardDataProviderImpl): CardDataProvider
44 |
45 | @Binds
46 | @Singleton
47 | abstract fun bindEntityExtractionProvider(provider: EntityExtractionpProviderImpl): EntityExtractionProvider
48 |
49 | @Binds
50 | @Singleton
51 | abstract fun bindSmartRepliesProvider(provider: ReplySuggestionProviderImpl): ReplySuggestionProvider
52 |
53 | @Binds
54 | @Singleton
55 | abstract fun bindEmojiLabellingProvider(provider: EmojiLabellingDataProviderImpl): EmojiLabellingDataProvider
56 |
57 | companion object {
58 |
59 | @Provides
60 | @Singleton
61 | fun provideFileProvider(@ApplicationContext context: Context): FileProvider = FileProviderImpl(context)
62 |
63 | @Provides
64 | @Singleton
65 | fun provideStyleTransfer(@ApplicationContext context: Context): StyleTransferProvider
66 | = StyleTransferProviderImpl(context)
67 |
68 | @Provides
69 | @Singleton
70 | fun provideDb(@ApplicationContext context: Context): AppDb = Room
71 | .databaseBuilder(context, AppDb::class.java, AppDb.FILE_NAME)
72 | .build()
73 |
74 | @Provides
75 | @Singleton
76 | fun provideFacesProvider(appDb: AppDb): FacesDataProvider = FacesDataProviderImpl(appDb.faceDao())
77 |
78 | @Provides
79 | @Singleton
80 | fun proviceFaceRecognitionProvider(
81 | @ApplicationContext context: Context,
82 | fileProvider: FileProvider,
83 | ): FaceRecognitionProvider = FaceRecognitionProviderImpl(context, fileProvider)
84 | }
85 | }
--------------------------------------------------------------------------------
/app/src/main/java/in/surajsau/jisho/di/cardreader/OnboardingScreenModule.kt:
--------------------------------------------------------------------------------
1 | package `in`.surajsau.jisho.di.cardreader
2 |
3 | import `in`.surajsau.jisho.data.CardDataProvider
4 | import `in`.surajsau.jisho.data.FileProvider
5 | import `in`.surajsau.jisho.domain.cardreader.GetCardDetails
6 | import `in`.surajsau.jisho.domain.cardreader.processor.CCFrontProcessor
7 | import `in`.surajsau.jisho.domain.cardreader.processor.IDCardBackProcessor
8 | import `in`.surajsau.jisho.domain.cardreader.processor.IDCardFrontProcessor
9 | import `in`.surajsau.jisho.domain.models.CreditCard
10 | import `in`.surajsau.jisho.domain.models.IDCard
11 | import dagger.Module
12 | import dagger.Provides
13 | import dagger.hilt.InstallIn
14 | import dagger.hilt.components.SingletonComponent
15 |
16 | @Module
17 | @InstallIn(SingletonComponent::class)
18 | abstract class OnboardingScreenModule {
19 |
20 | companion object {
21 |
22 | @Provides
23 | fun provideCCFrontDetails(
24 | fileProvider: FileProvider,
25 | cardDataProvider: CardDataProvider
26 | ): GetCardDetails {
27 | return GetCardDetails(
28 | textProcessor = CCFrontProcessor(),
29 | fileProvider = fileProvider,
30 | cardDataProvider = cardDataProvider
31 | )
32 | }
33 |
34 | @Provides
35 | fun provideGetCardFrontDetails(
36 | fileProvider: FileProvider,
37 | cardDataProvider: CardDataProvider
38 | ): GetCardDetails {
39 | return GetCardDetails(
40 | textProcessor = IDCardFrontProcessor(),
41 | fileProvider = fileProvider,
42 | cardDataProvider = cardDataProvider
43 | )
44 | }
45 |
46 | @Provides
47 | fun provideGetCardBackDetails(
48 | fileProvider: FileProvider,
49 | cardDataProvider: CardDataProvider
50 | ): GetCardDetails {
51 | return GetCardDetails(
52 | textProcessor = IDCardBackProcessor(),
53 | fileProvider = fileProvider,
54 | cardDataProvider = cardDataProvider
55 | )
56 | }
57 | }
58 |
59 | }
--------------------------------------------------------------------------------
/app/src/main/java/in/surajsau/jisho/domain/Cleanup.kt:
--------------------------------------------------------------------------------
1 | package `in`.surajsau.jisho.domain
2 |
3 | import `in`.surajsau.jisho.data.CardDataProvider
4 | import `in`.surajsau.jisho.data.facenet.FaceRecognitionProvider
5 | import javax.inject.Inject
6 |
7 | class Cleanup @Inject constructor(
8 | private val faceRecognitionProvider: FaceRecognitionProvider,
9 | ) {
10 | fun invoke() {
11 | faceRecognitionProvider.close()
12 | }
13 | }
--------------------------------------------------------------------------------
/app/src/main/java/in/surajsau/jisho/domain/cardreader/GetCardDetails.kt:
--------------------------------------------------------------------------------
1 | package `in`.surajsau.jisho.domain.cardreader
2 |
3 | import `in`.surajsau.jisho.data.CardDataProvider
4 | import `in`.surajsau.jisho.data.FileProvider
5 | import `in`.surajsau.jisho.domain.cardreader.processor.TextProcessor
6 | import kotlinx.coroutines.flow.Flow
7 | import kotlinx.coroutines.flow.flatMapLatest
8 | import kotlinx.coroutines.flow.flow
9 | import kotlinx.coroutines.flow.map
10 | import javax.inject.Inject
11 |
12 | class GetCardDetails @Inject constructor(
13 | private val textProcessor: TextProcessor,
14 | private val cardDataProvider: CardDataProvider,
15 | private val fileProvider: FileProvider
16 | ) {
17 |
18 | fun invoke(fileName: String): Flow
19 | = flow { emit(fileProvider.fetchCachedBitmap(fileName = fileName)) }
20 | .flatMapLatest { cardDataProvider.identifyTexts(it) }
21 | .map { textResult -> textProcessor.process(textResult) }
22 |
23 |
24 | }
--------------------------------------------------------------------------------
/app/src/main/java/in/surajsau/jisho/domain/cardreader/IdentifyText.kt:
--------------------------------------------------------------------------------
1 | package `in`.surajsau.jisho.domain.cardreader
2 |
3 | import `in`.surajsau.jisho.data.CardDataProvider
4 | import `in`.surajsau.jisho.data.FileProvider
5 | import kotlinx.coroutines.flow.Flow
6 | import kotlinx.coroutines.flow.flatMapLatest
7 | import kotlinx.coroutines.flow.flow
8 | import kotlinx.coroutines.flow.map
9 | import javax.inject.Inject
10 |
11 | class IdentifyText @Inject constructor(
12 | private val cardDataProvider: CardDataProvider,
13 | private val fileProvider: FileProvider
14 | ) {
15 |
16 | fun invoke(fileName: String, language: CardDataProvider.Language = CardDataProvider.Language.EN): Flow
17 | = flow { emit(fileProvider.fetchCachedBitmap(fileName)) }
18 | .flatMapLatest { cardDataProvider.identifyTexts(it, language) }
19 | .map { it.text }
20 | }
--------------------------------------------------------------------------------
/app/src/main/java/in/surajsau/jisho/domain/cardreader/processor/CCFrontProcessor.kt:
--------------------------------------------------------------------------------
1 | package `in`.surajsau.jisho.domain.cardreader.processor
2 |
3 | import `in`.surajsau.jisho.domain.models.CreditCard
4 | import android.util.Log
5 | import com.google.mlkit.vision.text.Text
6 | import com.soywiz.klock.DateTime
7 |
8 | class CCFrontProcessor : TextProcessor {
9 |
10 | override fun process(textResult: Text): CreditCard.Front {
11 | val lines = textResult.text
12 | Log.e("Card", lines)
13 | return CreditCard.Front("", DateTime.now().date, "")
14 | }
15 | }
--------------------------------------------------------------------------------
/app/src/main/java/in/surajsau/jisho/domain/cardreader/processor/IDCardBackProcessor.kt:
--------------------------------------------------------------------------------
1 | package `in`.surajsau.jisho.domain.cardreader.processor
2 |
3 | import `in`.surajsau.jisho.base.Optional
4 | import `in`.surajsau.jisho.domain.models.IDCard
5 | import android.util.Log
6 | import android.util.Patterns
7 | import com.google.mlkit.vision.text.Text
8 |
9 | /*
10 | Card No. : S00150
11 | Date of Issue : June.23, 2015
12 | Address for correspondence:
13 | Qr. No. - 413 C, Sector 4,
14 | Ukkunagaram Township,
15 | VISAKHAPATNAM - 530032
16 | Mob. No. : +91- 7829953236
17 | e-mail: i@surajsau.in
18 | 2015010006
19 | */
20 |
21 | class IDCardBackProcessor : TextProcessor {
22 |
23 | override fun process(textResult: Text): IDCard.Back {
24 | val lines = textResult.text.replace(":", "").split("\n")
25 |
26 | Log.e("Card", lines.joinToString("\n"))
27 |
28 | val mobileNumber = lines
29 | .map { Optional.of(MobileNumberRegex.find(it)?.value) }
30 | .first { it !is Optional.Empty }
31 |
32 | val email = lines
33 | .map { Optional.of(EmailRegex.find(it)?.value) }
34 | .first { it !is Optional.Empty }
35 |
36 | val address = lines.subList(
37 | fromIndex = lines.indexOfFirst { it.startsWith("Address") } + 1,
38 | toIndex = lines.indexOfFirst { it.startsWith("Mob.") }
39 | ).joinToString(separator = "\n")
40 |
41 | return IDCard.Back(
42 | address = address,
43 | mobileNumber = mobileNumber.getValue(),
44 | email = email.getValue()
45 | )
46 | }
47 |
48 | companion object {
49 | private val MobileNumberRegex = Regex("(\\+91-\\s+)\\d{10}")
50 | private val EmailRegex = Patterns.EMAIL_ADDRESS.pattern().toRegex()
51 | }
52 |
53 | }
--------------------------------------------------------------------------------
/app/src/main/java/in/surajsau/jisho/domain/cardreader/processor/IDCardFrontProcessor.kt:
--------------------------------------------------------------------------------
1 | package `in`.surajsau.jisho.domain.cardreader.processor
2 |
3 | import `in`.surajsau.jisho.domain.models.IDCard
4 | import android.util.Log
5 | import androidx.core.text.isDigitsOnly
6 | import com.google.mlkit.vision.text.Text
7 | import com.soywiz.klock.DateFormat
8 | import com.soywiz.klock.parseDate
9 |
10 | /*
11 | Sample:
12 | -------
13 | ALUMNI ASSOCIATION
14 | INDIAN INSTITUTE OF TECHNOLOGY ROORKEE
15 | (Formerly University of Roorkee))
16 | ROORKEE -247667, UTTRAKHAND, INDIA
17 | Membership No. : 2015010006
18 | Name
19 | :Suraj Kumar Sau
20 | Year
21 | :2015
22 | Degree
23 | : B.Tech (ECE)
24 | Date of Birth :26-04-1994
25 | Issuing Au
26 | Hon. Secretary
27 | Signature of Card Holder
28 |
29 | */
30 |
31 | class IDCardFrontProcessor: TextProcessor {
32 |
33 | override fun process(textResult: Text): IDCard.Front {
34 | val lines = textResult
35 | .text
36 | .replace(":", "")
37 | .split("\n")
38 | .filterNot {
39 | it.startsWith("ALUMNI") or
40 | it.startsWith("INDIAN") or
41 | it.startsWith("(Formerly") or
42 | it.startsWith("ROORKEE") or
43 | it.startsWith("Issuing") or
44 | it.startsWith("Hon.") or
45 | it.startsWith("Signature") or
46 | it.startsWith("Date") or
47 | it.startsWith("Degree") or
48 | it.startsWith("Name")
49 | }.map { it.trim() }
50 |
51 | Log.e("Card", lines.joinToString("\n"))
52 |
53 | val words = lines.flatMap { it.split(" ") }
54 |
55 | val year = words.firstOrNull { it.isDigitsOnly() && it.length == 4 } ?: ""
56 |
57 | val dateOfBirth = words.firstOrNull { it.matches(DateOfBirthRegex) } ?: ""
58 |
59 | val membershipNumber = words.firstOrNull { it.isDigitsOnly() && it.length > 4 } ?: ""
60 |
61 | val name = lines.firstOrNull { it.matches("[a-zA-Z\\s]+".toRegex()) and (it.split(" ").size > 1) } ?: ""
62 |
63 | val degree = lines.firstOrNull { it.matches(DegreeRegex) } ?: ""
64 |
65 | return IDCard.Front(
66 | membershipNumber = membershipNumber,
67 | name = name,
68 | year = year,
69 | dateOfBirth = DateFormat.invoke("dd-MM-yyyy").parseDate(dateOfBirth),
70 | degree = degree
71 | )
72 | }
73 |
74 | companion object {
75 | private val DateOfBirthRegex = Regex("(0[1-9]|[12][0-9]|3[01])-(0[1-9]|1[0-2])-\\d{4}")
76 | private val DegreeRegex = Regex("([BM])\\.([A-Za-z]+)\\s\\(([A-Z]+)\\)")
77 | }
78 | }
--------------------------------------------------------------------------------
/app/src/main/java/in/surajsau/jisho/domain/cardreader/processor/TextProcessor.kt:
--------------------------------------------------------------------------------
1 | package `in`.surajsau.jisho.domain.cardreader.processor
2 |
3 | import com.google.mlkit.vision.text.Text
4 |
5 | interface TextProcessor {
6 | fun process(textResult: Text): T
7 | }
--------------------------------------------------------------------------------
/app/src/main/java/in/surajsau/jisho/domain/chat/CheckEntityExtractorAvailability.kt:
--------------------------------------------------------------------------------
1 | package `in`.surajsau.jisho.domain.chat
2 |
3 | import `in`.surajsau.jisho.data.chat.EntityExtractionProvider
4 | import javax.inject.Inject
5 |
6 | class CheckEntityExtractorAvailability @Inject constructor(
7 | private val entityExtractionProvider: EntityExtractionProvider
8 | ) {
9 |
10 | fun invoke() = entityExtractionProvider.initModel()
11 | }
--------------------------------------------------------------------------------
/app/src/main/java/in/surajsau/jisho/domain/chat/FetchChatDetails.kt:
--------------------------------------------------------------------------------
1 | package `in`.surajsau.jisho.domain.chat
2 |
3 | import `in`.surajsau.jisho.data.ChatDataProvider
4 | import `in`.surajsau.jisho.domain.models.chat.ChatDetails
5 | import kotlinx.coroutines.flow.Flow
6 | import javax.inject.Inject
7 |
8 | class FetchChatDetails @Inject constructor(
9 | private val chatDataProvider: ChatDataProvider
10 | ) {
11 |
12 | fun invoke(): Flow = chatDataProvider.fetchChatDetails()
13 | }
14 |
--------------------------------------------------------------------------------
/app/src/main/java/in/surajsau/jisho/domain/chat/FetchLatestMessage.kt:
--------------------------------------------------------------------------------
1 | package `in`.surajsau.jisho.domain.chat
2 |
3 | import `in`.surajsau.jisho.base.Optional
4 | import `in`.surajsau.jisho.data.ChatDataProvider
5 | import `in`.surajsau.jisho.data.chat.EntityExtractionProvider
6 | import `in`.surajsau.jisho.data.chat.ReplySuggestionProvider
7 | import `in`.surajsau.jisho.data.model.ChatMessageModel
8 | import `in`.surajsau.jisho.domain.models.chat.ChatAnnotation
9 | import `in`.surajsau.jisho.domain.models.chat.ChatRowModel
10 | import androidx.compose.ui.text.SpanStyle
11 | import androidx.compose.ui.text.buildAnnotatedString
12 | import androidx.compose.ui.text.style.TextDecoration
13 | import androidx.compose.ui.text.withStyle
14 | import com.google.mlkit.nl.entityextraction.DateTimeEntity
15 | import com.google.mlkit.nl.entityextraction.Entity
16 | import com.soywiz.klock.DateTime
17 | import kotlinx.coroutines.flow.*
18 | import javax.inject.Inject
19 |
20 | class FetchLatestMessage @Inject constructor(
21 | private val replySuggestionProvider: ReplySuggestionProvider,
22 | private val chatDataProvider: ChatDataProvider,
23 | private val entityExtractionProvider: EntityExtractionProvider,
24 | ) {
25 |
26 | fun invoke(): Flow {
27 | return chatDataProvider.latestMessage
28 | .receiveAsFlow()
29 | .onEach {
30 | if (it is ChatMessageModel.Message) {
31 | replySuggestionProvider.addMessage(it)
32 | }
33 | }
34 | .map {
35 | when (it) {
36 | is ChatMessageModel.Message -> {
37 | val annotations = entityExtractionProvider.extractEntities(it.text)
38 | val mainText = it.text
39 |
40 | val annotationMaps = mutableListOf()
41 |
42 | val text = buildAnnotatedString {
43 | if (annotations.isEmpty()) {
44 | append(mainText)
45 | return@buildAnnotatedString
46 | }
47 |
48 | val firstAnnotation = annotations.first()
49 |
50 | annotations.forEachIndexed { index, annotation ->
51 | // if first annotation, append text before the first annotation
52 | if (index == 0 && annotation.start > 0) {
53 | append(mainText.substring(startIndex = 0, endIndex = firstAnnotation.start))
54 | }
55 |
56 | val entity = annotation.entities.first()
57 | val annotatedSubString = mainText.substring(startIndex = annotation.start, endIndex = annotation.end)
58 |
59 | val chatAnnotation = when {
60 | entity is DateTimeEntity -> {
61 | Optional.of(ChatAnnotation.Reminder(id = "${it.id}:$index", timeStamp = entity.timestampMillis))
62 | }
63 |
64 | entity.type == Entity.TYPE_ADDRESS -> {
65 | Optional.of(ChatAnnotation.Address(id = "${it.id}:$index", address = annotation.annotatedText))
66 | }
67 |
68 | entity.type == Entity.TYPE_EMAIL -> {
69 | Optional.of(ChatAnnotation.Email(id = "${it.id}:$index", email = annotation.annotatedText))
70 | }
71 |
72 | entity.type == Entity.TYPE_PHONE -> {
73 | Optional.of(ChatAnnotation.Phone(id = "${it.id}:$index", phone = annotation.annotatedText))
74 | }
75 |
76 | else -> Optional.Empty
77 | }
78 |
79 | if (chatAnnotation is Optional.Some) {
80 | annotationMaps.add(chatAnnotation.data)
81 |
82 | pushStringAnnotation(tag = chatAnnotation.data.tag, annotation = mainText)
83 | withStyle(style = SpanStyle(textDecoration = TextDecoration.Underline)) {
84 | append(annotatedSubString)
85 | }
86 | pop()
87 |
88 | } else {
89 | append(annotatedSubString)
90 | }
91 |
92 | // if last annotation, append text after the last annotation
93 | if (index == annotations.size - 1 && annotation.end < mainText.length - 1) {
94 | append(mainText.substring(startIndex = annotation.end + 1))
95 | }
96 | }
97 | }
98 |
99 | if (!it.imageUrl.isNullOrEmpty()) {
100 | ChatRowModel.PictureMessage(
101 | imageUrl = it.imageUrl,
102 | message = text,
103 | isLocal = it.isMe,
104 | timestamp = DateTime.invoke(it.timeStamp).toString("HH:mm"),
105 | annotationMaps = annotationMaps
106 | )
107 | } else {
108 | ChatRowModel.Message(
109 | value = text,
110 | isLocal = it.isMe,
111 | timestamp = DateTime.invoke(it.timeStamp).toString("HH:mm"),
112 | annotationMaps = annotationMaps
113 | )
114 | }
115 | }
116 |
117 | is ChatMessageModel.Typing -> ChatRowModel.Typing
118 | }
119 | }
120 | }
121 | }
--------------------------------------------------------------------------------
/app/src/main/java/in/surajsau/jisho/domain/chat/FetchSuggestions.kt:
--------------------------------------------------------------------------------
1 | package `in`.surajsau.jisho.domain.chat
2 |
3 | import `in`.surajsau.jisho.data.chat.ReplySuggestionProvider
4 | import kotlinx.coroutines.flow.Flow
5 | import kotlinx.coroutines.flow.map
6 | import kotlinx.coroutines.flow.receiveAsFlow
7 | import javax.inject.Inject
8 |
9 | class FetchSuggestions @Inject constructor(
10 | private val replySuggestionProvider: ReplySuggestionProvider
11 | ) {
12 |
13 | fun invoke(): Flow> {
14 | return replySuggestionProvider.suggestions
15 | .receiveAsFlow()
16 | .map { suggestions -> suggestions.map { it.text } }
17 | }
18 | }
--------------------------------------------------------------------------------
/app/src/main/java/in/surajsau/jisho/domain/chat/GetEmojiSuggestions.kt:
--------------------------------------------------------------------------------
1 | package `in`.surajsau.jisho.domain.chat
2 |
3 | import `in`.surajsau.jisho.data.chat.EmojiLabellingDataProvider
4 | import `in`.surajsau.jisho.data.model.Emoji
5 | import android.graphics.Bitmap
6 | import kotlinx.coroutines.flow.Flow
7 | import javax.inject.Inject
8 |
9 | class GetEmojiSuggestions @Inject constructor(
10 | private val emojiLabellingDataProvider: EmojiLabellingDataProvider
11 | ) {
12 |
13 | fun invoke(bitmap: Bitmap): Flow>
14 | = emojiLabellingDataProvider.getEmojis(bitmap)
15 | }
--------------------------------------------------------------------------------
/app/src/main/java/in/surajsau/jisho/domain/chat/LoadEmojis.kt:
--------------------------------------------------------------------------------
1 | package `in`.surajsau.jisho.domain.chat
2 |
3 | import `in`.surajsau.jisho.data.chat.EmojiLabellingDataProvider
4 | import javax.inject.Inject
5 |
6 | class LoadEmojis @Inject constructor(
7 | private val emojiLabellingDataProvider: EmojiLabellingDataProvider
8 | ) {
9 |
10 | suspend fun invoke() {
11 | emojiLabellingDataProvider.loadEmojis()
12 | }
13 | }
--------------------------------------------------------------------------------
/app/src/main/java/in/surajsau/jisho/domain/chat/SendMessage.kt:
--------------------------------------------------------------------------------
1 | package `in`.surajsau.jisho.domain.chat
2 |
3 | import `in`.surajsau.jisho.data.ChatDataProvider
4 | import javax.inject.Inject
5 |
6 | class SendMessage @Inject constructor(
7 | private val chatDataProvider: ChatDataProvider
8 | ) {
9 |
10 | suspend fun invoke(imageUrl: String?, message: String, isLocal: Boolean) {
11 | chatDataProvider.sendMessage(
12 | message = message,
13 | isMe = isLocal,
14 | imageUrl = imageUrl,
15 | )
16 | }
17 | }
--------------------------------------------------------------------------------
/app/src/main/java/in/surajsau/jisho/domain/facenet/DetectFaces.kt:
--------------------------------------------------------------------------------
1 | package `in`.surajsau.jisho.domain.facenet
2 |
3 | import `in`.surajsau.jisho.data.FileProvider
4 | import `in`.surajsau.jisho.data.facenet.FaceDetectionProvider
5 | import `in`.surajsau.jisho.data.facenet.FaceRecognitionProvider
6 | import `in`.surajsau.jisho.domain.models.FaceRecognitionResult
7 | import android.util.Log
8 | import kotlinx.coroutines.ExperimentalCoroutinesApi
9 | import kotlinx.coroutines.flow.Flow
10 | import kotlinx.coroutines.flow.flatMapLatest
11 | import kotlinx.coroutines.flow.flow
12 | import kotlinx.coroutines.flow.map
13 | import javax.inject.Inject
14 |
15 | @ExperimentalCoroutinesApi
16 | class DetectFaces @Inject constructor(
17 | private val faceDetectionProvider: FaceDetectionProvider,
18 | private val faceRecognitionProvider: FaceRecognitionProvider,
19 | private val fileProvider: FileProvider
20 | ) {
21 |
22 | fun invoke(fileName: String): Flow> {
23 | return flow { emit(fileProvider.fetchCachedBitmap(fileName)) }
24 | .flatMapLatest {
25 | faceDetectionProvider.getFaces(it)
26 | }
27 | .map { faceBitmaps ->
28 | faceBitmaps.map result@ { bitmap ->
29 | val faceFileName = fileProvider.cacheBitmap(bitmap)
30 | val faceFilePath = fileProvider.getCacheFilePath(faceFileName)
31 | val embeddings = faceRecognitionProvider.generateEmbedding(bitmap)
32 |
33 | val estimateResults = faceRecognitionProvider.compareEmbedding(embeddings)
34 |
35 | Log.e("Facenet", estimateResults.map { "${it.key}: ${it.value.joinToString()}" }.joinToString("\n"))
36 |
37 | val averageScores = estimateResults.values.map { scores -> scores.average() }
38 | val names = estimateResults.keys.toTypedArray()
39 |
40 | if (averageScores.maxOrNull() == null)
41 | return@result FaceRecognitionResult.NotRecognised(fileName = faceFileName, filePath = faceFilePath)
42 |
43 | if (averageScores.maxOrNull()!! >= 0.4) {
44 | val index = averageScores.indexOf(averageScores.maxOrNull())
45 | return@result FaceRecognitionResult.Recognised(
46 | fileName = faceFileName,
47 | filePath = faceFilePath,
48 | estimatedName = names[index]
49 | )
50 | }
51 |
52 | return@result FaceRecognitionResult.NotRecognised(fileName = faceFileName, filePath = faceFilePath)
53 | }
54 |
55 | }
56 | }
57 | }
--------------------------------------------------------------------------------
/app/src/main/java/in/surajsau/jisho/domain/facenet/FetchAllFaces.kt:
--------------------------------------------------------------------------------
1 | package `in`.surajsau.jisho.domain.facenet
2 |
3 | import `in`.surajsau.jisho.data.FileProvider
4 | import `in`.surajsau.jisho.data.db.FaceImage
5 | import `in`.surajsau.jisho.data.facenet.FacesDataProvider
6 | import `in`.surajsau.jisho.domain.models.FaceModel
7 | import kotlinx.coroutines.flow.Flow
8 | import kotlinx.coroutines.flow.map
9 | import javax.inject.Inject
10 |
11 | class FetchAllFaces @Inject constructor(
12 | private val facesDataProvider: FacesDataProvider,
13 | private val fileProvider: FileProvider,
14 | ) {
15 |
16 | fun invoke(): Flow> = facesDataProvider.getAllImages(isPrimary = true)
17 | .map { images ->
18 | images.map {
19 | FaceModel(
20 | imageFilePath = fileProvider.getFilePath(folderName = FileProvider.FACENET_IMAGE_FOLDER, fileName = it.fileName),
21 | faceName = it.faceName
22 | )
23 | }
24 | }
25 | }
--------------------------------------------------------------------------------
/app/src/main/java/in/surajsau/jisho/domain/facenet/FetchAllImages.kt:
--------------------------------------------------------------------------------
1 | package `in`.surajsau.jisho.domain.facenet
2 |
3 | import `in`.surajsau.jisho.data.FileProvider
4 | import `in`.surajsau.jisho.data.facenet.FacesDataProvider
5 | import `in`.surajsau.jisho.domain.models.GalleryModel
6 | import kotlinx.coroutines.flow.Flow
7 | import kotlinx.coroutines.flow.map
8 | import javax.inject.Inject
9 |
10 | class FetchAllImages @Inject constructor(
11 | private val facesDataProvider: FacesDataProvider,
12 | private val fileProvider: FileProvider,
13 | ) {
14 |
15 | fun invoke(): Flow> = facesDataProvider.getAllImages(isPrimary = false)
16 | .map { images ->
17 | images.map {
18 | GalleryModel.Image(
19 | imageFilePath = fileProvider.getFilePath(FileProvider.FACENET_IMAGE_FOLDER, it.fileName),
20 | faceName = it.faceName
21 | )
22 | }
23 | }
24 | }
--------------------------------------------------------------------------------
/app/src/main/java/in/surajsau/jisho/domain/facenet/FetchAllImagesForFace.kt:
--------------------------------------------------------------------------------
1 | package `in`.surajsau.jisho.domain.facenet
2 |
3 | import `in`.surajsau.jisho.data.FileProvider
4 | import `in`.surajsau.jisho.data.facenet.FacesDataProvider
5 | import `in`.surajsau.jisho.domain.models.GalleryModel
6 | import kotlinx.coroutines.flow.Flow
7 | import kotlinx.coroutines.flow.map
8 | import javax.inject.Inject
9 |
10 | class FetchAllImagesForFace @Inject constructor(
11 | private val facesDataProvider: FacesDataProvider,
12 | private val fileProvider: FileProvider,
13 | ) {
14 |
15 | fun invoke(faceName: String): Flow> = facesDataProvider
16 | .getImagesForFace(faceName = faceName)
17 | .map { images ->
18 | images.map {
19 | GalleryModel.Image(
20 | imageFilePath = fileProvider.getFilePath(
21 | folderName = FileProvider.FACENET_IMAGE_FOLDER,
22 | fileName = it.fileName
23 | ),
24 | faceName = it.faceName
25 | )
26 | }
27 | }
28 | }
--------------------------------------------------------------------------------
/app/src/main/java/in/surajsau/jisho/domain/facenet/FetchFaceNames.kt:
--------------------------------------------------------------------------------
1 | package `in`.surajsau.jisho.domain.facenet
2 |
3 | import `in`.surajsau.jisho.data.facenet.FacesDataProvider
4 | import kotlinx.coroutines.flow.Flow
5 | import kotlinx.coroutines.flow.flow
6 | import javax.inject.Inject
7 |
8 | class FetchFaceNames @Inject constructor(
9 | private val facesDataProvider: FacesDataProvider,
10 | ) {
11 |
12 | fun invoke(): Flow> = flow {
13 | val faceNames = facesDataProvider.getFaceNames()
14 | emit(faceNames)
15 | }
16 | }
--------------------------------------------------------------------------------
/app/src/main/java/in/surajsau/jisho/domain/facenet/Initiate.kt:
--------------------------------------------------------------------------------
1 | package `in`.surajsau.jisho.domain.facenet
2 |
3 | import `in`.surajsau.jisho.data.facenet.FaceRecognitionProvider
4 | import javax.inject.Inject
5 |
6 | class Initiate @Inject constructor(private val faceRecognitionProvider: FaceRecognitionProvider) {
7 |
8 | fun invoke() {
9 | faceRecognitionProvider.initiate()
10 | }
11 | }
--------------------------------------------------------------------------------
/app/src/main/java/in/surajsau/jisho/domain/facenet/LoadEmbeddings.kt:
--------------------------------------------------------------------------------
1 | package `in`.surajsau.jisho.domain.facenet
2 |
3 | import `in`.surajsau.jisho.data.facenet.FaceRecognitionProvider
4 | import `in`.surajsau.jisho.data.facenet.FacesDataProvider
5 | import javax.inject.Inject
6 |
7 | class LoadEmbeddings @Inject constructor(
8 | private val faceRecognitionProvider: FaceRecognitionProvider,
9 | private val facesDataProvider: FacesDataProvider,
10 | ) {
11 |
12 | suspend fun invoke() {
13 | val faceNames = facesDataProvider.getFaceNames()
14 | faceNames.forEach { faceRecognitionProvider.loadEmbeddingFor(faceName = it) }
15 | }
16 | }
--------------------------------------------------------------------------------
/app/src/main/java/in/surajsau/jisho/domain/facenet/SaveFaceEmbedding.kt:
--------------------------------------------------------------------------------
1 | package `in`.surajsau.jisho.domain.facenet
2 |
3 | import `in`.surajsau.jisho.data.FileProvider
4 | import `in`.surajsau.jisho.data.facenet.FaceRecognitionProvider
5 | import javax.inject.Inject
6 |
7 | class SaveFaceEmbedding @Inject constructor(
8 | private val fileProvider: FileProvider,
9 | private val faceRecognitionProvider: FaceRecognitionProvider,
10 | ) {
11 |
12 | suspend fun invoke(faceName: String, faceFileName: String) {
13 | val bitmap = fileProvider.fetchCachedBitmap(fileName = faceFileName)
14 | val embeddings = faceRecognitionProvider.generateEmbedding(bitmap)
15 | faceRecognitionProvider.saveEmbedding(faceName = faceName, embedding = embeddings)
16 | }
17 |
18 | }
--------------------------------------------------------------------------------
/app/src/main/java/in/surajsau/jisho/domain/facenet/SaveImage.kt:
--------------------------------------------------------------------------------
1 | package `in`.surajsau.jisho.domain.facenet
2 |
3 | import `in`.surajsau.jisho.data.FileProvider
4 | import `in`.surajsau.jisho.data.facenet.FacesDataProvider
5 | import javax.inject.Inject
6 |
7 | class SaveImage @Inject constructor(
8 | private val fileProvider: FileProvider,
9 | private val facesDataProvider: FacesDataProvider
10 | ) {
11 |
12 | suspend fun invoke(faceName: String, fileName: String) {
13 | val bitmap = fileProvider.fetchCachedBitmap(fileName = fileName)
14 | fileProvider.storeBitmap(folderName = FileProvider.FACENET_IMAGE_FOLDER, fileName = fileName, bitmap = bitmap)
15 |
16 | facesDataProvider.saveFace(faceName = faceName, fileName = fileName, isPrimary = false)
17 | }
18 | }
--------------------------------------------------------------------------------
/app/src/main/java/in/surajsau/jisho/domain/facenet/SaveNewFace.kt:
--------------------------------------------------------------------------------
1 | package `in`.surajsau.jisho.domain.facenet
2 |
3 | import `in`.surajsau.jisho.data.FileProvider
4 | import `in`.surajsau.jisho.data.facenet.FacesDataProvider
5 | import javax.inject.Inject
6 |
7 | class SaveNewFace @Inject constructor(
8 | private val fileProvider: FileProvider,
9 | private val facesDataProvider: FacesDataProvider,
10 | ) {
11 |
12 | suspend fun invoke(faceName: String, fileName: String) {
13 | val bitmap = fileProvider.fetchCachedBitmap(fileName = fileName)
14 | fileProvider.storeBitmap(folderName = FileProvider.FACENET_IMAGE_FOLDER, fileName = fileName, bitmap = bitmap)
15 |
16 | facesDataProvider.saveFace(faceName = faceName, fileName = fileName, isPrimary = true)
17 | }
18 | }
--------------------------------------------------------------------------------
/app/src/main/java/in/surajsau/jisho/domain/models/CreditCard.kt:
--------------------------------------------------------------------------------
1 | package `in`.surajsau.jisho.domain.models
2 |
3 | import com.soywiz.klock.Date
4 |
5 | sealed class CreditCard {
6 |
7 | data class Front(
8 | val cardNumber: String,
9 | val expiryDate: Date,
10 | val cardHolderName: String,
11 | ): CreditCard()
12 |
13 | data class Back(val cvv: String): CreditCard()
14 | }
15 |
--------------------------------------------------------------------------------
/app/src/main/java/in/surajsau/jisho/domain/models/FaceModel.kt:
--------------------------------------------------------------------------------
1 | package `in`.surajsau.jisho.domain.models
2 |
3 | data class FaceModel(val imageFilePath: String, val faceName: String)
--------------------------------------------------------------------------------
/app/src/main/java/in/surajsau/jisho/domain/models/FaceRecognitionResult.kt:
--------------------------------------------------------------------------------
1 | package `in`.surajsau.jisho.domain.models
2 |
3 | sealed class FaceRecognitionResult(val faceFileName: String, val faceFilePath: String) {
4 | data class Recognised(
5 | private val fileName: String,
6 | private val filePath: String,
7 | val estimatedName: String
8 | ): FaceRecognitionResult(faceFileName = fileName, faceFilePath = filePath)
9 |
10 | data class NotRecognised(
11 | private val fileName: String,
12 | private val filePath: String
13 | ): FaceRecognitionResult(faceFileName = fileName, faceFilePath = filePath)
14 | }
--------------------------------------------------------------------------------
/app/src/main/java/in/surajsau/jisho/domain/models/GalleryModel.kt:
--------------------------------------------------------------------------------
1 | package `in`.surajsau.jisho.domain.models
2 |
3 | sealed class GalleryModel {
4 | data class Image(val imageFilePath: String, val faceName: String): GalleryModel()
5 | object Empty: GalleryModel()
6 | }
7 |
--------------------------------------------------------------------------------
/app/src/main/java/in/surajsau/jisho/domain/models/IDCard.kt:
--------------------------------------------------------------------------------
1 | package `in`.surajsau.jisho.domain.models
2 |
3 | import com.soywiz.klock.Date
4 |
5 | sealed class IDCard {
6 | data class Front(
7 | val membershipNumber: String,
8 | val name: String,
9 | val year: String,
10 | val degree: String,
11 | val dateOfBirth: Date,
12 | ): IDCard()
13 |
14 | data class Back(
15 | val address: String,
16 | val mobileNumber: String,
17 | val email: String,
18 | ): IDCard()
19 | }
--------------------------------------------------------------------------------
/app/src/main/java/in/surajsau/jisho/domain/models/User.kt:
--------------------------------------------------------------------------------
1 | package `in`.surajsau.jisho.domain.models
2 |
3 | data class User(
4 | private val firstName: String,
5 | private val lastName: String,
6 | val profileUrl: String
7 | ) {
8 |
9 | val displayName: String
10 | get() = "$firstName $lastName"
11 | }
12 |
--------------------------------------------------------------------------------
/app/src/main/java/in/surajsau/jisho/domain/models/chat/ChatAnnotation.kt:
--------------------------------------------------------------------------------
1 | package `in`.surajsau.jisho.domain.models.chat
2 |
3 | sealed class ChatAnnotation(val tag: String) {
4 |
5 | data class Email(private val id: String, val email: String): ChatAnnotation(tag = id)
6 | data class Phone(private val id: String, val phone: String): ChatAnnotation(tag = id)
7 | data class Reminder(private val id: String, val timeStamp: Long): ChatAnnotation(tag = id)
8 | data class Address(private val id: String, val address: String): ChatAnnotation(tag = id)
9 | }
--------------------------------------------------------------------------------
/app/src/main/java/in/surajsau/jisho/domain/models/chat/ChatDetails.kt:
--------------------------------------------------------------------------------
1 | package `in`.surajsau.jisho.domain.models.chat
2 |
3 | data class ChatDetails(
4 | val chatName: String,
5 | val chatIconUrl: String,
6 | )
--------------------------------------------------------------------------------
/app/src/main/java/in/surajsau/jisho/domain/models/chat/ChatRowModel.kt:
--------------------------------------------------------------------------------
1 | package `in`.surajsau.jisho.domain.models.chat
2 |
3 | import androidx.compose.ui.text.AnnotatedString
4 |
5 | sealed interface ChatRowModel {
6 |
7 | val isLocal: Boolean
8 |
9 | object Typing: ChatRowModel {
10 | override val isLocal: Boolean = false
11 | }
12 |
13 | data class Message(
14 | val value: AnnotatedString,
15 | val timestamp: String,
16 | override val isLocal: Boolean,
17 | val annotationMaps: List
18 | ): ChatRowModel
19 |
20 | data class PictureMessage(
21 | val imageUrl: String,
22 | val timestamp: String,
23 | override val isLocal: Boolean,
24 | val message: AnnotatedString,
25 | val annotationMaps: List
26 | ): ChatRowModel
27 | }
28 |
--------------------------------------------------------------------------------
/app/src/main/java/in/surajsau/jisho/domain/models/chat/EntityModel.kt:
--------------------------------------------------------------------------------
1 | package `in`.surajsau.jisho.domain.models.chat
2 |
3 | import com.google.mlkit.nl.entityextraction.Entity
4 |
5 | data class EntityModel(
6 | val entity: Entity,
7 | val start: Int,
8 | val end: Int,
9 | )
--------------------------------------------------------------------------------
/app/src/main/java/in/surajsau/jisho/ui/Destinations.kt:
--------------------------------------------------------------------------------
1 | package `in`.surajsau.jisho.ui
2 |
3 | enum class Destinations(val value: String) {
4 | Home("home"),
5 |
6 | DigitalInk("digital_ink"),
7 | StyleTransfer("style_transfer"),
8 | SmartChat("gpt"),
9 | Facenet("facenet"),
10 | CardReader("card_reader")
11 | }
--------------------------------------------------------------------------------
/app/src/main/java/in/surajsau/jisho/ui/MLApp.kt:
--------------------------------------------------------------------------------
1 | package `in`.surajsau.jisho.ui
2 |
3 | import `in`.surajsau.jisho.base.BitmapCache
4 | import `in`.surajsau.jisho.base.LocalBitmapCache
5 | import `in`.surajsau.jisho.ui.cardreader.CardReaderViewModelImpl
6 | import `in`.surajsau.jisho.ui.cardreader.LocalOnboardingViewModel
7 | import `in`.surajsau.jisho.ui.cardreader.OnBoardingScreen
8 | import `in`.surajsau.jisho.ui.chat.LocalSmartChatViewModel
9 | import `in`.surajsau.jisho.ui.chat.SmartChatScreen
10 | import `in`.surajsau.jisho.ui.chat.SmartChatViewModelImpl
11 | import `in`.surajsau.jisho.ui.digitalink.DigitalInkScreen
12 | import `in`.surajsau.jisho.ui.digitalink.DigitalInkViewModelImpl
13 | import `in`.surajsau.jisho.ui.digitalink.LocalDigitalInkViewModel
14 | import `in`.surajsau.jisho.ui.facenet.FacenetScreen
15 | import `in`.surajsau.jisho.ui.facenet.FacenetViewModelImpl
16 | import `in`.surajsau.jisho.ui.facenet.LocalFacenetViewModel
17 | import `in`.surajsau.jisho.ui.home.HomeScreen
18 | import `in`.surajsau.jisho.ui.home.HomeViewModelImpl
19 | import `in`.surajsau.jisho.ui.home.LocalHomeViewModel
20 | import `in`.surajsau.jisho.ui.styletransfer.LocalStyleTransferViewModel
21 | import `in`.surajsau.jisho.ui.styletransfer.StyleTransferScreen
22 | import `in`.surajsau.jisho.ui.styletransfer.StyleTransferViewModelImpl
23 | import `in`.surajsau.jisho.ui.theme.JishoTheme
24 | import androidx.compose.foundation.layout.fillMaxSize
25 | import androidx.compose.material.MaterialTheme
26 | import androidx.compose.material.Surface
27 | import androidx.compose.runtime.Composable
28 | import androidx.compose.runtime.CompositionLocalProvider
29 | import androidx.compose.ui.Modifier
30 | import androidx.hilt.navigation.compose.hiltViewModel
31 | import androidx.navigation.compose.NavHost
32 | import androidx.navigation.compose.composable
33 | import androidx.navigation.compose.rememberNavController
34 |
35 | @Composable
36 | fun MLApp(
37 | navigateToSettings: () -> Unit
38 | ) {
39 | val navController = rememberNavController()
40 |
41 | JishoTheme {
42 | // A surface container using the 'background' color from the theme
43 | Surface(
44 | modifier = Modifier.fillMaxSize(),
45 | color = MaterialTheme.colors.background
46 | ) {
47 | NavHost(navController = navController, startDestination = Destinations.Home.value) {
48 | composable(route = Destinations.Home.value) {
49 | CompositionLocalProvider(
50 | LocalHomeViewModel provides hiltViewModel()
51 | ) {
52 | HomeScreen(
53 | navigateToDestination = { navController.navigate(route = it.value) },
54 | modifier = Modifier.fillMaxSize()
55 | )
56 | }
57 | }
58 | composable(route = Destinations.DigitalInk.value) {
59 | CompositionLocalProvider(
60 | LocalDigitalInkViewModel provides hiltViewModel(),
61 | ) {
62 | DigitalInkScreen(
63 | modifier = Modifier.fillMaxSize()
64 | )
65 | }
66 | }
67 | composable(route = Destinations.StyleTransfer.value) {
68 | CompositionLocalProvider(
69 | LocalStyleTransferViewModel provides hiltViewModel(),
70 | LocalBitmapCache provides BitmapCache()
71 | ) {
72 | StyleTransferScreen(
73 | modifier = Modifier.fillMaxSize(),
74 | navigateBack = { navController.popBackStack() },
75 | navigateToSettings = navigateToSettings
76 | )
77 | }
78 | }
79 | composable(route = Destinations.SmartChat.value) {
80 | CompositionLocalProvider(
81 | LocalSmartChatViewModel provides hiltViewModel(),
82 | ) {
83 | SmartChatScreen(
84 | modifier = Modifier.fillMaxSize(),
85 | onDismiss = { navController.popBackStack() }
86 | )
87 | }
88 | }
89 |
90 | composable(route = Destinations.Facenet.value) {
91 | CompositionLocalProvider(
92 | LocalFacenetViewModel provides hiltViewModel()
93 | ) {
94 | FacenetScreen(modifier = Modifier.fillMaxSize())
95 | }
96 | }
97 |
98 | composable(route = Destinations.CardReader.value) {
99 | CompositionLocalProvider(
100 | LocalOnboardingViewModel provides hiltViewModel()
101 | ) {
102 | OnBoardingScreen(modifier = Modifier.fillMaxSize())
103 | }
104 | }
105 | }
106 | }
107 | }
108 | }
--------------------------------------------------------------------------------
/app/src/main/java/in/surajsau/jisho/ui/base/AskPermissionScreen.kt:
--------------------------------------------------------------------------------
1 | package `in`.surajsau.jisho.ui.base
2 |
3 | import `in`.surajsau.jisho.ui.theme.Purple500
4 | import androidx.compose.foundation.background
5 | import androidx.compose.foundation.layout.*
6 | import androidx.compose.material.Button
7 | import androidx.compose.material.ButtonDefaults
8 | import androidx.compose.material.Text
9 | import androidx.compose.runtime.*
10 | import androidx.compose.ui.Modifier
11 | import androidx.compose.ui.graphics.Color
12 | import androidx.compose.ui.text.font.FontWeight
13 | import androidx.compose.ui.unit.dp
14 | import androidx.compose.ui.unit.sp
15 | import com.google.accompanist.permissions.ExperimentalPermissionsApi
16 | import com.google.accompanist.permissions.rememberPermissionState
17 |
18 | @OptIn(ExperimentalPermissionsApi::class)
19 | @Composable
20 | fun AskPermissionScreen(
21 | modifier: Modifier = Modifier,
22 | permission: String,
23 | onDismiss: () -> Unit,
24 | onPermissionDeniedFallback: (() -> Unit)? = null,
25 | onPermissionAcceptedContents: @Composable () -> Unit,
26 | ) {
27 |
28 | val permissionState = rememberPermissionState(permission = permission)
29 |
30 | var doNotShowRationale by remember { mutableStateOf(false) }
31 |
32 | Box(modifier = modifier) {
33 | when {
34 | permissionState.hasPermission -> onPermissionAcceptedContents.invoke()
35 |
36 | permissionState.shouldShowRationale
37 | || !permissionState.permissionRequested -> {
38 |
39 | if (doNotShowRationale) {
40 |
41 | PermissionRationale(
42 | title = "Check settings",
43 | onDismissed = { onDismiss.invoke() },
44 | onAccepted = { onPermissionDeniedFallback?.invoke() }
45 | )
46 |
47 | } else {
48 | PermissionRationale(
49 | title = "Require Camera permission",
50 | onDismissed = { doNotShowRationale = true },
51 | onAccepted = { permissionState.launchPermissionRequest() }
52 | )
53 | }
54 | }
55 |
56 | else -> {
57 |
58 | PermissionRationale(
59 | title = "Check settings",
60 | onDismissed = { onDismiss.invoke() },
61 | onAccepted = { onPermissionDeniedFallback?.invoke() }
62 | )
63 | }
64 |
65 | }
66 | }
67 |
68 |
69 | }
70 |
71 | @Composable
72 | fun PermissionRationale(
73 | modifier: Modifier = Modifier,
74 | title: String,
75 | onDismissed: () -> Unit,
76 | onAccepted: () -> Unit
77 | ) {
78 |
79 | Column(modifier = modifier
80 | .fillMaxSize()
81 | .background(Color.White)) {
82 |
83 | Text(
84 | text = title,
85 | color = Color.DarkGray,
86 | modifier = Modifier
87 | .fillMaxWidth()
88 | .padding(16.dp),
89 | fontSize = 36.sp,
90 | fontWeight = FontWeight.Bold
91 | )
92 |
93 | Spacer(modifier = Modifier.weight(1f))
94 |
95 | Row(modifier = Modifier
96 | .fillMaxWidth()
97 | .padding(16.dp)) {
98 |
99 | Spacer(modifier = Modifier.weight(1f))
100 |
101 | Button(
102 | onClick = { onDismissed.invoke() },
103 | colors = ButtonDefaults.buttonColors(
104 | backgroundColor = Color.Transparent,
105 | contentColor = Purple500
106 | ),
107 | elevation = ButtonDefaults
108 | .elevation(
109 | defaultElevation = 0.dp,
110 | pressedElevation = 0.dp
111 | )
112 | ) {
113 | Text(text = "Cancel",
114 | fontWeight = FontWeight.Normal,
115 | fontSize = 18.sp,
116 | modifier = Modifier.padding(8.dp)
117 | )
118 | }
119 |
120 | Button(
121 | onClick = { onAccepted.invoke() },
122 | colors = ButtonDefaults.buttonColors(
123 | backgroundColor = Color.Transparent,
124 | contentColor = Purple500
125 | ),
126 | elevation = ButtonDefaults
127 | .elevation(
128 | defaultElevation = 0.dp,
129 | pressedElevation = 0.dp
130 | )
131 | ) {
132 | Text(text = "Allow",
133 | fontWeight = FontWeight.Medium,
134 | fontSize = 18.sp,
135 | modifier = Modifier.padding(8.dp)
136 | )
137 | }
138 | }
139 |
140 | }
141 | }
142 |
143 |
--------------------------------------------------------------------------------
/app/src/main/java/in/surajsau/jisho/ui/base/ExternalIntentHandler.kt:
--------------------------------------------------------------------------------
1 | package `in`.surajsau.jisho.ui.base
2 |
3 | import android.content.Context
4 | import android.content.Intent
5 | import android.net.Uri
6 | import android.provider.AlarmClock
7 | import android.util.Log
8 | import androidx.activity.compose.rememberLauncherForActivityResult
9 | import androidx.activity.result.contract.ActivityResultContracts
10 | import androidx.compose.runtime.Composable
11 | import androidx.compose.runtime.mutableStateOf
12 | import androidx.compose.runtime.remember
13 | import androidx.compose.ui.platform.LocalContext
14 | import com.google.accompanist.permissions.ExperimentalPermissionsApi
15 | import com.google.accompanist.permissions.rememberPermissionState
16 | import com.soywiz.klock.DateTime
17 |
18 | @OptIn(ExperimentalPermissionsApi::class)
19 | @Composable
20 | fun rememberExternalIntentHandler(): ExternalIntentHandler {
21 | val context = LocalContext.current
22 |
23 | return remember { ExternalIntentHandler(context = context) }
24 | }
25 |
26 | class ExternalIntentHandler constructor(
27 | private val context: Context,
28 | ) {
29 |
30 | fun openMap(location: String) {
31 | val gmmIntentUri =
32 | Uri.parse("geo:0,0?q=$location")
33 | val intent = Intent(Intent.ACTION_VIEW, gmmIntentUri).apply {
34 | setPackage("com.google.android.apps.maps")
35 | }
36 |
37 | context.startActivity(intent)
38 | }
39 |
40 | fun openAlarm(message: String, timestamp: Long) {
41 | val dateTime = DateTime.invoke(timestamp)
42 | val intent = Intent(AlarmClock.ACTION_SET_ALARM).apply {
43 | putExtra(AlarmClock.EXTRA_MESSAGE, message)
44 | putExtra(AlarmClock.EXTRA_HOUR, dateTime.hours)
45 | putExtra(AlarmClock.EXTRA_MINUTES, dateTime.minutes)
46 | }
47 |
48 | context.startActivity(intent)
49 | }
50 |
51 | fun openPhone(phone: String) {
52 | val intent = Intent(Intent.ACTION_CALL).apply {
53 | data = Uri.parse("tel:$phone")
54 | }
55 |
56 | if (intent.resolveActivity(context.packageManager) != null)
57 | context.startActivity(intent)
58 | }
59 |
60 | fun openEmail(address: String) {
61 | val intent = Intent(Intent.ACTION_SEND).apply {
62 | putExtra(Intent.EXTRA_EMAIL, address)
63 | }
64 |
65 | context.startActivity(Intent.createChooser(intent, "Send Email"))
66 | }
67 | }
--------------------------------------------------------------------------------
/app/src/main/java/in/surajsau/jisho/ui/base/ForEachRow.kt:
--------------------------------------------------------------------------------
1 | package `in`.surajsau.jisho.ui.base
2 |
3 | import androidx.compose.foundation.background
4 | import androidx.compose.foundation.layout.*
5 | import androidx.compose.foundation.lazy.LazyListScope
6 | import androidx.compose.material.Text
7 | import androidx.compose.runtime.Composable
8 | import androidx.compose.ui.Alignment
9 | import androidx.compose.ui.Modifier
10 | import androidx.compose.ui.graphics.Color
11 | import androidx.compose.ui.tooling.preview.Preview
12 | import androidx.compose.ui.unit.dp
13 |
14 | @Composable
15 | inline fun ForEachRow(
16 | items: List,
17 | modifier: Modifier = Modifier,
18 | horizontalArrangement: Arrangement.Horizontal = Arrangement.Start,
19 | verticalAlignment: Alignment.Vertical = Alignment.CenterVertically,
20 | rowItem: @Composable RowScope.(T) -> Unit,
21 | ) {
22 | Row(
23 | modifier = modifier,
24 | horizontalArrangement = horizontalArrangement,
25 | verticalAlignment = verticalAlignment
26 | ) {
27 | items.forEach { rowItem(it) }
28 | }
29 | }
30 |
31 | @Composable
32 | inline fun ForEachRowIndexed(
33 | items: List,
34 | modifier: Modifier = Modifier,
35 | horizontalArrangement: Arrangement.Horizontal = Arrangement.Start,
36 | verticalAlignment: Alignment.Vertical = Alignment.CenterVertically,
37 | rowItem: @Composable RowScope.(Int, T) -> Unit,
38 | ) {
39 | Row(
40 | modifier = modifier,
41 | horizontalArrangement = horizontalArrangement,
42 | verticalAlignment = verticalAlignment
43 | ) {
44 | items.forEachIndexed { index, elem -> rowItem(index, elem) }
45 | }
46 | }
47 |
48 | @Preview
49 | @Composable
50 | private fun PreviewForEachRow() {
51 |
52 | Box(modifier = Modifier.fillMaxWidth().background(Color.White).height(100.dp)) {
53 | ForEachRow(items = (1..3).toList(), modifier = Modifier.padding(4.dp)) {
54 | Box(modifier = Modifier.size(100.dp).padding(end = 4.dp).background(Color.LightGray)) {
55 | Text(text = "$it", modifier = Modifier.align(Alignment.Center))
56 | }
57 | }
58 | }
59 | }
--------------------------------------------------------------------------------
/app/src/main/java/in/surajsau/jisho/ui/base/Line.kt:
--------------------------------------------------------------------------------
1 | package `in`.surajsau.jisho.ui.base
2 |
3 | import androidx.compose.foundation.background
4 | import androidx.compose.foundation.layout.Box
5 | import androidx.compose.foundation.layout.height
6 | import androidx.compose.foundation.layout.width
7 | import androidx.compose.runtime.Composable
8 | import androidx.compose.ui.Modifier
9 | import androidx.compose.ui.graphics.Color
10 | import androidx.compose.ui.unit.Dp
11 | import androidx.compose.ui.unit.dp
12 |
13 | @Composable
14 | fun Line(
15 | modifier: Modifier = Modifier,
16 | thickness: Dp = 1.dp,
17 | color: Color = Color.DarkGray,
18 | isVertical: Boolean = false,
19 |
20 | ) {
21 | Box(
22 | modifier = modifier.apply {
23 | if (isVertical)
24 | width(thickness)
25 | else
26 | height(thickness)
27 | }.background(color)
28 | )
29 | }
--------------------------------------------------------------------------------
/app/src/main/java/in/surajsau/jisho/ui/base/camera/Camera.kt:
--------------------------------------------------------------------------------
1 | package `in`.surajsau.jisho.ui.base.camera
2 |
3 | import androidx.camera.core.CameraSelector
4 | import androidx.camera.core.ImageCapture
5 | import androidx.camera.core.ImageCaptureException
6 | import androidx.camera.core.Preview
7 | import androidx.camera.lifecycle.ProcessCameraProvider
8 | import androidx.camera.view.PreviewView
9 | import androidx.compose.runtime.*
10 | import androidx.compose.ui.Modifier
11 | import androidx.compose.ui.platform.LocalContext
12 | import androidx.compose.ui.platform.LocalLifecycleOwner
13 | import androidx.compose.ui.viewinterop.AndroidView
14 | import androidx.core.content.ContextCompat
15 | import java.io.File
16 | import kotlin.coroutines.resume
17 | import kotlin.coroutines.suspendCoroutine
18 |
19 | enum class CameraAction {
20 | Click, SwitchCamera, None
21 | }
22 |
23 | private fun CameraSelector.toggle() = when (this) {
24 | CameraSelector.DEFAULT_BACK_CAMERA -> CameraSelector.DEFAULT_FRONT_CAMERA
25 | else -> CameraSelector.DEFAULT_BACK_CAMERA
26 | }
27 |
28 | @Composable
29 | fun Camera(
30 | modifier: Modifier = Modifier,
31 | cameraAction: CameraAction,
32 | onImageCaptured: (String) -> Unit,
33 | ) {
34 |
35 | val context = LocalContext.current
36 |
37 | val imageCapture: ImageCapture = remember { ImageCapture.Builder().build() }
38 |
39 | var cameraSelector by remember { mutableStateOf(CameraSelector.DEFAULT_BACK_CAMERA) }
40 |
41 | val lifecycleOwner = LocalLifecycleOwner.current
42 |
43 | val previewView = remember {
44 | PreviewView(context).apply { this.scaleType = PreviewView.ScaleType.FILL_CENTER }
45 | }
46 |
47 | DisposableEffect(cameraAction) {
48 | when (cameraAction) {
49 | CameraAction.Click -> {
50 | val imageFileName = "${System.currentTimeMillis()}.jpg"
51 | val imageFile = File(context.externalCacheDir, imageFileName)
52 | val outputImageOption = ImageCapture.OutputFileOptions.Builder(imageFile)
53 | .build()
54 |
55 | imageCapture.takePicture(
56 | outputImageOption,
57 | ContextCompat.getMainExecutor(context),
58 | object: ImageCapture.OnImageSavedCallback {
59 | override fun onError(exception: ImageCaptureException) {
60 | exception.printStackTrace()
61 | }
62 |
63 | override fun onImageSaved(outputFileResults: ImageCapture.OutputFileResults) {
64 | onImageCaptured.invoke(imageFileName)
65 | }
66 | }
67 | )
68 | }
69 | CameraAction.SwitchCamera -> {
70 | cameraSelector = cameraSelector.toggle()
71 | }
72 | }
73 |
74 | onDispose {}
75 | }
76 |
77 | LaunchedEffect(cameraSelector) {
78 | val cameraProvider = suspendCoroutine { continuation ->
79 | ProcessCameraProvider.getInstance(context).also { future ->
80 | future.addListener({
81 | continuation.resume(future.get())
82 | }, ContextCompat.getMainExecutor(context))
83 | }
84 | }
85 |
86 | val previewUseCase = Preview.Builder()
87 | .build()
88 | .also { it.setSurfaceProvider(previewView.surfaceProvider) }
89 |
90 | runCatching {
91 | cameraProvider.unbindAll()
92 | cameraProvider.bindToLifecycle(
93 | lifecycleOwner,
94 | cameraSelector,
95 | previewUseCase,
96 | imageCapture
97 | )
98 | }
99 | }
100 |
101 | AndroidView(modifier = modifier, factory = { previewView })
102 | }
--------------------------------------------------------------------------------
/app/src/main/java/in/surajsau/jisho/ui/base/camera/CameraControls.kt:
--------------------------------------------------------------------------------
1 | package `in`.surajsau.jisho.ui.base.camera
2 |
3 | import `in`.surajsau.jisho.R
4 | import androidx.compose.foundation.Image
5 | import androidx.compose.foundation.background
6 | import androidx.compose.foundation.border
7 | import androidx.compose.foundation.clickable
8 | import androidx.compose.foundation.layout.*
9 | import androidx.compose.foundation.shape.CircleShape
10 | import androidx.compose.runtime.Composable
11 | import androidx.compose.ui.Alignment
12 | import androidx.compose.ui.Modifier
13 | import androidx.compose.ui.draw.clipToBounds
14 | import androidx.compose.ui.graphics.Color
15 | import androidx.compose.ui.layout.ContentScale
16 | import androidx.compose.ui.res.painterResource
17 | import androidx.compose.ui.unit.dp
18 |
19 | @Composable
20 | fun CameraControls(
21 | modifier: Modifier = Modifier,
22 | onCameraAction: (CameraAction) -> Unit
23 | ) {
24 |
25 | Row(
26 | modifier = modifier
27 | .padding(16.dp),
28 | horizontalArrangement = Arrangement.SpaceEvenly,
29 | verticalAlignment = Alignment.Bottom
30 | ) {
31 |
32 | Spacer(modifier = Modifier.size(48.dp))
33 |
34 | Box(
35 | modifier = Modifier
36 | .size(48.dp)
37 | .border(width = 2.dp, color = Color.DarkGray, shape = CircleShape)
38 | .background(Color.White, CircleShape)
39 | .clickable { onCameraAction.invoke(CameraAction.Click) }
40 | )
41 |
42 | Image(
43 | painter = painterResource(id = R.drawable.ic_switch_camera),
44 | contentDescription = null,
45 | modifier = Modifier
46 | .size(36.dp)
47 | .clickable { onCameraAction.invoke(CameraAction.SwitchCamera) }
48 | .clipToBounds(),
49 | contentScale = ContentScale.Fit
50 | )
51 |
52 | }
53 |
54 | }
--------------------------------------------------------------------------------
/app/src/main/java/in/surajsau/jisho/ui/cardreader/CameraScreen.kt:
--------------------------------------------------------------------------------
1 | package `in`.surajsau.jisho.ui.cardreader
2 |
3 | import `in`.surajsau.jisho.ui.base.camera.Camera
4 | import `in`.surajsau.jisho.ui.base.camera.CameraAction
5 | import `in`.surajsau.jisho.ui.base.camera.CameraControls
6 | import androidx.compose.foundation.layout.Column
7 | import androidx.compose.foundation.layout.fillMaxWidth
8 | import androidx.compose.foundation.layout.padding
9 | import androidx.compose.material.Text
10 | import androidx.compose.runtime.*
11 | import androidx.compose.ui.Modifier
12 | import androidx.compose.ui.unit.dp
13 |
14 | @Composable
15 | fun CameraScreen(
16 | modifier: Modifier = Modifier,
17 | instructionMessage: String,
18 | onImageCaptured: (String) -> Unit,
19 | ) {
20 |
21 | var cameraAction by remember { mutableStateOf(CameraAction.None) }
22 |
23 | Column(modifier = modifier) {
24 |
25 | Camera(
26 | modifier = Modifier
27 | .fillMaxWidth()
28 | .weight(1f),
29 | cameraAction = cameraAction,
30 | onImageCaptured = {
31 | onImageCaptured.invoke(it)
32 | cameraAction = CameraAction.None
33 | })
34 |
35 | Text(
36 | modifier = Modifier
37 | .fillMaxWidth()
38 | .padding(16.dp),
39 | text = instructionMessage,
40 | )
41 |
42 | CameraControls(
43 | modifier = Modifier.fillMaxWidth()
44 | .weight(1f),
45 | onCameraAction = { cameraAction = it }
46 | )
47 | }
48 | }
--------------------------------------------------------------------------------
/app/src/main/java/in/surajsau/jisho/ui/cardreader/CardReaderScreen.kt:
--------------------------------------------------------------------------------
1 | package `in`.surajsau.jisho.ui.cardreader
2 |
3 | import `in`.surajsau.jisho.base.use
4 | import `in`.surajsau.jisho.ui.base.AskPermissionScreen
5 | import android.widget.Toast
6 | import androidx.compose.foundation.layout.*
7 | import androidx.compose.material.*
8 | import androidx.compose.runtime.Composable
9 | import androidx.compose.runtime.LaunchedEffect
10 | import androidx.compose.ui.Alignment
11 | import androidx.compose.ui.Modifier
12 | import androidx.compose.ui.platform.LocalContext
13 | import androidx.compose.ui.unit.dp
14 |
15 | @OptIn(ExperimentalMaterialApi::class)
16 | @Composable
17 | fun CardReaderScreen(
18 | modifier: Modifier = Modifier,
19 | onDismiss: (() -> Unit)? = null,
20 | ) {
21 |
22 | val context = LocalContext.current
23 |
24 | val (state, event) = use(viewModel = LocalOnboardingViewModel.current, initialStateValue = CardReaderViewModel.State())
25 |
26 | LaunchedEffect(state.instruction) {
27 | val instruction = state.instruction.takeIf { it.isNotEmpty() } ?: return@LaunchedEffect
28 | Toast.makeText(context, instruction, Toast.LENGTH_SHORT).show()
29 | }
30 |
31 | Box(modifier = modifier) {
32 | AskPermissionScreen(
33 | permission = android.Manifest.permission.CAMERA,
34 | onDismiss = { onDismiss?.invoke() },
35 | onPermissionDeniedFallback = { event(CardReaderViewModel.Event.OnPermissionDenied) }
36 | ) {
37 | CameraScreen(
38 | modifier = Modifier.fillMaxSize(),
39 | instructionMessage = state.instruction,
40 | onImageCaptured = { event(CardReaderViewModel.Event.CameraResultReceived(it)) }
41 | )
42 | }
43 |
44 | if (state.showLoader) {
45 | CircularProgressIndicator(
46 | modifier = Modifier
47 | .align(Alignment.Center)
48 | .padding(vertical = 24.dp)
49 | )
50 | }
51 | }
52 | }
--------------------------------------------------------------------------------
/app/src/main/java/in/surajsau/jisho/ui/cardreader/DetailsScreen.kt:
--------------------------------------------------------------------------------
1 | package `in`.surajsau.jisho.ui.cardreader
2 |
3 | import `in`.surajsau.jisho.base.use
4 | import androidx.compose.foundation.layout.*
5 | import androidx.compose.foundation.text.KeyboardOptions
6 | import androidx.compose.material.*
7 | import androidx.compose.material.icons.Icons
8 | import androidx.compose.material.icons.filled.ArrowBack
9 | import androidx.compose.runtime.*
10 | import androidx.compose.ui.Alignment
11 | import androidx.compose.ui.Modifier
12 | import androidx.compose.ui.graphics.Color
13 | import androidx.compose.ui.text.font.FontWeight
14 | import androidx.compose.ui.text.input.KeyboardCapitalization
15 | import androidx.compose.ui.text.input.KeyboardType
16 | import androidx.compose.ui.unit.dp
17 | import androidx.compose.ui.unit.sp
18 |
19 | @Composable
20 | fun DetailsScreen(
21 | modifier: Modifier = Modifier,
22 | screen: CardReaderViewModel.Screen,
23 | navigateBack: () -> Unit,
24 | ) {
25 |
26 | val (_, event) = use(
27 | viewModel = LocalOnboardingViewModel.current,
28 | initialStateValue = CardReaderViewModel.State(),
29 | )
30 |
31 | var membershipNumber by remember { mutableStateOf("") }
32 | var name by remember { mutableStateOf("") }
33 | var year by remember { mutableStateOf("") }
34 | var degree by remember { mutableStateOf("") }
35 | var address by remember { mutableStateOf("") }
36 | var mobileNumber by remember { mutableStateOf("") }
37 | var email by remember { mutableStateOf("") }
38 | var dob by remember { mutableStateOf("") }
39 |
40 | LaunchedEffect(screen) {
41 | if (screen is CardReaderViewModel.Screen.FilledDetails) {
42 | membershipNumber = screen.front.membershipNumber
43 | name = screen.front.name
44 | year = screen.front.year
45 | degree = screen.front.degree
46 | dob = screen.front.dateOfBirth.toString()
47 |
48 | address = screen.back.address
49 | mobileNumber = screen.back.mobileNumber
50 | email = screen.back.email
51 | }
52 | }
53 |
54 | Column(modifier = modifier) {
55 |
56 | TopAppBar(backgroundColor = Color.White, elevation = 0.dp) {
57 |
58 | Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxSize()) {
59 | IconButton(onClick = { navigateBack.invoke() }) {
60 | Icon(imageVector = Icons.Filled.ArrowBack, contentDescription = "back")
61 | }
62 |
63 | Text(text = screen.title, fontWeight = FontWeight.Bold, fontSize = 16.sp)
64 | }
65 | }
66 |
67 | Column(
68 | modifier = Modifier
69 | .fillMaxWidth()
70 | .weight(1f)
71 | .padding(16.dp),
72 | horizontalAlignment = Alignment.CenterHorizontally,
73 | ) {
74 |
75 | TextField(
76 | modifier = Modifier.fillMaxWidth(),
77 | value = name,
78 | onValueChange = { name = it },
79 | label = { Text(text = "Name") },
80 | keyboardOptions = KeyboardOptions(
81 | capitalization = KeyboardCapitalization.Words,
82 | keyboardType = KeyboardType.Text
83 | )
84 | )
85 |
86 | Spacer(modifier = Modifier.height(16.dp))
87 |
88 | TextField(
89 | modifier = Modifier.fillMaxWidth(),
90 | value = membershipNumber,
91 | onValueChange = { membershipNumber = it },
92 | label = { Text(text = "Membership No.") },
93 | keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number)
94 | )
95 |
96 | Spacer(modifier = Modifier.height(16.dp))
97 |
98 | Row(modifier = Modifier.fillMaxWidth()) {
99 |
100 | TextField(
101 | modifier = Modifier.weight(3f),
102 | value = degree,
103 | onValueChange = { degree = it },
104 | label = { Text(text = "Degree") },
105 | keyboardOptions = KeyboardOptions(
106 | keyboardType = KeyboardType.Text,
107 | capitalization = KeyboardCapitalization.Characters,
108 | ),
109 | )
110 |
111 | TextField(
112 | modifier = Modifier
113 | .weight(1f)
114 | .padding(horizontal = 4.dp),
115 | value = year,
116 | onValueChange = { year = it },
117 | label = { Text(text = "Year") },
118 | keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
119 | )
120 | }
121 |
122 | Spacer(modifier = Modifier.height(16.dp))
123 |
124 | TextField(
125 | modifier = Modifier.fillMaxWidth(),
126 | value = address,
127 | label = { Text(text = "Address") },
128 | onValueChange = { address = it },
129 | maxLines = 3,
130 | )
131 |
132 | Spacer(modifier = Modifier.height(16.dp))
133 |
134 | Row(modifier = Modifier.fillMaxWidth()) {
135 | TextField(
136 | modifier = Modifier.weight(1f),
137 | value = mobileNumber,
138 | onValueChange = { mobileNumber = it },
139 | label = { Text(text = "Mobile No.") },
140 | keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Phone)
141 | )
142 |
143 | Spacer(modifier = Modifier.width(4.dp))
144 |
145 | TextField(
146 | modifier = Modifier.weight(1f),
147 | value = email,
148 | onValueChange = { email = it },
149 | label = { Text(text = "Email") },
150 | keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Email),
151 | )
152 | }
153 |
154 | Spacer(modifier = Modifier.height(16.dp))
155 |
156 | TextField(
157 | modifier = Modifier.fillMaxWidth(),
158 | value = dob,
159 | onValueChange = { dob = it },
160 | label = { Text(text = "Date of Birth") },
161 | keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
162 | )
163 |
164 | Spacer(modifier = Modifier.height(16.dp))
165 |
166 | Button(
167 | modifier = Modifier
168 | .fillMaxWidth()
169 | .padding(horizontal = 24.dp),
170 | onClick = { event(CardReaderViewModel.Event.ConfirmClicked) }
171 | ) {
172 | Text(text = "Confirm")
173 | }
174 | }
175 | }
176 |
177 | }
--------------------------------------------------------------------------------
/app/src/main/java/in/surajsau/jisho/ui/cardreader/OnBoardingScreen.kt:
--------------------------------------------------------------------------------
1 | package `in`.surajsau.jisho.ui.cardreader
2 |
3 | import `in`.surajsau.jisho.base.use
4 | import androidx.compose.foundation.BorderStroke
5 | import androidx.compose.foundation.layout.*
6 | import androidx.compose.foundation.shape.RoundedCornerShape
7 | import androidx.compose.material.Button
8 | import androidx.compose.material.ButtonDefaults
9 | import androidx.compose.material.MaterialTheme
10 | import androidx.compose.material.Text
11 | import androidx.compose.runtime.Composable
12 | import androidx.compose.ui.Alignment
13 | import androidx.compose.ui.Modifier
14 | import androidx.compose.ui.graphics.Color
15 | import androidx.compose.ui.text.font.FontWeight
16 | import androidx.compose.ui.unit.dp
17 | import androidx.compose.ui.unit.sp
18 |
19 | @Composable
20 | fun OnBoardingScreen(
21 | modifier: Modifier = Modifier,
22 | ) {
23 |
24 | val (state, event) = use(
25 | viewModel = LocalOnboardingViewModel.current,
26 | initialStateValue = CardReaderViewModel.State()
27 | )
28 |
29 | Box(modifier = modifier) {
30 | when (val screen = state.screen) {
31 | is CardReaderViewModel.Screen.Intro -> IntroScreen(
32 | modifier = Modifier.fillMaxSize(),
33 | onScreenSelected = { event(CardReaderViewModel.Event.ScreenSelected(it)) }
34 | )
35 |
36 | is CardReaderViewModel.Screen.CardReader -> CardReaderScreen(modifier = Modifier.fillMaxSize())
37 |
38 | is CardReaderViewModel.Screen.EmptyDetails,
39 |
40 | is CardReaderViewModel.Screen.FilledDetails -> {
41 | DetailsScreen(
42 | modifier = Modifier.fillMaxSize(),
43 | screen = screen,
44 | navigateBack = {}
45 | )
46 | }
47 | }
48 | }
49 | }
50 |
51 | @Composable
52 | private fun IntroScreen(
53 | modifier: Modifier = Modifier,
54 | onScreenSelected: (CardReaderViewModel.Screen) -> Unit,
55 | ) {
56 |
57 | Box(modifier = modifier) {
58 |
59 | Column(
60 | modifier = Modifier
61 | .align(Alignment.Center)
62 | .padding(24.dp),
63 | horizontalAlignment = Alignment.CenterHorizontally
64 | ) {
65 |
66 | Text(
67 | text = "Welcome to Alumni App",
68 | fontSize = 24.sp,
69 | fontWeight = FontWeight.Bold
70 | )
71 |
72 | Spacer(modifier = Modifier.height(16.dp))
73 |
74 | Button(onClick = { onScreenSelected.invoke(CardReaderViewModel.Screen.CardReader) }) {
75 | Text(text = "Open Card Reader")
76 | }
77 |
78 | Spacer(modifier = Modifier.height(16.dp))
79 |
80 | Text(text = "- or -")
81 |
82 | Spacer(modifier = Modifier.height(16.dp))
83 |
84 | Button(
85 | onClick = { onScreenSelected.invoke(CardReaderViewModel.Screen.EmptyDetails) },
86 | colors = ButtonDefaults.buttonColors(
87 | backgroundColor = Color.Transparent,
88 | contentColor = MaterialTheme.colors.primary,
89 | disabledBackgroundColor = Color.Transparent
90 | ),
91 | elevation = null,
92 | border = BorderStroke(width = 1.dp, color = MaterialTheme.colors.primary),
93 | shape = MaterialTheme.shapes.small
94 | ) {
95 | Text(text = "Fill out manually")
96 | }
97 |
98 | }
99 | }
100 | }
--------------------------------------------------------------------------------
/app/src/main/java/in/surajsau/jisho/ui/cardreader/OnBoardingViewModel.kt:
--------------------------------------------------------------------------------
1 | package `in`.surajsau.jisho.ui.cardreader
2 |
3 | import `in`.surajsau.jisho.base.Optional
4 | import `in`.surajsau.jisho.base.SingleFlowViewModel
5 | import `in`.surajsau.jisho.domain.cardreader.GetCardDetails
6 | import `in`.surajsau.jisho.domain.models.IDCard
7 | import androidx.compose.runtime.compositionLocalOf
8 | import androidx.lifecycle.ViewModel
9 | import androidx.lifecycle.viewModelScope
10 | import dagger.hilt.android.lifecycle.HiltViewModel
11 | import kotlinx.coroutines.Dispatchers
12 | import kotlinx.coroutines.flow.*
13 | import kotlinx.coroutines.launch
14 | import javax.inject.Inject
15 |
16 | @HiltViewModel
17 | class CardReaderViewModelImpl @Inject constructor(
18 | private val getFrontDetails: GetCardDetails,
19 | private val getBackDetails: GetCardDetails,
20 | ): ViewModel(), CardReaderViewModel {
21 |
22 | private val _frontDetails = MutableStateFlow>(Optional.Empty)
23 | private val _backDetails = MutableStateFlow>(Optional.Empty)
24 |
25 | private val _cardReaderMode = MutableStateFlow(CardReaderViewModel.CardReaderMode.FrontCapture)
26 |
27 | private val _currentScreen = MutableStateFlow(CardReaderViewModel.Screen.Intro)
28 |
29 | private val _showLoader = MutableStateFlow(false)
30 |
31 | private var instruction = ""
32 |
33 | override val state: StateFlow
34 | get() = combine(
35 | _frontDetails,
36 | _backDetails,
37 | _cardReaderMode,
38 | _currentScreen,
39 | _showLoader,
40 | ) { frontDetails, backDetails, cardReaderMode, currentScreen, showLoader ->
41 | // val instruction = when (cardReaderMode) {
42 | // CardReaderViewModel.CardReaderMode.FrontCapture -> "Show front of the Card"
43 | // CardReaderViewModel.CardReaderMode.BackCapture -> "Show back of the Card"
44 | //
45 | // else -> ""
46 | // }
47 |
48 | val screen = when (cardReaderMode) {
49 | CardReaderViewModel.CardReaderMode.Result -> CardReaderViewModel.Screen.FilledDetails(
50 | front = frontDetails.getValue(),
51 | back = backDetails.getValue(),
52 | )
53 |
54 | else -> currentScreen
55 | }
56 |
57 | CardReaderViewModel.State(
58 | instruction = instruction,
59 | showLoader = showLoader,
60 | screen = screen,
61 | )
62 | }
63 | .stateIn(viewModelScope, SharingStarted.WhileSubscribed(), CardReaderViewModel.State())
64 |
65 | override fun onEvent(event: CardReaderViewModel.Event) {
66 | when (event) {
67 | is CardReaderViewModel.Event.CameraResultReceived -> {
68 | val cardReaderMode = _cardReaderMode.value
69 |
70 | _showLoader.value = true
71 |
72 | viewModelScope.launch {
73 | when (cardReaderMode) {
74 | CardReaderViewModel.CardReaderMode.FrontCapture -> {
75 | getFrontDetails.invoke(fileName = event.fileName)
76 | .flowOn(Dispatchers.IO)
77 | .collect {
78 | _frontDetails.value = Optional.of(it)
79 | _cardReaderMode.value = CardReaderViewModel.CardReaderMode.BackCapture
80 | }
81 | }
82 |
83 | CardReaderViewModel.CardReaderMode.BackCapture -> {
84 | getBackDetails.invoke(fileName = event.fileName)
85 | .flowOn(Dispatchers.IO)
86 | .collect {
87 | _backDetails.value = Optional.of(it)
88 | _cardReaderMode.value = CardReaderViewModel.CardReaderMode.Result
89 | }
90 | }
91 | }
92 | }
93 | }
94 |
95 | is CardReaderViewModel.Event.ScreenSelected -> {
96 | _currentScreen.value = event.screen
97 | }
98 |
99 | is CardReaderViewModel.Event.ConfirmClicked -> {
100 |
101 | }
102 | }
103 | }
104 |
105 | }
106 |
107 | interface CardReaderViewModel : SingleFlowViewModel {
108 |
109 | sealed class Event{
110 | data class CameraResultReceived(val fileName: String): Event()
111 | object OnPermissionDenied: Event()
112 | data class ScreenSelected(val screen: Screen): Event()
113 | object ConfirmClicked: Event()
114 | }
115 |
116 | data class State(
117 | val showLoader: Boolean = false,
118 | val instruction: String = "",
119 | val screen: Screen = Screen.Intro,
120 | )
121 |
122 | enum class CardReaderMode {
123 | FrontCapture,
124 | BackCapture,
125 | Result,
126 | }
127 |
128 | sealed class Screen(val title: String) {
129 | object Intro: Screen("")
130 | object CardReader: Screen("")
131 | object EmptyDetails: Screen("Enter details")
132 | data class FilledDetails(
133 | val front: IDCard.Front,
134 | val back: IDCard.Back,
135 | ): Screen("Confirm details")
136 | }
137 | }
138 |
139 | val LocalOnboardingViewModel = compositionLocalOf {
140 | error("Factory for CardReaderViewModel is not implemented")
141 | }
--------------------------------------------------------------------------------
/app/src/main/java/in/surajsau/jisho/ui/chat/ChatClickableText.kt:
--------------------------------------------------------------------------------
1 | package `in`.surajsau.jisho.ui.chat
2 |
3 | import `in`.surajsau.jisho.domain.models.chat.ChatAnnotation
4 | import `in`.surajsau.jisho.domain.models.chat.ChatRowModel
5 | import `in`.surajsau.jisho.ui.base.rememberExternalIntentHandler
6 | import android.content.pm.PackageManager
7 | import android.util.Log
8 | import androidx.activity.compose.rememberLauncherForActivityResult
9 | import androidx.activity.result.contract.ActivityResultContracts
10 | import androidx.compose.foundation.text.ClickableText
11 | import androidx.compose.runtime.*
12 | import androidx.compose.ui.Modifier
13 | import androidx.compose.ui.graphics.Color
14 | import androidx.compose.ui.platform.LocalContext
15 | import androidx.compose.ui.text.AnnotatedString
16 | import androidx.compose.ui.text.TextStyle
17 | import androidx.core.content.ContextCompat
18 | import com.google.accompanist.permissions.ExperimentalPermissionsApi
19 | import com.google.accompanist.permissions.rememberPermissionState
20 |
21 | @OptIn(ExperimentalPermissionsApi::class)
22 | @Composable
23 | fun ChatClickableText(
24 | annotatedString: AnnotatedString,
25 | annotationMaps: List,
26 | modifier: Modifier = Modifier
27 | ) {
28 | val callPermissionState = rememberPermissionState(permission = android.Manifest.permission.CALL_PHONE)
29 |
30 | val externalIntentHandler = rememberExternalIntentHandler()
31 |
32 | ClickableText(
33 | modifier = modifier,
34 | text = annotatedString,
35 | style = TextStyle(color = Color.White),
36 | onClick = { offset ->
37 | annotationMaps.forEach { annotation ->
38 | val tag = annotation.tag
39 | annotatedString.getStringAnnotations(tag = tag, start = offset, end = offset).firstOrNull()?.let {
40 | when (annotation) {
41 | is ChatAnnotation.Reminder -> externalIntentHandler.openAlarm(message = it.item, timestamp = annotation.timeStamp)
42 | is ChatAnnotation.Address -> externalIntentHandler.openMap(annotation.address)
43 | is ChatAnnotation.Phone -> {
44 | when {
45 | callPermissionState.hasPermission -> externalIntentHandler.openPhone(annotation.phone)
46 | else -> callPermissionState.launchPermissionRequest()
47 | }
48 |
49 | }
50 | is ChatAnnotation.Email -> externalIntentHandler.openEmail(annotation.email)
51 | }
52 | }
53 | }
54 | }
55 | )
56 | }
--------------------------------------------------------------------------------
/app/src/main/java/in/surajsau/jisho/ui/digitalink/DigitalInkScreen.kt:
--------------------------------------------------------------------------------
1 | package `in`.surajsau.jisho.ui.digitalink
2 |
3 | import `in`.surajsau.jisho.base.use
4 | import `in`.surajsau.jisho.ui.theme.DigitalInkColors
5 | import `in`.surajsau.jisho.ui.theme.Purple700
6 | import androidx.compose.foundation.background
7 | import androidx.compose.foundation.clickable
8 | import androidx.compose.foundation.layout.*
9 | import androidx.compose.foundation.lazy.LazyRow
10 | import androidx.compose.foundation.lazy.items
11 | import androidx.compose.foundation.shape.RoundedCornerShape
12 | import androidx.compose.material.*
13 | import androidx.compose.runtime.*
14 | import androidx.compose.ui.Alignment
15 | import androidx.compose.ui.Modifier
16 | import androidx.compose.ui.graphics.Color
17 | import androidx.compose.ui.platform.LocalLifecycleOwner
18 | import androidx.compose.ui.text.TextStyle
19 | import androidx.compose.ui.text.style.TextAlign
20 | import androidx.compose.ui.unit.dp
21 | import androidx.compose.ui.unit.sp
22 | import androidx.lifecycle.Lifecycle
23 | import androidx.lifecycle.LifecycleEventObserver
24 |
25 | @Composable
26 | fun DigitalInkScreen(
27 | modifier: Modifier = Modifier
28 | ) {
29 |
30 | val lifecycleOwner = LocalLifecycleOwner.current
31 |
32 | val (state, event) = use(LocalDigitalInkViewModel.current, DigitalInkViewModel.State())
33 |
34 | DisposableEffect(Unit) {
35 | val lifecycleObserver = LifecycleEventObserver { _, event ->
36 | if (event == Lifecycle.Event.ON_STOP)
37 | event(DigitalInkViewModel.Event.OnStop)
38 | }
39 |
40 | lifecycleOwner.lifecycle.addObserver(lifecycleObserver)
41 | onDispose { lifecycleOwner.lifecycle.removeObserver(lifecycleObserver) }
42 | }
43 |
44 | Box(modifier = modifier) {
45 | if (state.showModelStatusProgress) {
46 | ModelStatusProgress(
47 | statusText = "Checking models...",
48 | modifier = Modifier
49 | .align(Alignment.Center)
50 | )
51 | } else {
52 |
53 | Column(modifier = Modifier.fillMaxSize()) {
54 |
55 | Card(elevation = 4.dp) {
56 | Column(modifier = Modifier.fillMaxWidth()) {
57 | TextField(
58 | value = state.finalText,
59 | onValueChange = { event(DigitalInkViewModel.Event.TextChanged(it)) },
60 | modifier = Modifier.fillMaxWidth(),
61 | maxLines = 1,
62 | textStyle = TextStyle(color = DigitalInkColors.Text, fontSize = 24.sp),
63 | colors = TextFieldDefaults.textFieldColors(
64 | backgroundColor = Color.White,
65 | placeholderColor = DigitalInkColors.PredictionPlaceholder,
66 | focusedIndicatorColor = Color.Transparent,
67 | unfocusedIndicatorColor = Color.Transparent,
68 | cursorColor = DigitalInkColors.PredictionText
69 | ),
70 | placeholder = {
71 | Text(
72 | text = "Enter text(Japanese)",
73 | fontSize = 24.sp,
74 | )
75 | },
76 | shape = RoundedCornerShape(0.dp)
77 | )
78 |
79 | Spacer(modifier = Modifier
80 | .fillMaxWidth()
81 | .height(1.dp)
82 | .background(Color.LightGray))
83 |
84 | Text(
85 | text = state.translation,
86 | fontSize = 24.sp,
87 | color = Purple700,
88 | modifier = Modifier
89 | .fillMaxWidth()
90 | .padding(16.dp)
91 | )
92 | }
93 | }
94 |
95 | Spacer(modifier = Modifier.weight(1f, fill = true))
96 |
97 | LazyRow(
98 | modifier = Modifier
99 | .fillMaxWidth()
100 | .height(48.dp)
101 | .background(DigitalInkColors.PredictionBackground)
102 | ) {
103 | items(state.predictions) { prediction ->
104 | Prediction(
105 | text = prediction,
106 | onClick = { DigitalInkViewModel.Event.PredictionSelected(it) }
107 | )
108 | }
109 | }
110 |
111 | DrawSpace(
112 | reset = state.resetCanvas,
113 | onDrawEvent = { event(DigitalInkViewModel.Event.Pointer(it)) },
114 | modifier = Modifier
115 | .fillMaxWidth()
116 | .weight(1f, fill = true)
117 | )
118 | }
119 | }
120 | }
121 | }
122 |
123 | @Composable
124 | fun ModelStatusProgress(
125 | statusText: String,
126 | modifier: Modifier = Modifier
127 | ) {
128 |
129 | Column(modifier = modifier) {
130 |
131 | CircularProgressIndicator(
132 | modifier = Modifier
133 | .align(Alignment.CenterHorizontally)
134 | .padding(vertical = 24.dp)
135 | )
136 |
137 | Text(
138 | text = statusText,
139 | textAlign = TextAlign.Center,
140 | fontSize = 18.sp
141 | )
142 | }
143 | }
144 |
145 | @Composable
146 | fun Prediction(
147 | text: String,
148 | onClick: (String) -> Unit,
149 | modifier: Modifier = Modifier
150 | ) {
151 |
152 | Row(modifier = modifier) {
153 | Text(
154 | text = text,
155 | color = DigitalInkColors.PredictionText,
156 | modifier = modifier
157 | .padding(horizontal = 16.dp, vertical = 8.dp)
158 | .clickable { onClick.invoke(text) },
159 | )
160 |
161 | Box(modifier = Modifier
162 | .fillMaxHeight()
163 | .width(1.dp)
164 | .background(DigitalInkColors.PredictionDivider)
165 | )
166 | }
167 | }
--------------------------------------------------------------------------------
/app/src/main/java/in/surajsau/jisho/ui/digitalink/DigitalInkViewModel.kt:
--------------------------------------------------------------------------------
1 | package `in`.surajsau.jisho.ui.digitalink
2 |
3 | import `in`.surajsau.jisho.base.SingleFlowViewModel
4 | import `in`.surajsau.jisho.data.DigitalInkProvider
5 | import `in`.surajsau.jisho.data.TranslatorProvider
6 | import androidx.compose.runtime.Composable
7 | import androidx.compose.runtime.compositionLocalOf
8 | import androidx.lifecycle.ViewModel
9 | import androidx.lifecycle.viewModelScope
10 | import dagger.hilt.android.lifecycle.HiltViewModel
11 | import kotlinx.coroutines.Job
12 | import kotlinx.coroutines.delay
13 | import kotlinx.coroutines.flow.*
14 | import kotlinx.coroutines.launch
15 | import javax.inject.Inject
16 |
17 | @HiltViewModel
18 | class DigitalInkViewModelImpl @Inject constructor(
19 | private val digitalInkProvider: DigitalInkProvider,
20 | private val translatorProvider: TranslatorProvider
21 | ): ViewModel(), DigitalInkViewModel {
22 |
23 | private var finishRecordingJob: Job? = null
24 |
25 | private val _digitalInkModelStatus = digitalInkProvider.checkIfModelIsDownlaoded()
26 | .flatMapLatest { status ->
27 | if (status == MLKitModelStatus.Downloaded)
28 | flowOf(status)
29 | else
30 | digitalInkProvider.downloadModel()
31 | }
32 | .stateIn(viewModelScope, SharingStarted.Lazily, MLKitModelStatus.NotDownloaded)
33 |
34 | private val _translatorModelStatus = translatorProvider.checkIfModelIsDownloaded()
35 | .stateIn(viewModelScope, SharingStarted.Lazily, MLKitModelStatus.NotDownloaded)
36 |
37 | private val _predictions = digitalInkProvider.predictions
38 | .consumeAsFlow()
39 | .onEach {
40 | if (it.isEmpty())
41 | return@onEach
42 |
43 | setFinalText(text = _finalText.value.plus(it[0]))
44 | }
45 | .stateIn(viewModelScope, SharingStarted.Lazily, emptyList())
46 |
47 | private val _translation = translatorProvider.translation
48 | .consumeAsFlow()
49 | .stateIn(viewModelScope, SharingStarted.Lazily, "")
50 |
51 | private val _finalText = MutableStateFlow("")
52 | private val _resetCanvas = MutableStateFlow(false)
53 |
54 | override val state: StateFlow
55 | get() = combine(
56 | _digitalInkModelStatus,
57 | _translatorModelStatus,
58 | _resetCanvas,
59 | _predictions,
60 | _translation,
61 | _finalText
62 | ) { result ->
63 | val digitalInkModelStatus = result[0] as MLKitModelStatus
64 | val translatorModelStatus = result[1] as MLKitModelStatus
65 | val resetCanvas = result[2] as Boolean
66 | val predictions = result[3] as List
67 | val translation = result[4] as String
68 | val finalText = result[5] as String
69 | val areModelsDownloaded = digitalInkModelStatus == MLKitModelStatus.Downloaded && translatorModelStatus == MLKitModelStatus.Downloaded
70 |
71 | DigitalInkViewModel.State(
72 | resetCanvas = resetCanvas,
73 | showModelStatusProgress = !areModelsDownloaded,
74 | finalText = finalText,
75 | translation = translation,
76 | predictions = predictions
77 | )
78 | }.stateIn(viewModelScope, SharingStarted.Eagerly, DigitalInkViewModel.State())
79 |
80 | override fun onEvent(event: DigitalInkViewModel.Event) {
81 |
82 | when (event) {
83 | is DigitalInkViewModel.Event.Pointer -> {
84 |
85 | when (val drawEvent = event.event) {
86 | is DrawEvent.Down -> {
87 | this.finishRecordingJob?.cancel()
88 | _resetCanvas.value = false
89 |
90 | digitalInkProvider.record(drawEvent.x, drawEvent.y)
91 | }
92 |
93 | is DrawEvent.Move -> {
94 | digitalInkProvider.record(drawEvent.x, drawEvent.y)
95 | }
96 |
97 | is DrawEvent.Up -> {
98 | this.finishRecordingJob = viewModelScope.launch {
99 | delay(DEBOUNCE_INTERVAL)
100 | _resetCanvas.value = true
101 | digitalInkProvider.finishRecording()
102 | }
103 | }
104 | }
105 | }
106 |
107 | is DigitalInkViewModel.Event.OnStop -> {
108 | digitalInkProvider.close()
109 | translatorProvider.close()
110 | }
111 |
112 | is DigitalInkViewModel.Event.TextChanged -> {
113 | setFinalText(event.text)
114 | }
115 |
116 | is DigitalInkViewModel.Event.PredictionSelected -> {
117 | setFinalText(text = _finalText.value.dropLast(1).plus(event.prediction))
118 | }
119 | }
120 | }
121 |
122 | private fun setFinalText(text: String) {
123 | _finalText.value = text
124 |
125 | if (text.isNotEmpty())
126 | translatorProvider.translate(text)
127 | }
128 |
129 | companion object {
130 | private const val DEBOUNCE_INTERVAL = 1000L
131 | }
132 | }
133 |
134 | interface DigitalInkViewModel: SingleFlowViewModel {
135 |
136 | data class State(
137 | val resetCanvas: Boolean = false,
138 | val showModelStatusProgress: Boolean = false,
139 | val finalText: String = "",
140 | val translation: String = "",
141 | val predictions: List = emptyList(),
142 | )
143 |
144 | sealed class Event {
145 | data class TextChanged(val text: String): Event()
146 | data class Pointer(val event: DrawEvent): Event()
147 | data class PredictionSelected(val prediction: String): Event()
148 |
149 | object OnStop: Event()
150 | }
151 | }
152 |
153 | val LocalDigitalInkViewModel = compositionLocalOf {
154 | error("LocalDigitalViewModelFactory not provided")
155 | }
156 |
157 | @Composable
158 | fun provideDigitalInkViewModel(viewModelFactory: @Composable () -> DigitalInkViewModel)
159 | = LocalDigitalInkViewModel provides viewModelFactory.invoke()
--------------------------------------------------------------------------------
/app/src/main/java/in/surajsau/jisho/ui/digitalink/DrawSpace.kt:
--------------------------------------------------------------------------------
1 | package `in`.surajsau.jisho.ui.digitalink
2 |
3 | import `in`.surajsau.jisho.ui.theme.DigitalInkColors
4 | import android.view.MotionEvent
5 | import androidx.compose.foundation.Canvas
6 | import androidx.compose.foundation.background
7 | import androidx.compose.foundation.gestures.detectDragGestures
8 | import androidx.compose.runtime.*
9 | import androidx.compose.ui.ExperimentalComposeUiApi
10 | import androidx.compose.ui.Modifier
11 | import androidx.compose.ui.graphics.Color
12 | import androidx.compose.ui.graphics.Path
13 | import androidx.compose.ui.graphics.StrokeCap
14 | import androidx.compose.ui.graphics.StrokeJoin
15 | import androidx.compose.ui.graphics.drawscope.Stroke
16 | import androidx.compose.ui.input.pointer.pointerInput
17 | import androidx.compose.ui.input.pointer.pointerInteropFilter
18 | import kotlin.math.abs
19 |
20 | sealed class DrawEvent {
21 | data class Down(val x: Float, val y: Float): DrawEvent()
22 | data class Move(val x: Float, val y: Float): DrawEvent()
23 | object Up: DrawEvent()
24 | }
25 |
26 | private sealed class DrawPath {
27 | data class MoveTo(val x: Float, val y: Float): DrawPath()
28 | data class CurveTo(val prevX: Float, val prevY: Float, val x: Float, val y: Float): DrawPath()
29 | }
30 |
31 | @OptIn(ExperimentalComposeUiApi::class)
32 | @Composable
33 | fun DrawSpace(
34 | modifier: Modifier = Modifier,
35 | reset: Boolean = false,
36 | onDrawEvent: (DrawEvent) -> Unit,
37 | ) {
38 |
39 | val path = remember { Path() }
40 |
41 | var drawPath by remember { mutableStateOf(null) }
42 |
43 | if (reset) {
44 | drawPath = null
45 | path.reset()
46 | }
47 |
48 | Canvas(
49 | modifier = modifier
50 | .background(Color.White)
51 | .pointerInteropFilter { event ->
52 | when (event.action) {
53 | MotionEvent.ACTION_DOWN -> {
54 | drawPath = DrawPath.MoveTo(event.x, event.y)
55 | onDrawEvent.invoke(DrawEvent.Down(event.x, event.y))
56 | }
57 | MotionEvent.ACTION_MOVE -> {
58 | val prevX = when (drawPath) {
59 | is DrawPath.MoveTo -> (drawPath as DrawPath.MoveTo).x
60 | is DrawPath.CurveTo -> (drawPath as DrawPath.CurveTo).x
61 |
62 | else -> 0f
63 | }
64 |
65 | val prevY = when (drawPath) {
66 | is DrawPath.MoveTo -> (drawPath as DrawPath.MoveTo).y
67 | is DrawPath.CurveTo -> (drawPath as DrawPath.CurveTo).y
68 |
69 | else -> 0f
70 | }
71 | drawPath = DrawPath.CurveTo(prevX, prevY, event.x, event.y)
72 | onDrawEvent.invoke(DrawEvent.Move(event.x, event.y))
73 | }
74 | MotionEvent.ACTION_UP -> {
75 | onDrawEvent.invoke(DrawEvent.Up)
76 | }
77 |
78 | else -> { /* do nothing */ }
79 | }
80 |
81 | return@pointerInteropFilter true
82 | }
83 | ) {
84 | if (drawPath == null)
85 | return@Canvas
86 |
87 | when (drawPath) {
88 | is DrawPath.MoveTo -> {
89 | val (x, y) = drawPath as DrawPath.MoveTo
90 | path.moveTo(x, y)
91 | }
92 |
93 | is DrawPath.CurveTo -> {
94 | val (prevX, prevY, x, y) = drawPath as DrawPath.CurveTo
95 | path.quadraticBezierTo(prevX, prevY, (x + prevX)/2, (y + prevY)/2)
96 | }
97 | }
98 |
99 | drawPath(
100 | path = path,
101 | color = DigitalInkColors.Stroke,
102 | style = Stroke(width = 5f, cap = StrokeCap.Round, join = StrokeJoin.Round)
103 | )
104 | }
105 | }
--------------------------------------------------------------------------------
/app/src/main/java/in/surajsau/jisho/ui/digitalink/MLKitModelStatus.kt:
--------------------------------------------------------------------------------
1 | package `in`.surajsau.jisho.ui.digitalink
2 |
3 | enum class MLKitModelStatus {
4 | NotDownloaded, Downloaded, CheckingDownload, Downloading
5 | }
--------------------------------------------------------------------------------
/app/src/main/java/in/surajsau/jisho/ui/facenet/CameraScreen.kt:
--------------------------------------------------------------------------------
1 | package `in`.surajsau.jisho.ui.facenet
2 |
3 | import `in`.surajsau.jisho.ui.base.camera.Camera
4 | import `in`.surajsau.jisho.ui.base.camera.CameraAction
5 | import `in`.surajsau.jisho.ui.base.camera.CameraControls
6 | import androidx.compose.foundation.layout.Column
7 | import androidx.compose.foundation.layout.fillMaxWidth
8 | import androidx.compose.runtime.*
9 | import androidx.compose.ui.Modifier
10 |
11 | @Composable
12 | fun CameraScreen(
13 | modifier: Modifier,
14 | onImageCaptured: (String) -> Unit,
15 | ) {
16 |
17 | var cameraAction by remember { mutableStateOf(CameraAction.None) }
18 |
19 | Column(modifier = modifier) {
20 |
21 | Camera(
22 | modifier = Modifier
23 | .fillMaxWidth()
24 | .weight(1f),
25 | cameraAction = cameraAction,
26 | onImageCaptured = onImageCaptured
27 | )
28 |
29 | CameraControls(
30 | modifier = Modifier.fillMaxWidth(),
31 | onCameraAction = { cameraAction = it }
32 | )
33 | }
34 | }
--------------------------------------------------------------------------------
/app/src/main/java/in/surajsau/jisho/ui/facenet/SavedFaceImage.kt:
--------------------------------------------------------------------------------
1 | package `in`.surajsau.jisho.ui.facenet
2 |
3 | import `in`.surajsau.jisho.domain.models.FaceModel
4 | import androidx.compose.foundation.Image
5 | import androidx.compose.foundation.background
6 | import androidx.compose.foundation.border
7 | import androidx.compose.foundation.layout.*
8 | import androidx.compose.foundation.shape.CircleShape
9 | import androidx.compose.material.Text
10 | import androidx.compose.runtime.Composable
11 | import androidx.compose.ui.Alignment
12 | import androidx.compose.ui.Modifier
13 | import androidx.compose.ui.draw.clip
14 | import androidx.compose.ui.draw.clipToBounds
15 | import androidx.compose.ui.graphics.Color
16 | import androidx.compose.ui.layout.ContentScale
17 | import androidx.compose.ui.text.style.TextAlign
18 | import androidx.compose.ui.tooling.preview.Preview
19 | import androidx.compose.ui.unit.dp
20 | import androidx.compose.ui.unit.sp
21 | import coil.compose.rememberImagePainter
22 | import java.io.File
23 |
24 | @Composable
25 | fun SavedFace(
26 | modifier: Modifier = Modifier,
27 | isSelected: Boolean = false,
28 | model: FaceModel,
29 | ) {
30 |
31 | Column(
32 | modifier = modifier,
33 | horizontalAlignment = Alignment.CenterHorizontally
34 | ) {
35 | Image(
36 | painter = rememberImagePainter(data = File(model.imageFilePath)),
37 | contentDescription = model.faceName,
38 | modifier = Modifier
39 | .clip(CircleShape)
40 | .background(Color.LightGray)
41 | .border(
42 | width = if (isSelected) 4.dp else 0.dp,
43 | color = Color.DarkGray,
44 | shape = CircleShape
45 | )
46 | .clipToBounds()
47 | .fillMaxWidth()
48 | .aspectRatio(1f),
49 | contentScale = ContentScale.Fit
50 | )
51 |
52 | Text(
53 | text = model.faceName,
54 | modifier = Modifier.padding(top = 4.dp),
55 | fontSize = 12.sp,
56 | maxLines = 2,
57 | textAlign = TextAlign.Center
58 | )
59 | }
60 | }
61 |
62 | @Preview
63 | @Composable
64 | private fun previewSavedFaceImage() {
65 |
66 | SavedFace(
67 | modifier = Modifier.width(56.dp),
68 | model = FaceModel("", "John Doe")
69 | )
70 | }
--------------------------------------------------------------------------------
/app/src/main/java/in/surajsau/jisho/ui/home/HomeScreen.kt:
--------------------------------------------------------------------------------
1 | package `in`.surajsau.jisho.ui.home
2 |
3 | import `in`.surajsau.jisho.base.use
4 | import `in`.surajsau.jisho.data.model.Screen
5 | import `in`.surajsau.jisho.ui.Destinations
6 | import android.os.Build
7 | import androidx.compose.foundation.Image
8 | import androidx.compose.foundation.background
9 | import androidx.compose.foundation.clickable
10 | import androidx.compose.foundation.layout.*
11 | import androidx.compose.foundation.lazy.LazyColumn
12 | import androidx.compose.foundation.lazy.items
13 | import androidx.compose.foundation.lazy.itemsIndexed
14 | import androidx.compose.foundation.shape.RoundedCornerShape
15 | import androidx.compose.material.Text
16 | import androidx.compose.runtime.Composable
17 | import androidx.compose.ui.Modifier
18 | import androidx.compose.ui.draw.clip
19 | import androidx.compose.ui.draw.clipToBounds
20 | import androidx.compose.ui.graphics.Color
21 | import androidx.compose.ui.layout.ContentScale
22 | import androidx.compose.ui.platform.LocalContext
23 | import androidx.compose.ui.text.font.FontWeight
24 | import androidx.compose.ui.unit.dp
25 | import androidx.compose.ui.unit.sp
26 | import coil.ImageLoader
27 | import coil.compose.rememberImagePainter
28 | import coil.decode.GifDecoder
29 | import coil.decode.ImageDecoderDecoder
30 |
31 | @Composable
32 | fun HomeScreen(
33 | modifier: Modifier = Modifier,
34 | navigateToDestination: (Destinations) -> Unit
35 | ) {
36 |
37 | val (state, _) = use(LocalHomeViewModel.current, HomeViewModel.State())
38 |
39 | Box(modifier = modifier) {
40 | LazyColumn(
41 | modifier = Modifier.fillMaxSize()
42 | .padding(horizontal = 16.dp)
43 | ) {
44 | itemsIndexed(state.screens) { index, screen ->
45 |
46 | ScreenCard(
47 | screen = screen,
48 | onClick = { navigateToDestination.invoke(it) },
49 | modifier = Modifier.padding(
50 | top = if (index == 0) 16.dp else 0.dp,
51 | bottom = 16.dp
52 | )
53 | )
54 | }
55 | }
56 | }
57 | }
58 |
59 | @Composable
60 | fun ScreenCard(
61 | screen: Screen,
62 | modifier: Modifier = Modifier,
63 | onClick: (Destinations) -> Unit
64 | ) {
65 | val context = LocalContext.current
66 |
67 | val painter = rememberImagePainter(
68 | data = screen.previewImage,
69 | imageLoader = ImageLoader.invoke(context).newBuilder()
70 | .componentRegistry {
71 | val decoder = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
72 | ImageDecoderDecoder(LocalContext.current)
73 | } else {
74 | GifDecoder()
75 | }
76 | add(decoder)
77 | }
78 | .build()
79 | )
80 |
81 | Row(
82 | modifier = modifier
83 | .clickable { onClick.invoke(screen.destinations) }
84 | .fillMaxWidth()
85 | .height(200.dp)
86 | .clip(RoundedCornerShape(4.dp))
87 | .background(Color.White)
88 | ) {
89 | Image(
90 | painter = painter,
91 | contentDescription = null,
92 | modifier = Modifier
93 | .weight(1f)
94 | .fillMaxHeight()
95 | .clipToBounds()
96 | .height(200.dp),
97 | contentScale = ContentScale.Crop
98 | )
99 |
100 | Column(
101 | modifier = Modifier.weight(2f)
102 | .padding(16.dp)
103 | ) {
104 | Text(
105 | text = screen.title,
106 | fontSize = 18.sp,
107 | fontWeight = FontWeight.Medium,
108 | modifier = Modifier
109 | .fillMaxWidth()
110 | )
111 |
112 | Text(
113 | text = screen.description,
114 | modifier = Modifier.fillMaxWidth()
115 | .padding(top = 8.dp)
116 | )
117 | }
118 | }
119 | }
--------------------------------------------------------------------------------
/app/src/main/java/in/surajsau/jisho/ui/home/HomeViewModel.kt:
--------------------------------------------------------------------------------
1 | package `in`.surajsau.jisho.ui.home
2 |
3 | import `in`.surajsau.jisho.base.SingleFlowViewModel
4 | import `in`.surajsau.jisho.data.ScreensDataProvider
5 | import `in`.surajsau.jisho.data.model.Screen
6 | import androidx.compose.runtime.Composable
7 | import androidx.compose.runtime.compositionLocalOf
8 | import androidx.lifecycle.ViewModel
9 | import androidx.lifecycle.viewModelScope
10 | import dagger.hilt.android.lifecycle.HiltViewModel
11 | import kotlinx.coroutines.flow.*
12 | import javax.inject.Inject
13 |
14 | @HiltViewModel
15 | class HomeViewModelImpl @Inject constructor(
16 | private val screensDataProvider: ScreensDataProvider,
17 | ) : ViewModel(), HomeViewModel {
18 |
19 | private val _screens = screensDataProvider.fetchScreen()
20 | .stateIn(viewModelScope, SharingStarted.Lazily, emptyList())
21 |
22 | override val state: StateFlow
23 | get() = _screens
24 | .map { HomeViewModel.State(screens = it) }
25 | .stateIn(viewModelScope, SharingStarted.Eagerly, HomeViewModel.State())
26 |
27 | override fun onEvent(event: HomeViewModel.Event) {}
28 |
29 | }
30 |
31 | interface HomeViewModel: SingleFlowViewModel {
32 |
33 | sealed class Event
34 |
35 | data class State(
36 | val screens: List = emptyList(),
37 | )
38 |
39 | }
40 |
41 | val LocalHomeViewModel = compositionLocalOf {
42 | error("HomeViewModel not provided")
43 | }
--------------------------------------------------------------------------------
/app/src/main/java/in/surajsau/jisho/ui/styletransfer/CameraScreen.kt:
--------------------------------------------------------------------------------
1 | package `in`.surajsau.jisho.ui.styletransfer
2 |
3 | import `in`.surajsau.jisho.ui.base.camera.Camera
4 | import `in`.surajsau.jisho.ui.base.camera.CameraAction
5 | import `in`.surajsau.jisho.ui.base.camera.CameraControls
6 | import androidx.compose.foundation.layout.Column
7 | import androidx.compose.foundation.layout.fillMaxWidth
8 | import androidx.compose.runtime.*
9 | import androidx.compose.ui.Modifier
10 |
11 | @Composable
12 | internal fun CameraScreen(
13 | modifier: Modifier = Modifier,
14 | onImageCaptured: (String) -> Unit,
15 | ) {
16 |
17 | var cameraAction by remember { mutableStateOf(CameraAction.None) }
18 |
19 | Column(modifier = modifier) {
20 |
21 | Camera(
22 | modifier = Modifier.fillMaxWidth()
23 | .weight(1f),
24 | cameraAction = cameraAction,
25 | onImageCaptured = onImageCaptured
26 | )
27 |
28 | CameraControls(
29 | modifier = Modifier.fillMaxWidth()
30 | .weight(1f),
31 | onCameraAction = { cameraAction = it }
32 | )
33 | }
34 | }
--------------------------------------------------------------------------------
/app/src/main/java/in/surajsau/jisho/ui/styletransfer/StylePreviewScreen.kt:
--------------------------------------------------------------------------------
1 | package `in`.surajsau.jisho.ui.styletransfer
2 |
3 | import `in`.surajsau.jisho.base.LocalBitmapCache
4 | import android.graphics.Bitmap
5 | import android.graphics.BitmapFactory
6 | import androidx.compose.foundation.Image
7 | import androidx.compose.foundation.background
8 | import androidx.compose.foundation.border
9 | import androidx.compose.foundation.clickable
10 | import androidx.compose.foundation.layout.*
11 | import androidx.compose.foundation.lazy.LazyRow
12 | import androidx.compose.foundation.lazy.itemsIndexed
13 | import androidx.compose.foundation.shape.RoundedCornerShape
14 | import androidx.compose.material.CircularProgressIndicator
15 | import androidx.compose.material.Text
16 | import androidx.compose.runtime.*
17 | import androidx.compose.ui.Alignment
18 | import androidx.compose.ui.Modifier
19 | import androidx.compose.ui.draw.clip
20 | import androidx.compose.ui.draw.clipToBounds
21 | import androidx.compose.ui.graphics.Color
22 | import androidx.compose.ui.graphics.asImageBitmap
23 | import androidx.compose.ui.layout.ContentScale
24 | import androidx.compose.ui.platform.LocalContext
25 | import androidx.compose.ui.unit.dp
26 | import kotlinx.coroutines.Dispatchers
27 | import kotlinx.coroutines.launch
28 |
29 | @Composable
30 | fun StylePreviewScreen(
31 | imagePreview: @Composable () -> Unit,
32 | previewGallery: @Composable () -> Unit,
33 | modifier: Modifier = Modifier,
34 | ) {
35 |
36 | Column(modifier = modifier) {
37 |
38 | Box(modifier = Modifier
39 | .fillMaxWidth()
40 | .weight(1f)
41 | ) {
42 | imagePreview()
43 | }
44 |
45 | Box(modifier = Modifier
46 | .fillMaxWidth()
47 | .weight(1f)
48 | ) {
49 | previewGallery()
50 | }
51 | }
52 |
53 | }
54 |
55 | @Composable
56 | fun ImagePreview(
57 | bitmap: Bitmap?,
58 | modifier: Modifier = Modifier,
59 | showLoader: Boolean = false,
60 | ) {
61 |
62 | Box(modifier = modifier) {
63 | if (bitmap != null) {
64 | Image(
65 | bitmap = bitmap.asImageBitmap(),
66 | contentDescription = null,
67 | modifier = Modifier.fillMaxSize()
68 | )
69 | }
70 |
71 | if (showLoader) {
72 | CircularProgressIndicator(modifier = Modifier.align(Alignment.Center))
73 | }
74 | }
75 | }
76 |
77 | @Composable
78 | fun PreviewGallery(
79 | previews: List,
80 | modifier: Modifier = Modifier,
81 | onStyleSelected: (String) -> Unit
82 | ) {
83 |
84 | Column(modifier = modifier) {
85 |
86 | Text(
87 | text = "Select a style to apply",
88 | color = Color.DarkGray,
89 | modifier = Modifier.align(Alignment.CenterHorizontally)
90 | .padding(vertical = 24.dp)
91 | )
92 |
93 | LazyRow(
94 | modifier = modifier
95 | .fillMaxWidth()
96 | .padding(16.dp)
97 | ) {
98 | itemsIndexed(previews) { index, fileName ->
99 | PreviewGalleryItem(
100 | fileName = fileName,
101 | onClick = { onStyleSelected.invoke(it) },
102 | modifier = Modifier
103 | .width(96.dp)
104 | .height(96.dp)
105 | .padding(
106 | start = if (index == 0) 16.dp else 0.dp,
107 | end = 16.dp
108 | )
109 | )
110 | }
111 | }
112 | }
113 | }
114 |
115 | @Composable
116 | fun PreviewGalleryItem(
117 | fileName: String,
118 | onClick: (String) -> Unit,
119 | modifier: Modifier = Modifier
120 | ) {
121 |
122 | val bitmapCache = LocalBitmapCache.current
123 |
124 | val context = LocalContext.current
125 |
126 | var previewBitmap by remember { mutableStateOf(null) }
127 |
128 | LaunchedEffect(fileName) {
129 | if (!bitmapCache.has(fileName)) {
130 | launch(Dispatchers.IO) {
131 | val bitmapStream = context.assets.open(fileName)
132 | val bitmap = BitmapFactory.decodeStream(bitmapStream)
133 |
134 | bitmapCache.save(fileName, bitmap)
135 | bitmapStream.close()
136 |
137 | previewBitmap = bitmap
138 | }
139 | } else {
140 | previewBitmap = bitmapCache.get(fileName)
141 | }
142 | }
143 |
144 | if (previewBitmap != null) {
145 | Image(
146 | bitmap = previewBitmap!!.asImageBitmap(),
147 | contentDescription = null,
148 | modifier = modifier
149 | .clip(RoundedCornerShape(4.dp))
150 | .clipToBounds()
151 | .clickable { onClick.invoke(fileName) },
152 | contentScale = ContentScale.Crop
153 | )
154 | }
155 | }
156 |
--------------------------------------------------------------------------------
/app/src/main/java/in/surajsau/jisho/ui/styletransfer/StyleTransferScreen.kt:
--------------------------------------------------------------------------------
1 | package `in`.surajsau.jisho.ui.styletransfer
2 |
3 | import `in`.surajsau.jisho.base.use
4 | import `in`.surajsau.jisho.ui.base.AskPermissionScreen
5 | import androidx.compose.foundation.background
6 | import androidx.compose.foundation.layout.*
7 | import androidx.compose.runtime.*
8 | import androidx.compose.ui.Modifier
9 | import androidx.compose.ui.graphics.Color
10 | import androidx.compose.ui.platform.LocalLifecycleOwner
11 | import androidx.lifecycle.Lifecycle
12 | import androidx.lifecycle.LifecycleEventObserver
13 |
14 | @Composable
15 | fun StyleTransferScreen(
16 | modifier: Modifier = Modifier,
17 | navigateBack: () -> Unit,
18 | navigateToSettings: () -> Unit,
19 | ) {
20 |
21 | val (state, event) = use(LocalStyleTransferViewModel.current, StyleTransferViewModel.State())
22 |
23 | val lifecycleOwner = LocalLifecycleOwner.current
24 |
25 | DisposableEffect(Unit) {
26 | val lifecycleObserver = LifecycleEventObserver { _, lifecycleEvent ->
27 | if (lifecycleEvent == Lifecycle.Event.ON_STOP) {
28 | event(StyleTransferViewModel.Event.OnStop)
29 | }
30 | }
31 | lifecycleOwner.lifecycle.addObserver(lifecycleObserver)
32 |
33 | onDispose {
34 | lifecycleOwner.lifecycle.removeObserver(lifecycleObserver)
35 | }
36 | }
37 |
38 | Box(modifier = modifier
39 | .fillMaxSize()
40 | .background(Color.White)
41 | ) {
42 |
43 | AskPermissionScreen(
44 | modifier = Modifier.fillMaxSize(),
45 | permission = android.Manifest.permission.CAMERA,
46 | onDismiss = { navigateBack.invoke() },
47 | onPermissionDeniedFallback = { navigateToSettings.invoke() },
48 | ) {
49 |
50 | when (val screenMode = state.mode) {
51 | is StyleTransferViewModel.ScreenMode.Camera -> {
52 | CameraScreen(modifier = Modifier.fillMaxSize(),
53 | onImageCaptured = { fileName ->
54 | event(StyleTransferViewModel.Event.CameraResultReceived(fileName))
55 | }
56 | )
57 | }
58 |
59 | is StyleTransferViewModel.ScreenMode.StylePreview -> {
60 | StylePreviewScreen(
61 | modifier = Modifier.fillMaxSize(),
62 | imagePreview = {
63 | ImagePreview(
64 | bitmap = screenMode.image,
65 | showLoader = screenMode.showLoading,
66 | modifier = Modifier.fillMaxSize()
67 | )
68 | },
69 | previewGallery = {
70 | PreviewGallery(
71 | previews = screenMode.stylePreviews,
72 | onStyleSelected = { fileName ->
73 | event(StyleTransferViewModel.Event.StyleSelected(fileName))
74 | },
75 | modifier = Modifier.fillMaxSize()
76 | )
77 | }
78 | )
79 | }
80 | }
81 |
82 | }
83 | }
84 |
85 | }
--------------------------------------------------------------------------------
/app/src/main/java/in/surajsau/jisho/ui/styletransfer/StyleTransferViewModel.kt:
--------------------------------------------------------------------------------
1 | package `in`.surajsau.jisho.ui.styletransfer
2 |
3 | import `in`.surajsau.jisho.base.FileName
4 | import `in`.surajsau.jisho.base.SingleFlowViewModel
5 | import `in`.surajsau.jisho.data.FileProvider
6 | import `in`.surajsau.jisho.data.StyleTransferProvider
7 | import android.graphics.Bitmap
8 | import androidx.compose.runtime.compositionLocalOf
9 | import androidx.lifecycle.ViewModel
10 | import androidx.lifecycle.viewModelScope
11 | import dagger.hilt.android.lifecycle.HiltViewModel
12 | import kotlinx.coroutines.flow.*
13 | import kotlinx.coroutines.launch
14 | import javax.inject.Inject
15 |
16 | @HiltViewModel
17 | class StyleTransferViewModelImpl @Inject constructor(
18 | private val styleTransferProvider: StyleTransferProvider,
19 | private val fileProvider: FileProvider,
20 | ) : ViewModel(), StyleTransferViewModel {
21 |
22 | private val _cameraImageFileName = MutableStateFlow(null)
23 | private val _styleImageFileName = MutableStateFlow(null)
24 |
25 | private val _isImageCaptured = MutableStateFlow(false)
26 |
27 | private val _cameraImage = _cameraImageFileName.filterNotNull().map { fileProvider.fetchCachedBitmap(it.value) }
28 | private val _styleImage = _styleImageFileName.filterNotNull().flatMapLatest { fileProvider.fetchAssetBitmap(it.value) }
29 |
30 | private val _styleTransferState = combine(
31 | _cameraImage,
32 | _styleImage
33 | ) { cameraIamge, styleImage -> Pair(cameraIamge, styleImage) }
34 | .flatMapLatest { (targetImage, styleImage) -> styleTransferProvider.process(targetImage, styleImage) }
35 | .stateIn(viewModelScope, SharingStarted.Lazily, StyleTransferProvider.StyleTransferState.Idle)
36 |
37 | override val state: StateFlow
38 | get() = combine(
39 | _styleTransferState,
40 | _isImageCaptured
41 | ){ styleTransferState, isImagedCaptured ->
42 | val mode = if (!isImagedCaptured) {
43 | StyleTransferViewModel.ScreenMode.Camera
44 | } else {
45 | StyleTransferViewModel.ScreenMode.StylePreview(
46 | showLoading = styleTransferState !is StyleTransferProvider.StyleTransferState.Finished,
47 | image = (styleTransferState as? StyleTransferProvider.StyleTransferState.Finished)?.image,
48 | stylePreviews = StyleTransferProvider.Styles
49 | )
50 | }
51 |
52 | StyleTransferViewModel.State(mode = mode)
53 | }
54 | .stateIn(viewModelScope, SharingStarted.Eagerly, StyleTransferViewModel.State())
55 |
56 | override fun onEvent(event: StyleTransferViewModel.Event) {
57 | viewModelScope.launch {
58 | when (event) {
59 | is StyleTransferViewModel.Event.CameraResultReceived -> {
60 | _isImageCaptured.value = true
61 | _cameraImageFileName.value = FileName(event.fileName)
62 | }
63 |
64 | is StyleTransferViewModel.Event.StyleSelected -> { _styleImageFileName.value = FileName(event.fileName) }
65 |
66 | is StyleTransferViewModel.Event.OnStop -> {}
67 | }
68 | }
69 | }
70 |
71 | }
72 |
73 | interface StyleTransferViewModel: SingleFlowViewModel {
74 |
75 | sealed class ScreenMode {
76 | object Camera: ScreenMode()
77 |
78 | data class StylePreview(
79 | val showLoading: Boolean,
80 | val image: Bitmap?,
81 | val stylePreviews: List
82 | ): ScreenMode()
83 | }
84 |
85 | sealed class Event {
86 | data class CameraResultReceived(val fileName: String): Event()
87 | data class StyleSelected(val fileName: String): Event()
88 |
89 | object OnStop: Event()
90 | }
91 |
92 | data class State(
93 | val mode: ScreenMode = ScreenMode.Camera
94 | )
95 | }
96 |
97 | val LocalStyleTransferViewModel = compositionLocalOf {
98 | error("StyleTransferViewModel not provided")
99 | }
--------------------------------------------------------------------------------
/app/src/main/java/in/surajsau/jisho/ui/theme/Color.kt:
--------------------------------------------------------------------------------
1 | package `in`.surajsau.jisho.ui.theme
2 |
3 | import androidx.compose.ui.graphics.Color
4 |
5 | val Purple200 = Color(0xFFBB86FC)
6 | val Purple500 = Color(0xFF6200EE)
7 | val Purple700 = Color(0xFF3700B3)
8 | val Teal200 = Color(0xFF03DAC5)
9 |
10 | object DigitalInkColors {
11 | val PredictionDivider = Color(206, 206, 206)
12 | val PredictionBackground = Color(233, 233, 233)
13 | val PredictionText = Color(77, 77, 77)
14 | val PredictionPlaceholder = Color(186, 186, 186)
15 |
16 | val Text = Color(71, 71, 71)
17 | val Stroke = Color(39, 39, 39)
18 | }
--------------------------------------------------------------------------------
/app/src/main/java/in/surajsau/jisho/ui/theme/Shape.kt:
--------------------------------------------------------------------------------
1 | package `in`.surajsau.jisho.ui.theme
2 |
3 | import androidx.compose.foundation.shape.RoundedCornerShape
4 | import androidx.compose.material.Shapes
5 | import androidx.compose.ui.unit.dp
6 |
7 | val Shapes = Shapes(
8 | small = RoundedCornerShape(4.dp),
9 | medium = RoundedCornerShape(4.dp),
10 | large = RoundedCornerShape(0.dp)
11 | )
--------------------------------------------------------------------------------
/app/src/main/java/in/surajsau/jisho/ui/theme/Theme.kt:
--------------------------------------------------------------------------------
1 | package `in`.surajsau.jisho.ui.theme
2 |
3 | import androidx.compose.foundation.isSystemInDarkTheme
4 | import androidx.compose.material.MaterialTheme
5 | import androidx.compose.material.darkColors
6 | import androidx.compose.material.lightColors
7 | import androidx.compose.runtime.Composable
8 | import androidx.compose.ui.graphics.Color
9 |
10 | private val DarkColorPalette = darkColors(
11 | primary = Purple200,
12 | primaryVariant = Purple700,
13 | secondary = Teal200
14 | )
15 |
16 | private val LightColorPalette = lightColors(
17 | primary = Purple500,
18 | primaryVariant = Purple700,
19 | secondary = Teal200,
20 | background = Color(238, 238, 238),
21 | surface = Color(238, 238, 238),
22 |
23 | /* Other default colors to override
24 | background = Color.White,
25 | surface = Color.White,
26 | onPrimary = Color.White,
27 | onSecondary = Color.Black,
28 | onBackground = Color.Black,
29 | onSurface = Color.Black,
30 | */
31 | )
32 |
33 | @Composable
34 | fun JishoTheme(darkTheme: Boolean = isSystemInDarkTheme(), content: @Composable () -> Unit) {
35 | val colors = if (darkTheme) {
36 | DarkColorPalette
37 | } else {
38 | LightColorPalette
39 | }
40 |
41 | MaterialTheme(
42 | colors = colors,
43 | typography = Typography,
44 | shapes = Shapes,
45 | content = content
46 | )
47 | }
--------------------------------------------------------------------------------
/app/src/main/java/in/surajsau/jisho/ui/theme/Type.kt:
--------------------------------------------------------------------------------
1 | package `in`.surajsau.jisho.ui.theme
2 |
3 | import androidx.compose.material.Typography
4 | import androidx.compose.ui.text.TextStyle
5 | import androidx.compose.ui.text.font.FontFamily
6 | import androidx.compose.ui.text.font.FontWeight
7 | import androidx.compose.ui.unit.sp
8 |
9 | // Set of Material typography styles to start with
10 | val Typography = Typography(
11 | body1 = TextStyle(
12 | fontFamily = FontFamily.Default,
13 | fontWeight = FontWeight.Normal,
14 | fontSize = 16.sp
15 | )
16 | /* Other default text styles to override
17 | button = TextStyle(
18 | fontFamily = FontFamily.Default,
19 | fontWeight = FontWeight.W500,
20 | fontSize = 14.sp
21 | ),
22 | caption = TextStyle(
23 | fontFamily = FontFamily.Default,
24 | fontWeight = FontWeight.Normal,
25 | fontSize = 12.sp
26 | )
27 | */
28 | )
--------------------------------------------------------------------------------
/app/src/main/ml/facenet.tflite:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/surajsau/ML-Android/66ca1373067c57257e7d26f9b922d2d0915b0f4a/app/src/main/ml/facenet.tflite
--------------------------------------------------------------------------------
/app/src/main/ml/magenta_style_predictor.tflite:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/surajsau/ML-Android/66ca1373067c57257e7d26f9b922d2d0915b0f4a/app/src/main/ml/magenta_style_predictor.tflite
--------------------------------------------------------------------------------
/app/src/main/ml/magenta_style_transfer.tflite:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/surajsau/ML-Android/66ca1373067c57257e7d26f9b922d2d0915b0f4a/app/src/main/ml/magenta_style_transfer.tflite
--------------------------------------------------------------------------------
/app/src/main/ml/style_predictor_model.tflite:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/surajsau/ML-Android/66ca1373067c57257e7d26f9b922d2d0915b0f4a/app/src/main/ml/style_predictor_model.tflite
--------------------------------------------------------------------------------
/app/src/main/ml/style_transfer_model.tflite:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/surajsau/ML-Android/66ca1373067c57257e7d26f9b922d2d0915b0f4a/app/src/main/ml/style_transfer_model.tflite
--------------------------------------------------------------------------------
/app/src/main/res/drawable-v24/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
15 |
18 |
21 |
22 |
23 |
24 |
30 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
10 |
15 |
20 |
25 |
30 |
35 |
40 |
45 |
50 |
55 |
60 |
65 |
70 |
75 |
80 |
85 |
90 |
95 |
100 |
105 |
110 |
115 |
120 |
125 |
130 |
135 |
140 |
145 |
150 |
155 |
160 |
165 |
170 |
171 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_switch_camera.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/surajsau/ML-Android/66ca1373067c57257e7d26f9b922d2d0915b0f4a/app/src/main/res/drawable/ic_switch_camera.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/surajsau/ML-Android/66ca1373067c57257e7d26f9b922d2d0915b0f4a/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/surajsau/ML-Android/66ca1373067c57257e7d26f9b922d2d0915b0f4a/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/surajsau/ML-Android/66ca1373067c57257e7d26f9b922d2d0915b0f4a/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/surajsau/ML-Android/66ca1373067c57257e7d26f9b922d2d0915b0f4a/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/surajsau/ML-Android/66ca1373067c57257e7d26f9b922d2d0915b0f4a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #FFBB86FC
4 | #FF6200EE
5 | #FF3700B3
6 | #FF03DAC5
7 | #FF018786
8 | #FF000000
9 | #FFFFFFFF
10 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | ML
3 |
--------------------------------------------------------------------------------
/app/src/main/res/values/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/backup_rules.xml:
--------------------------------------------------------------------------------
1 |
8 |
9 |
13 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/data_extraction_rules.xml:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
12 |
13 |
19 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/file_paths.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/test/java/in/surajsau/jisho/ExampleUnitTest.kt:
--------------------------------------------------------------------------------
1 | package `in`.surajsau.jisho
2 |
3 | import org.junit.Test
4 |
5 | import org.junit.Assert.*
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * See [testing documentation](http://d.android.com/tools/testing).
11 | */
12 | class ExampleUnitTest {
13 | @Test
14 | fun addition_isCorrect() {
15 | assertEquals(4, 2 + 2)
16 | }
17 | }
--------------------------------------------------------------------------------
/build.gradle.kts:
--------------------------------------------------------------------------------
1 | buildscript {
2 | repositories {
3 | google()
4 | mavenCentral()
5 | }
6 | dependencies {
7 | classpath("com.android.tools.build:gradle:7.0.3")
8 | classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.5.31")
9 | classpath("com.google.dagger:hilt-android-gradle-plugin:2.38.1")
10 | // NOTE: Do not place your application dependencies here; they belong
11 | // in the individual module build.gradle files
12 | }
13 | }
--------------------------------------------------------------------------------
/buildSrc/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | `kotlin-dsl`
3 | }
4 |
5 | repositories {
6 | jcenter()
7 | }
8 |
--------------------------------------------------------------------------------
/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=-Xmx2048m -Dfile.encoding=UTF-8
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 | # AndroidX package structure to make it clearer which packages are bundled with the
15 | # Android operating system, and which are packaged with your app"s APK
16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn
17 | android.useAndroidX=true
18 | # Kotlin code style for this project: "official" or "obsolete":
19 | kotlin.code.style=official
20 | # Enables namespacing of each library's R class so that its R class includes only the
21 | # resources declared in the library itself and none from the library's dependencies,
22 | # thereby reducing the size of the R class for that library
23 | android.nonTransitiveRClass=true
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/surajsau/ML-Android/66ca1373067c57257e7d26f9b922d2d0915b0f4a/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Mon Nov 01 13:02:41 IST 2021
2 | distributionBase=GRADLE_USER_HOME
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.3-rc-1-bin.zip
4 | distributionPath=wrapper/dists
5 | zipStorePath=wrapper/dists
6 | zipStoreBase=GRADLE_USER_HOME
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | #
4 | # Copyright 2015 the original author or authors.
5 | #
6 | # Licensed under the Apache License, Version 2.0 (the "License");
7 | # you may not use this file except in compliance with the License.
8 | # You may obtain a copy of the License at
9 | #
10 | # https://www.apache.org/licenses/LICENSE-2.0
11 | #
12 | # Unless required by applicable law or agreed to in writing, software
13 | # distributed under the License is distributed on an "AS IS" BASIS,
14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | # See the License for the specific language governing permissions and
16 | # limitations under the License.
17 | #
18 |
19 | ##############################################################################
20 | ##
21 | ## Gradle start up script for UN*X
22 | ##
23 | ##############################################################################
24 |
25 | # Attempt to set APP_HOME
26 | # Resolve links: $0 may be a link
27 | PRG="$0"
28 | # Need this for relative symlinks.
29 | while [ -h "$PRG" ] ; do
30 | ls=`ls -ld "$PRG"`
31 | link=`expr "$ls" : '.*-> \(.*\)$'`
32 | if expr "$link" : '/.*' > /dev/null; then
33 | PRG="$link"
34 | else
35 | PRG=`dirname "$PRG"`"/$link"
36 | fi
37 | done
38 | SAVED="`pwd`"
39 | cd "`dirname \"$PRG\"`/" >/dev/null
40 | APP_HOME="`pwd -P`"
41 | cd "$SAVED" >/dev/null
42 |
43 | APP_NAME="Gradle"
44 | APP_BASE_NAME=`basename "$0"`
45 |
46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
48 |
49 | # Use the maximum available, or set MAX_FD != -1 to use that value.
50 | MAX_FD="maximum"
51 |
52 | warn () {
53 | echo "$*"
54 | }
55 |
56 | die () {
57 | echo
58 | echo "$*"
59 | echo
60 | exit 1
61 | }
62 |
63 | # OS specific support (must be 'true' or 'false').
64 | cygwin=false
65 | msys=false
66 | darwin=false
67 | nonstop=false
68 | case "`uname`" in
69 | CYGWIN* )
70 | cygwin=true
71 | ;;
72 | Darwin* )
73 | darwin=true
74 | ;;
75 | MINGW* )
76 | msys=true
77 | ;;
78 | NONSTOP* )
79 | nonstop=true
80 | ;;
81 | esac
82 |
83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
84 |
85 |
86 | # Determine the Java command to use to start the JVM.
87 | if [ -n "$JAVA_HOME" ] ; then
88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
89 | # IBM's JDK on AIX uses strange locations for the executables
90 | JAVACMD="$JAVA_HOME/jre/sh/java"
91 | else
92 | JAVACMD="$JAVA_HOME/bin/java"
93 | fi
94 | if [ ! -x "$JAVACMD" ] ; then
95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
96 |
97 | Please set the JAVA_HOME variable in your environment to match the
98 | location of your Java installation."
99 | fi
100 | else
101 | JAVACMD="java"
102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
103 |
104 | Please set the JAVA_HOME variable in your environment to match the
105 | location of your Java installation."
106 | fi
107 |
108 | # Increase the maximum file descriptors if we can.
109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
110 | MAX_FD_LIMIT=`ulimit -H -n`
111 | if [ $? -eq 0 ] ; then
112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
113 | MAX_FD="$MAX_FD_LIMIT"
114 | fi
115 | ulimit -n $MAX_FD
116 | if [ $? -ne 0 ] ; then
117 | warn "Could not set maximum file descriptor limit: $MAX_FD"
118 | fi
119 | else
120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
121 | fi
122 | fi
123 |
124 | # For Darwin, add options to specify how the application appears in the dock
125 | if $darwin; then
126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
127 | fi
128 |
129 | # For Cygwin or MSYS, switch paths to Windows format before running java
130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
133 |
134 | JAVACMD=`cygpath --unix "$JAVACMD"`
135 |
136 | # We build the pattern for arguments to be converted via cygpath
137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
138 | SEP=""
139 | for dir in $ROOTDIRSRAW ; do
140 | ROOTDIRS="$ROOTDIRS$SEP$dir"
141 | SEP="|"
142 | done
143 | OURCYGPATTERN="(^($ROOTDIRS))"
144 | # Add a user-defined pattern to the cygpath arguments
145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
147 | fi
148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
149 | i=0
150 | for arg in "$@" ; do
151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
153 |
154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
156 | else
157 | eval `echo args$i`="\"$arg\""
158 | fi
159 | i=`expr $i + 1`
160 | done
161 | case $i in
162 | 0) set -- ;;
163 | 1) set -- "$args0" ;;
164 | 2) set -- "$args0" "$args1" ;;
165 | 3) set -- "$args0" "$args1" "$args2" ;;
166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;;
167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
172 | esac
173 | fi
174 |
175 | # Escape application args
176 | save () {
177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
178 | echo " "
179 | }
180 | APP_ARGS=`save "$@"`
181 |
182 | # Collect all arguments for the java command, following the shell quoting and substitution rules
183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
184 |
185 | exec "$JAVACMD" "$@"
186 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @rem
2 | @rem Copyright 2015 the original author or authors.
3 | @rem
4 | @rem Licensed under the Apache License, Version 2.0 (the "License");
5 | @rem you may not use this file except in compliance with the License.
6 | @rem You may obtain a copy of the License at
7 | @rem
8 | @rem https://www.apache.org/licenses/LICENSE-2.0
9 | @rem
10 | @rem Unless required by applicable law or agreed to in writing, software
11 | @rem distributed under the License is distributed on an "AS IS" BASIS,
12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | @rem See the License for the specific language governing permissions and
14 | @rem limitations under the License.
15 | @rem
16 |
17 | @if "%DEBUG%" == "" @echo off
18 | @rem ##########################################################################
19 | @rem
20 | @rem Gradle startup script for Windows
21 | @rem
22 | @rem ##########################################################################
23 |
24 | @rem Set local scope for the variables with windows NT shell
25 | if "%OS%"=="Windows_NT" setlocal
26 |
27 | set DIRNAME=%~dp0
28 | if "%DIRNAME%" == "" set DIRNAME=.
29 | set APP_BASE_NAME=%~n0
30 | set APP_HOME=%DIRNAME%
31 |
32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter.
33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
34 |
35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
37 |
38 | @rem Find java.exe
39 | if defined JAVA_HOME goto findJavaFromJavaHome
40 |
41 | set JAVA_EXE=java.exe
42 | %JAVA_EXE% -version >NUL 2>&1
43 | if "%ERRORLEVEL%" == "0" goto execute
44 |
45 | echo.
46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
47 | echo.
48 | echo Please set the JAVA_HOME variable in your environment to match the
49 | echo location of your Java installation.
50 |
51 | goto fail
52 |
53 | :findJavaFromJavaHome
54 | set JAVA_HOME=%JAVA_HOME:"=%
55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
56 |
57 | if exist "%JAVA_EXE%" goto execute
58 |
59 | echo.
60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
61 | echo.
62 | echo Please set the JAVA_HOME variable in your environment to match the
63 | echo location of your Java installation.
64 |
65 | goto fail
66 |
67 | :execute
68 | @rem Setup the command line
69 |
70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
71 |
72 |
73 | @rem Execute Gradle
74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
75 |
76 | :end
77 | @rem End local scope for the variables with windows NT shell
78 | if "%ERRORLEVEL%"=="0" goto mainEnd
79 |
80 | :fail
81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
82 | rem the _cmd.exe /c_ return code!
83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
84 | exit /b 1
85 |
86 | :mainEnd
87 | if "%OS%"=="Windows_NT" endlocal
88 |
89 | :omega
90 |
--------------------------------------------------------------------------------
/screenshots/card_reader.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/surajsau/ML-Android/66ca1373067c57257e7d26f9b922d2d0915b0f4a/screenshots/card_reader.gif
--------------------------------------------------------------------------------
/screenshots/face.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/surajsau/ML-Android/66ca1373067c57257e7d26f9b922d2d0915b0f4a/screenshots/face.gif
--------------------------------------------------------------------------------
/screenshots/gpt2.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/surajsau/ML-Android/66ca1373067c57257e7d26f9b922d2d0915b0f4a/screenshots/gpt2.gif
--------------------------------------------------------------------------------
/screenshots/translate_app.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/surajsau/ML-Android/66ca1373067c57257e7d26f9b922d2d0915b0f4a/screenshots/translate_app.gif
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | pluginManagement {
2 | repositories {
3 | gradlePluginPortal()
4 | google()
5 | mavenCentral()
6 | }
7 | }
8 | dependencyResolutionManagement {
9 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
10 | repositories {
11 | google()
12 | mavenCentral()
13 | }
14 | }
15 | rootProject.name = "Jisho"
16 | include ':app'
17 |
--------------------------------------------------------------------------------