{
"repository_url": "https://github.com/ronelsolomon/java-project.git",
"owner": "ronelsolomon",
"name": "java-project.git",
"extracted_at": "2026-03-02T22:50:30.435875",
"files": {
"gradlew": {
"content": "#!/bin/sh\n\n#\n# Copyright © 2015-2021 the original authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n# https://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n#\n# SPDX-License-Identifier: Apache-2.0\n#\n\n##############################################################################\n#\n# Gradle start up script for POSIX generated by Gradle.\n#\n# Important for running:\n#\n# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is\n# noncompliant, but you have some other compliant shell such as ksh or\n# bash, then to run this script, type that shell name before the whole\n# command line, like:\n#\n# ksh Gradle\n#\n# Busybox and similar reduced shells will NOT work, because this script\n# requires all of these POSIX shell features:\n# * functions;\n# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,\n# «${var#prefix}», «${var%suffix}», and «$( cmd )»;\n# * compound commands having a testable exit status, especially «case»;\n# * various built-in commands including «command», «set», and «ulimit».\n#\n# Important for patching:\n#\n# (2) This script targets any POSIX shell, so it avoids extensions provided\n# by Bash, Ksh, etc; in particular arrays are avoided.\n#\n# The \"traditional\" practice of packing multiple parameters into a\n# space-separated string is a well documented source of bugs and security\n# problems, so this is (mostly) avoided, by progressively accumulating\n# options in \"$@\", and eventually passing that to Java.\n#\n# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,\n# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;\n# see the in-line comments for details.\n#\n# There are tweaks for specific operating systems such as AIX, CygWin,\n# Darwin, MinGW, and NonStop.\n#\n# (3) This script is generated from the Groovy template\n# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt\n# within the Gradle project.\n#\n# You can find Gradle at https://github.com/gradle/gradle/.\n#\n##############################################################################\n\n# Attempt to set APP_HOME\n\n# Resolve links: $0 may be a link\napp_path=$0\n\n# Need this for daisy-chained symlinks.\nwhile\n APP_HOME=${app_path%\"${app_path##*/}\"} # leaves a trailing /; empty if no leading path\n [ -h \"$app_path\" ]\ndo\n ls=$( ls -ld \"$app_path\" )\n link=${ls#*' -> '}\n case $link in #(\n /*) app_path=$link ;; #(\n *) app_path=$APP_HOME$link ;;\n esac\ndone\n\n# This is normally unused\n# shellcheck disable=SC2034\nAPP_BASE_NAME=${0##*/}\n# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)\nAPP_HOME=$( cd -P \"${APP_HOME:-./}\" > /dev/null && printf '%s\\n' \"$PWD\" ) || exit\n\n# Use the maximum available, or set MAX_FD != -1 to use that value.\nMAX_FD=maximum\n\nwarn () {\n echo \"$*\"\n} >&2\n\ndie () {\n echo\n echo \"$*\"\n echo\n exit 1\n} >&2\n\n# OS specific support (must be 'true' or 'false').\ncygwin=false\nmsys=false\ndarwin=false\nnonstop=false\ncase \"$( uname )\" in #(\n CYGWIN* ) cygwin=true ;; #(\n Darwin* ) darwin=true ;; #(\n MSYS* | MINGW* ) msys=true ;; #(\n NONSTOP* ) nonstop=true ;;\nesac\n\nCLASSPATH=\"\\\\\\\"\\\\\\\"\"\n\n\n# Determine the Java command to use to start the JVM.\nif [ -n \"$JAVA_HOME\" ] ; then\n if [ -x \"$JAVA_HOME/jre/sh/java\" ] ; then\n # IBM's JDK on AIX uses strange locations for the executables\n JAVACMD=$JAVA_HOME/jre/sh/java\n else\n JAVACMD=$JAVA_HOME/bin/java\n fi\n if [ ! -x \"$JAVACMD\" ] ; then\n die \"ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\n fi\nelse\n JAVACMD=java\n if ! command -v java >/dev/null 2>&1\n then\n die \"ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\n fi\nfi\n\n# Increase the maximum file descriptors if we can.\nif ! \"$cygwin\" && ! \"$darwin\" && ! \"$nonstop\" ; then\n case $MAX_FD in #(\n max*)\n # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.\n # shellcheck disable=SC2039,SC3045\n MAX_FD=$( ulimit -H -n ) ||\n warn \"Could not query maximum file descriptor limit\"\n esac\n case $MAX_FD in #(\n '' | soft) :;; #(\n *)\n # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.\n # shellcheck disable=SC2039,SC3045\n ulimit -n \"$MAX_FD\" ||\n warn \"Could not set maximum file descriptor limit to $MAX_FD\"\n esac\nfi\n\n# Collect all arguments for the java command, stacking in reverse order:\n# * args from the command line\n# * the main class name\n# * -classpath\n# * -D...appname settings\n# * --module-path (only if needed)\n# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.\n\n# For Cygwin or MSYS, switch paths to Windows format before running java\nif \"$cygwin\" || \"$msys\" ; then\n APP_HOME=$( cygpath --path --mixed \"$APP_HOME\" )\n CLASSPATH=$( cygpath --path --mixed \"$CLASSPATH\" )\n\n JAVACMD=$( cygpath --unix \"$JAVACMD\" )\n\n # Now convert the arguments - kludge to limit ourselves to /bin/sh\n for arg do\n if\n case $arg in #(\n -*) false ;; # don't mess with options #(\n /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath\n [ -e \"$t\" ] ;; #(\n *) false ;;\n esac\n then\n arg=$( cygpath --path --ignore --mixed \"$arg\" )\n fi\n # Roll the args list around exactly as many times as the number of\n # args, so each arg winds up back in the position where it started, but\n # possibly modified.\n #\n # NB: a `for` loop captures its iteration list before it begins, so\n # changing the positional parameters here affects neither the number of\n # iterations, nor the values presented in `arg`.\n shift # remove old arg\n set -- \"$@\" \"$arg\" # push replacement arg\n done\nfi\n\n\n# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\nDEFAULT_JVM_OPTS='\"-Xmx64m\" \"-Xms64m\"'\n\n# Collect all arguments for the java command:\n# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,\n# and any embedded shellness will be escaped.\n# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be\n# treated as '${Hostname}' itself on the command line.\n\nset -- \\\n \"-Dorg.gradle.appname=$APP_BASE_NAME\" \\\n -classpath \"$CLASSPATH\" \\\n -jar \"$APP_HOME/gradle/wrapper/gradle-wrapper.jar\" \\\n \"$@\"\n\n# Stop when \"xargs\" is not available.\nif ! command -v xargs >/dev/null 2>&1\nthen\n die \"xargs is not available\"\nfi\n\n# Use \"xargs\" to parse quoted args.\n#\n# With -n1 it outputs one arg per line, with the quotes and backslashes removed.\n#\n# In Bash we could simply go:\n#\n# readarray ARGS < <( xargs -n1 <<<\"$var\" ) &&\n# set -- \"${ARGS[@]}\" \"$@\"\n#\n# but POSIX shell has neither arrays nor command substitution, so instead we\n# post-process each arg (as a line of input to sed) to backslash-escape any\n# character that might be a shell metacharacter, then use eval to reverse\n# that process (while maintaining the separation between arguments), and wrap\n# the whole thing up as a single \"set\" statement.\n#\n# This will of course break if any of these variables contains a newline or\n# an unmatched quote.\n#\n\neval \"set -- $(\n printf '%s\\n' \"$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS\" |\n xargs -n1 |\n sed ' s~[^-[:alnum:]+,./:=@_]~\\\\&~g; ' |\n tr '\\n' ' '\n )\" '\"$@\"'\n\nexec \"$JAVACMD\" \"$@\"\n",
"size": 8710,
"language": "unknown"
},
"build.gradle": {
"content": "// Top-level build file\nbuildscript {\n ext {\n kotlin_version = '1.9.0'\n nav_version = '2.7.7'\n hilt_version = '2.51.1'\n }\n repositories {\n google()\n mavenCentral()\n }\n dependencies {\n classpath 'com.android.tools.build:gradle:8.2.2'\n classpath \"org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version\"\n classpath \"com.google.dagger:hilt-android-gradle-plugin:$hilt_version\"\n classpath \"androidx.navigation:navigation-safe-args-gradle-plugin:$nav_version\"\n }\n}\n\nallprojects {\n repositories {\n google()\n mavenCentral()\n maven { url 'https://jitpack.io' }\n }\n}\n\ntask clean(type: Delete) {\n delete rootProject.buildDir\n}",
"size": 729,
"language": "unknown"
},
".gitattributes": {
"content": "# Auto detect text files and perform LF normalization\n* text=auto\n",
"size": 66,
"language": "unknown"
},
"activity_main.xml": {
"content": "\n\n\n \n\n \n\n \n\n \n\n \n\n",
"size": 2062,
"language": "xml"
},
"gradlew.bat": {
"content": "@rem\n@rem Copyright 2015 the original author or authors.\n@rem\n@rem Licensed under the Apache License, Version 2.0 (the \"License\");\n@rem you may not use this file except in compliance with the License.\n@rem You may obtain a copy of the License at\n@rem\n@rem https://www.apache.org/licenses/LICENSE-2.0\n@rem\n@rem Unless required by applicable law or agreed to in writing, software\n@rem distributed under the License is distributed on an \"AS IS\" BASIS,\n@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n@rem See the License for the specific language governing permissions and\n@rem limitations under the License.\n@rem\n@rem SPDX-License-Identifier: Apache-2.0\n@rem\n\n@if \"%DEBUG%\"==\"\" @echo off\n@rem ##########################################################################\n@rem\n@rem Gradle startup script for Windows\n@rem\n@rem ##########################################################################\n\n@rem Set local scope for the variables with windows NT shell\nif \"%OS%\"==\"Windows_NT\" setlocal\n\nset DIRNAME=%~dp0\nif \"%DIRNAME%\"==\"\" set DIRNAME=.\n@rem This is normally unused\nset APP_BASE_NAME=%~n0\nset APP_HOME=%DIRNAME%\n\n@rem Resolve any \".\" and \"..\" in APP_HOME to make it shorter.\nfor %%i in (\"%APP_HOME%\") do set APP_HOME=%%~fi\n\n@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\nset DEFAULT_JVM_OPTS=\"-Xmx64m\" \"-Xms64m\"\n\n@rem Find java.exe\nif defined JAVA_HOME goto findJavaFromJavaHome\n\nset JAVA_EXE=java.exe\n%JAVA_EXE% -version >NUL 2>&1\nif %ERRORLEVEL% equ 0 goto execute\n\necho. 1>&2\necho ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2\necho. 1>&2\necho Please set the JAVA_HOME variable in your environment to match the 1>&2\necho location of your Java installation. 1>&2\n\ngoto fail\n\n:findJavaFromJavaHome\nset JAVA_HOME=%JAVA_HOME:\"=%\nset JAVA_EXE=%JAVA_HOME%/bin/java.exe\n\nif exist \"%JAVA_EXE%\" goto execute\n\necho. 1>&2\necho ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2\necho. 1>&2\necho Please set the JAVA_HOME variable in your environment to match the 1>&2\necho location of your Java installation. 1>&2\n\ngoto fail\n\n:execute\n@rem Setup the command line\n\nset CLASSPATH=\n\n\n@rem Execute Gradle\n\"%JAVA_EXE%\" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% \"-Dorg.gradle.appname=%APP_BASE_NAME%\" -classpath \"%CLASSPATH%\" -jar \"%APP_HOME%\\gradle\\wrapper\\gradle-wrapper.jar\" %*\n\n:end\n@rem End local scope for the variables with windows NT shell\nif %ERRORLEVEL% equ 0 goto mainEnd\n\n:fail\nrem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of\nrem the _cmd.exe /c_ return code!\nset EXIT_CODE=%ERRORLEVEL%\nif %EXIT_CODE% equ 0 set EXIT_CODE=1\nif not \"\"==\"%GRADLE_EXIT_CONSOLE%\" exit %EXIT_CODE%\nexit /b %EXIT_CODE%\n\n:mainEnd\nif \"%OS%\"==\"Windows_NT\" endlocal\n\n:omega\n",
"size": 2843,
"language": "unknown"
},
"app/build.gradle": {
"content": "plugins {\n id 'com.android.application'\n id 'kotlin-android'\n id 'kotlin-kapt'\n id 'dagger.hilt.android.plugin'\n id 'androidx.navigation.safeargs.kotlin'\n}\n\nandroid {\n namespace 'com.example.emotiondetector'\n compileSdk 34\n\n defaultConfig {\n applicationId \"com.example.emotiondetector\"\n minSdk 24\n targetSdk 34\n versionCode 1\n versionName \"1.0\"\n\n testInstrumentationRunner \"androidx.test.runner.AndroidJUnitRunner\"\n }\n\n buildTypes {\n release {\n minifyEnabled true\n shrinkResources true\n proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'\n }\n debug {\n applicationIdSuffix \".debug\"\n debuggable true\n }\n }\n\n buildFeatures {\n viewBinding true\n mlModelBinding true\n }\n\n compileOptions {\n sourceCompatibility JavaVersion.VERSION_17\n targetCompatibility JavaVersion.VERSION_17\n }\n\n kotlinOptions {\n jvmTarget = '17'\n }\n\n aaptOptions {\n noCompress \"tflite\"\n }\n}\n\ndependencies {\n // Core Android\n implementation 'androidx.core:core-ktx:1.12.0'\n implementation 'androidx.appcompat:appcompat:1.6.1'\n implementation 'com.google.android.material:material:1.11.0'\n implementation 'androidx.constraintlayout:constraintlayout:2.1.4'\n implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.7.0'\n\n // CameraX\n def camerax_version = \"1.3.1\"\n implementation \"androidx.camera:camera-camera2:$camerax_version\"\n implementation \"androidx.camera:camera-lifecycle:$camerax_version\"\n implementation \"androidx.camera:camera-view:$camerax_version\"\n\n // ML Kit\n implementation 'com.google.mlkit:face-detection:17.1.0'\n implementation 'com.google.mlkit:image-labeling:17.0.7'\n\n // TensorFlow Lite\n implementation 'org.tensorflow:tensorflow-lite:2.14.0'\n implementation 'org.tensorflow:tensorflow-lite-support:0.4.4'\n implementation 'org.tensorflow:tensorflow-lite-metadata:0.4.4'\n implementation 'org.tensorflow:tensorflow-lite-gpu:2.14.0'\n implementation 'org.tensorflow:tensorflow-lite-task-vision:0.4.4'\n\n // MediaPipe\n implementation 'com.google.mediapipe:tasks-vision:0.10.0'\n\n // Dependency Injection\n implementation \"com.google.dagger:hilt-android:$hilt_version\"\n kapt \"com.google.dagger:hilt-android-compiler:$hilt_version\"\n\n // Navigation\n implementation \"androidx.navigation:navigation-fragment-ktx:$nav_version\"\n implementation \"androidx.navigation:navigation-ui-ktx:$nav_version\"\n\n // Coroutines\n implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3'\n implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-play-services:1.7.3'\n\n // Room\n def room_version = \"2.6.1\"\n implementation \"androidx.room:room-runtime:$room_version\"\n implementation \"androidx.room:room-ktx:$room_version\"\n kapt \"androidx.room:room-compiler:$room_version\"\n\n // WorkManager\n implementation \"androidx.work:work-runtime-ktx:2.9.0\"\n\n // Testing\n testImplementation 'junit:junit:4.13.2'\n androidTestImplementation 'androidx.test.ext:junit:1.1.5'\n androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'\n}",
"size": 3284,
"language": "unknown"
},
"app/src/test/java/com/example/emotiondetector/MainViewModelTest.kt": {
"content": "package com.example.emotiondetector\n\nimport android.net.Uri\nimport androidx.arch.core.executor.testing.InstantTaskExecutorRule\nimport androidx.test.ext.junit.runners.AndroidJUnit4\nimport com.example.emotiondetector.data.AppData\nimport com.example.emotiondetector.ui.MainViewModel\nimport com.example.emotiondetector.util.AppLogger\nimport com.example.emotiondetector.util.FileUtils\nimport com.example.emotiondetector.util.Prefs\nimport com.google.common.truth.Truth.assertThat\nimport dagger.hilt.android.testing.HiltAndroidRule\nimport dagger.hilt.android.testing.HiltAndroidTest\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.ExperimentalCoroutinesApi\nimport kotlinx.coroutines.test.StandardTestDispatcher\nimport kotlinx.coroutines.test.TestCoroutineDispatcher\nimport kotlinx.coroutines.test.TestScope\nimport kotlinx.coroutines.test.advanceUntilIdle\nimport kotlinx.coroutines.test.resetMain\nimport kotlinx.coroutines.test.runTest\nimport kotlinx.coroutines.test.setMain\nimport org.junit.After\nimport org.junit.Before\nimport org.junit.Rule\nimport org.junit.Test\nimport org.junit.runner.RunWith\nimport org.mockito.ArgumentMatchers.anyString\nimport org.mockito.Mockito.any\nimport org.mockito.Mockito.mock\nimport org.mockito.Mockito.verify\nimport org.mockito.Mockito.`when` as whenever\nimport javax.inject.Inject\n\n@OptIn(ExperimentalCoroutinesApi::class)\n@HiltAndroidTest\n@RunWith(AndroidJUnit4::class)\nclass MainViewModelTest {\n\n @get:Rule\n val hiltRule = HiltAndroidRule(this)\n\n @get:Rule\n val instantTaskExecutorRule = InstantTaskExecutorRule()\n\n private lateinit var viewModel: MainViewModel\n private lateinit var testDispatcher: TestCoroutineDispatcher\n private lateinit var testScope: TestScope\n \n // Mocks\n private lateinit var mockAppLogger: AppLogger\n private lateinit var mockPrefs: Prefs\n private lateinit var mockFileUtils: FileUtils\n\n @Before\n fun setup() {\n testDispatcher = TestCoroutineDispatcher()\n testScope = TestScope(testDispatcher)\n Dispatchers.setMain(testDispatcher)\n \n // Initialize mocks\n mockAppLogger = mock(AppLogger::class.java)\n mockPrefs = mock(Prefs::class.java)\n mockFileUtils = mock(FileUtils::class.java)\n \n // Initialize Hilt\n hiltRule.inject()\n \n // Create ViewModel with test dependencies\n viewModel = MainViewModel(mockAppLogger, mockPrefs, mockFileUtils)\n }\n\n @After\n fun tearDown() {\n Dispatchers.resetMain()\n testDispatcher.cleanupTestCoroutines()\n }\n\n @Test\n fun `initializeApp increments app launch count`() = testScope.runTest {\n // Given\n var launchCount = 0\n whenever(mockPrefs.json(anyString(), any())).thenReturn(launchCount)\n \n // When\n viewModel.initializeApp()\n advanceUntilIdle()\n \n // Then\n verify(mockPrefs).json(\"app_launch_count\", 0)\n verify(mockAppLogger).d(\"MainViewModel\", \"App launch count: 1\")\n }\n\n @Test\n fun `toggleDarkMode updates preference and logs change`() {\n // Given\n val darkModeEnabled = true\n \n // When\n viewModel.toggleDarkMode(darkModeEnabled)\n \n // Then\n verify(mockPrefs).json(\"dark_mode\", darkModeEnabled)\n verify(mockAppLogger).d(\"MainViewModel\", \"Dark mode enabled\")\n }\n \n @Test\n fun `saveImage with valid uri saves image and updates state`() = testScope.runTest {\n // Given\n val testUri = mock(Uri::class.java)\n val expectedPath = \"/test/path/image.jpg\"\n whenever(mockFileUtils.saveImageFromUri(testUri, \"emotion_captures\"))\n .thenReturn(expectedPath)\n \n // When\n viewModel.saveImage(testUri)\n advanceUntilIdle()\n \n // Then\n verify(mockFileUtils).saveImageFromUri(testUri, \"emotion_captures\")\n verify(mockAppLogger).i(\"MainViewModel\", anyString())\n \n val state = viewModel.uiState.value as MainViewModel.MainUiState.Success\n assertThat(state.message).isEqualTo(\"Image saved successfully\")\n }\n \n @Test\n fun `saveImage with error updates state with error message`() = testScope.runTest {\n // Given\n val testUri = mock(Uri::class.java)\n val errorMessage = \"Failed to save image\"\n whenever(mockFileUtils.saveImageFromUri(testUri, \"emotion_captures\"))\n .thenThrow(RuntimeException(errorMessage))\n \n // When\n viewModel.saveImage(testUri)\n advanceUntilIdle()\n \n // Then\n val state = viewModel.uiState.value as MainViewModel.MainUiState.Error\n assertThat(state.message).contains(errorMessage)\n }\n \n @Test\n fun `loadInitialData loads cached data from prefs`() {\n // Given\n val testData = AppData()\n whenever(mockPrefs.json(\"cached_data\")).thenReturn(testData)\n \n // When\n viewModel.loadInitialData()\n \n // Then\n verify(mockPrefs).json(\"cached_data\")\n }\n}\n",
"size": 5100,
"language": "kotlin"
},
"app/src/test/java/com/example/emotiondetector/utils/TestUtils.kt": {
"content": "package com.example.emotiondetector.utils\n\nimport android.content.Context\nimport androidx.test.core.app.ApplicationProvider\nimport com.example.emotiondetector.util.AppLogger\nimport com.example.emotiondetector.util.FileUtils\nimport com.example.emotiondetector.util.Prefs\nimport org.mockito.ArgumentMatchers.anyString\nimport org.mockito.Mockito\nimport org.mockito.Mockito.`when`\nimport java.io.File\nimport java.util.concurrent.Executors\n\n/**\n * Test utilities for common test functionality\n */\nobject TestUtils {\n \n /**\n * Create a test application context\n */\n fun getTestContext(): Context {\n return ApplicationProvider.getApplicationContext()\n }\n \n /**\n * Create a test file in the test app's cache directory\n */\n fun createTestFile(fileName: String, content: String = \"test content\"): File {\n val context = getTestContext()\n val file = File(context.cacheDir, fileName)\n file.parentFile?.mkdirs()\n file.writeText(content)\n return file\n }\n \n /**\n * Create a mocked Prefs instance with common configurations\n */\n fun createMockPrefs(): Prefs {\n val prefs = Mockito.mock(Prefs::class.java)\n `when`(prefs.json(anyString(), any())).thenAnswer { it.arguments[1] as Any }\n return prefs\n }\n \n /**\n * Create a mocked AppLogger instance\n */\n fun createMockLogger(): AppLogger {\n return Mockito.mock(AppLogger::class.java)\n }\n \n /**\n * Create a test FileUtils instance\n */\n fun createTestFileUtils(): FileUtils {\n val context = getTestContext()\n return FileUtils(context).apply {\n // Override any methods if needed for testing\n }\n }\n \n /**\n * Create a test executor for coroutine testing\n */\n fun createTestExecutor() = Executors.newSingleThreadExecutor()\n \n /**\n * Get a test asset file path\n */\n fun getTestAssetPath(assetName: String): String {\n return \"src/test/assets/$assetName\"\n }\n \n /**\n * Sleep for a short duration to allow coroutines to complete\n */\n suspend fun delayForCoroutines() {\n kotlinx.coroutines.delay(100)\n }\n \n /**\n * Helper to create mock with relaxed settings\n */\n inline fun relaxedMock(): T = Mockito.mock(T::class.java, \n Mockito.CALLS_REAL_METHODS ?: Mockito.RETURNS_DEFAULTS\n )\n}\n\n/**\n * Helper function to read a file from test resources\n */\nfun readTestResourceFile(fileName: String): String {\n val classLoader = TestUtils::class.java.classLoader\n return classLoader?.getResourceAsStream(fileName)?.bufferedReader()?.use { it.readText() }\n ?: throw IllegalStateException(\"Could not read test resource: $fileName\")\n}\n\n/**\n * Helper function to create a temporary file for testing\n */\nfun createTempTestFile(prefix: String = \"test\", suffix: String = \".tmp\"): File {\n return File.createTempFile(prefix, suffix, ApplicationProvider.getApplicationContext().cacheDir).apply {\n deleteOnExit()\n }\n}\n",
"size": 3081,
"language": "kotlin"
},
"app/src/test/java/com/example/emotiondetector/workers/DownloadModelWorkerTest.kt": {
"content": "package com.example.emotiondetector.workers\n\nimport android.content.Context\nimport androidx.test.core.app.ApplicationProvider\nimport androidx.test.ext.junit.runners.AndroidJUnit4\nimport androidx.work.ListenableWorker.Result\nimport androidx.work.WorkerParameters\nimport androidx.work.testing.TestListenableWorkerBuilder\nimport com.example.emotiondetector.data.ModelManager\nimport com.example.emotiondetector.data.ModelPreferences\nimport com.example.emotiondetector.di.AppModule\nimport com.example.emotiondetector.di.WorkerModule\nimport com.example.emotiondetector.util.AppLogger\nimport com.example.emotiondetector.util.NetworkUtils\nimport com.google.common.truth.Truth.assertThat\nimport dagger.hilt.android.testing.HiltAndroidRule\nimport dagger.hilt.android.testing.HiltAndroidTest\nimport dagger.hilt.android.testing.UninstallModules\nimport kotlinx.coroutines.ExperimentalCoroutinesApi\nimport kotlinx.coroutines.test.runTest\nimport org.junit.Before\nimport org.junit.Rule\nimport org.junit.Test\nimport org.junit.runner.RunWith\nimport org.mockito.Mock\nimport org.mockito.Mockito.`when`\nimport org.mockito.Mockito.anyString\nimport org.mockito.MockitoAnnotations\nimport javax.inject.Inject\n\n@OptIn(ExperimentalCoroutinesApi::class)\n@HiltAndroidTest\n@UninstallModules(AppModule::class, WorkerModule::class)\n@RunWith(AndroidJUnit4::class)\nclass DownloadModelWorkerTest {\n\n @get:Rule\n val hiltRule = HiltAndroidRule(this)\n\n @Mock\n private lateinit var mockModelManager: ModelManager\n\n @Mock\n private lateinit var mockModelPreferences: ModelPreferences\n\n @Mock\n private lateinit var mockNetworkUtils: NetworkUtils\n\n @Mock\n private lateinit var mockAppLogger: AppLogger\n\n private lateinit var context: Context\n private lateinit var workerParams: WorkerParameters\n\n @Before\n fun setUp() {\n MockitoAnnotations.openMocks(this)\n context = ApplicationProvider.getApplicationContext()\n \n // Create test worker parameters\n workerParams = TestListenableWorkerBuilder(context).build().run {\n this.workerParameters\n }\n \n // Initialize Hilt\n hiltRule.inject()\n }\n\n @Test\n fun `doWork when download succeeds returns success`() = runTest {\n // Given\n `when`(mockNetworkUtils.isNetworkAvailable()).thenReturn(true)\n `when`(mockModelManager.downloadModel(anyString())).thenReturn(Result.success())\n \n // Create worker with test dependencies\n val worker = TestListenableWorkerBuilder(context)\n .setWorkerFactory(createWorkerFactory())\n .build()\n \n // When\n val result = worker.doWork()\n \n // Then\n assertThat(result).isEqualTo(Result.success())\n }\n \n @Test\n fun `doWork when network is unavailable returns retry`() = runTest {\n // Given\n `when`(mockNetworkUtils.isNetworkAvailable()).thenReturn(false)\n \n // Create worker with test dependencies\n val worker = TestListenableWorkerBuilder(context)\n .setWorkerFactory(createWorkerFactory())\n .build()\n \n // When\n val result = worker.doWork()\n \n // Then\n assertThat(result).isEqualTo(Result.retry())\n }\n \n @Test\n fun `doWork when download fails returns failure`() = runTest {\n // Given\n `when`(mockNetworkUtils.isNetworkAvailable()).thenReturn(true)\n `when`(mockModelManager.downloadModel(anyString()))\n .thenReturn(Result.failure())\n \n // Create worker with test dependencies\n val worker = TestListenableWorkerBuilder(context)\n .setWorkerFactory(createWorkerFactory())\n .build()\n \n // When\n val result = worker.doWork()\n \n // Then\n assertThat(result).isEqualTo(Result.failure())\n }\n \n private fun createWorkerFactory(): TestWorkerFactory {\n return TestWorkerFactory(\n mockModelManager,\n mockModelPreferences,\n mockNetworkUtils,\n mockAppLogger\n )\n }\n \n // Test worker factory for dependency injection\n class TestWorkerFactory(\n private val modelManager: ModelManager,\n private val modelPreferences: ModelPreferences,\n private val networkUtils: NetworkUtils,\n private val appLogger: AppLogger\n ) : androidx.work.WorkerFactory() {\n override fun createWorker(\n appContext: Context,\n workerClassName: String,\n workerParameters: WorkerParameters\n ): ListenableWorker? {\n return DownloadModelWorker(\n appContext,\n workerParameters,\n modelManager,\n modelPreferences,\n networkUtils,\n appLogger\n )\n }\n }\n}\n",
"size": 4950,
"language": "kotlin"
},
"app/src/main/AndroidManifest.xml": {
"content": "\n\n\n \n \n \n \n \n\n \n \n\n \n\n \n \n \n \n \n \n\n \n \n \n \n\n",
"size": 2138,
"language": "xml"
},
"app/src/main/java/com/example/emotiondetector/EmotionDetectorApp.kt": {
"content": "package com.example.emotiondetector\n\nimport android.app.Application\nimport android.content.Context\nimport androidx.hilt.work.HiltWorkerFactory\nimport androidx.work.Configuration\nimport com.example.emotiondetector.di.AppModule\nimport com.example.emotiondetector.di.WorkerModule\nimport com.example.emotiondetector.util.AppLogger\nimport com.example.emotiondetector.util.FileUtils\nimport com.example.emotiondetector.util.Prefs\nimport dagger.hilt.android.HiltAndroidApp\nimport javax.inject.Inject\n\n/**\n * Main application class that initializes Hilt and application-wide components\n */\n@HiltAndroidApp\nclass EmotionDetectorApp : Application(), Configuration.Provider {\n\n @Inject\n lateinit var workerFactory: HiltWorkerFactory\n\n @Inject\n lateinit var appLogger: AppLogger\n\n @Inject\n lateinit var prefs: Prefs\n\n @Inject\n lateinit var fileUtils: FileUtils\n\n override fun onCreate() {\n super.onCreate()\n \n // Initialize application components\n initializeApp()\n }\n\n private fun initializeApp() {\n // Log app start\n appLogger.i(\"Application\", \"Emotion Detector app starting...\")\n \n // Initialize directories\n fileUtils.initializeAppDirs()\n \n // Log initialization complete\n appLogger.i(\"Application\", \"Application initialization complete\")\n }\n\n override fun getWorkManagerConfiguration(): Configuration {\n return Configuration.Builder()\n .setWorkerFactory(workerFactory)\n .setMinimumLoggingLevel(android.util.Log.INFO)\n .build()\n }\n\n companion object {\n /**\n * Get the application instance\n */\n fun from(context: Context): EmotionDetectorApp {\n return context.applicationContext as EmotionDetectorApp\n }\n }\n}\n\n// Add this to your AndroidManifest.xml:\n// \n",
"size": 1934,
"language": "kotlin"
},
"app/src/main/java/com/example/emotiondetector/ui/MainViewModel.kt": {
"content": "package com.example.emotiondetector.ui\n\nimport android.net.Uri\nimport androidx.lifecycle.ViewModel\nimport androidx.lifecycle.viewModelScope\nimport com.example.emotiondetector.util.AppLogger\nimport com.example.emotiondetector.util.DateTimeUtils\nimport com.example.emotiondetector.util.FileUtils\nimport com.example.emotiondetector.util.Prefs\nimport com.example.emotiondetector.util.json\nimport dagger.hilt.android.lifecycle.HiltViewModel\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport kotlinx.coroutines.flow.StateFlow\nimport kotlinx.coroutines.flow.asStateFlow\nimport kotlinx.coroutines.launch\nimport java.util.*\nimport javax.inject.Inject\n\n@HiltViewModel\nclass MainViewModel @Inject constructor(\n private val appLogger: AppLogger,\n private val prefs: Prefs,\n private val fileUtils: FileUtils\n) : ViewModel() {\n\n private val _uiState = MutableStateFlow(MainUiState.Loading)\n val uiState: StateFlow = _uiState.asStateFlow()\n\n // Preferences example\n private var appLaunchCount by prefs.json(\"app_launch_count\", 0)\n \n // User settings with default values\n private var isDarkMode by prefs.json(\"dark_mode\", false)\n private var lastUpdateCheck by prefs.json(\"last_update_check\")\n \n init {\n initializeApp()\n }\n \n private fun initializeApp() {\n viewModelScope.launch {\n try {\n // Track app launches\n appLaunchCount++\n appLogger.d(\"MainViewModel\", \"App launch count: $appLaunchCount\")\n \n // Update last check time\n lastUpdateCheck = Date()\n \n // Check for updates if needed\n checkForUpdates()\n \n // Load initial data\n loadInitialData()\n \n _uiState.value = MainUiState.Success(\"App initialized successfully\")\n } catch (e: Exception) {\n appLogger.e(\"MainViewModel\", \"Error initializing app\", e)\n _uiState.value = MainUiState.Error(\"Initialization failed: ${e.message}\")\n }\n }\n }\n \n private fun checkForUpdates() {\n // Example: Check for updates if it's been more than 24 hours\n val lastCheck = lastUpdateCheck?.time ?: 0L\n val oneDayInMillis = 24 * 60 * 60 * 1000L\n \n if (System.currentTimeMillis() - lastCheck > oneDayInMillis) {\n appLogger.i(\"MainViewModel\", \"Checking for updates...\")\n // TODO: Implement actual update check\n }\n }\n \n private fun loadInitialData() {\n // Example: Load some initial data\n val cachedData = prefs.json(\"cached_data\") ?: AppData()\n // Process cached data...\n }\n \n fun toggleDarkMode(enabled: Boolean) {\n isDarkMode = enabled\n appLogger.d(\"MainViewModel\", \"Dark mode ${if (enabled) \"enabled\" else \"disabled\"}\")\n // TODO: Apply theme changes\n }\n \n fun saveImage(uri: Uri) {\n viewModelScope.launch {\n try {\n _uiState.value = MainUiState.Loading\n \n // Example: Save image using FileUtils\n val savedPath = fileUtils.saveImageFromUri(uri, \"emotion_captures\")\n \n // Log the action with timestamp\n val timestamp = DateTimeUtils.formatDate(Date(), \"yyyy-MM-dd HH:mm:ss\")\n appLogger.i(\"MainViewModel\", \"Image saved at: $savedPath ($timestamp)\")\n \n _uiState.value = MainUiState.Success(\"Image saved successfully\")\n } catch (e: Exception) {\n appLogger.e(\"MainViewModel\", \"Error saving image\", e)\n _uiState.value = MainUiState.Error(\"Failed to save image: ${e.message}\")\n }\n }\n }\n \n // Data classes for state management\n sealed class MainUiState {\n object Loading : MainUiState()\n data class Success(val message: String) : MainUiState()\n data class Error(val message: String) : MainUiState()\n }\n \n data class AppData(\n val lastSync: Date = Date(),\n val settings: Map = emptyMap(),\n val recentEmotions: List = emptyList()\n )\n}\n",
"size": 4281,
"language": "kotlin"
},
"app/src/main/java/com/example/emotiondetector/ui/MainActivity.kt": {
"content": "package com.example.emotiondetector.ui\n\nimport android.Manifest\nimport android.os.Bundle\nimport androidx.activity.ComponentActivity\nimport androidx.activity.compose.setContent\nimport androidx.activity.result.contract.ActivityResultContracts\nimport androidx.compose.foundation.layout.fillMaxSize\nimport androidx.compose.material3.MaterialTheme\nimport androidx.compose.material3.Surface\nimport androidx.compose.runtime.*\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.platform.LocalLifecycleOwner\nimport androidx.lifecycle.Lifecycle\nimport androidx.lifecycle.LifecycleEventObserver\nimport com.example.emotiondetector.ui.theme.EmotionDetectorTheme\nimport dagger.hilt.android.AndroidEntryPoint\n\n@AndroidEntryPoint\nclass MainActivity : ComponentActivity() {\n \n private val requestPermissionLauncher = registerForActivityResult(\n ActivityResultContracts.RequestPermission()\n ) { isGranted ->\n if (isGranted) {\n // Permission granted, start camera\n } else {\n // Show rationale\n }\n }\n\n\n override fun onCreate(savedInstanceState: Bundle?) {\n super.onCreate(savedInstanceState)\n \n setContent {\n EmotionDetectorTheme {\n // A surface container using the 'background' color from the theme\n Surface(\n modifier = Modifier.fillMaxSize(),\n color = MaterialTheme.colorScheme.background\n ) {\n EmotionDetectorApp(\n onPermissionDenied = {\n // Handle permission denied\n }\n )\n }\n }\n }\n }\n}\n\n@Composable\nfun EmotionDetectorApp(\n onPermissionDenied: () -> Unit\n) {\n val lifecycleOwner = LocalLifecycleOwner.current\n \n // Handle camera permission\n DisposableEffect(key1 = lifecycleOwner) {\n val observer = LifecycleEventObserver { _, event ->\n if (event == Lifecycle.Event.ON_START) {\n // Check and request permissions\n }\n }\n \n lifecycleOwner.lifecycle.addObserver(observer)\n onDispose {\n lifecycleOwner.lifecycle.removeObserver(observer)\n }\n }\n \n // Main navigation\n MainNavigation()\n}\n\n@Composable\nfun MainNavigation() {\n // Navigation setup will be implemented here\n CameraScreen()\n}\n\n@Composable\nfun CameraScreen() {\n // Camera preview and emotion detection UI will be implemented here\n Surface(\n modifier = Modifier.fillMaxSize(),\n color = MaterialTheme.colorScheme.surface\n ) {\n // Camera preview and emotion detection overlay\n }\n}\n",
"size": 2711,
"language": "kotlin"
},
"app/src/main/java/com/example/emotiondetector/ui/viewmodel/EmotionViewModel.kt": {
"content": "package com.example.emotiondetector.ui.viewmodel\n\nimport android.graphics.Bitmap\nimport android.util.Log\nimport androidx.camera.core.ImageProxy\nimport androidx.compose.runtime.getValue\nimport androidx.compose.runtime.mutableStateOf\nimport androidx.compose.runtime.setValue\nimport androidx.lifecycle.ViewModel\nimport androidx.lifecycle.viewModelScope\nimport com.example.emotiondetector.domain.EmotionDetector\nimport com.example.emotiondetector.domain.EmotionResult\nimport dagger.hilt.android.lifecycle.HiltViewModel\nimport kotlinx.coroutines.Job\nimport kotlinx.coroutines.delay\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport kotlinx.coroutines.flow.StateFlow\nimport kotlinx.coroutines.flow.asStateFlow\nimport kotlinx.coroutines.flow.update\nimport kotlinx.coroutines.launch\nimport java.util.concurrent.atomic.AtomicBoolean\nimport javax.inject.Inject\n\n@HiltViewModel\nclass EmotionViewModel @Inject constructor(\n private val emotionDetector: EmotionDetector\n) : ViewModel() {\n\n private val _uiState = MutableStateFlow(EmotionUiState())\n val uiState: StateFlow = _uiState.asStateFlow()\n \n private val isProcessing = AtomicBoolean(false)\n private var processingJob: Job? = null\n \n // Track FPS for performance monitoring\n private var frameCount = 0\n private var lastFpsUpdateTime = System.currentTimeMillis()\n \n // Model performance metrics\n private var totalInferenceTime = 0L\n private var inferenceCount = 0\n \n // Current camera facing direction\n var cameraFacingFront by mutableStateOf(true)\n private set\n \n /**\n * Process a camera frame for emotion detection\n */\n fun processImage(imageProxy: ImageProxy) {\n // Skip if already processing or UI is not ready\n if (isProcessing.get() || !_uiState.value.isReady) {\n imageProxy.close()\n return\n }\n \n isProcessing.set(true)\n \n // Cancel any existing processing job\n processingJob?.cancel()\n \n processingJob = viewModelScope.launch {\n try {\n val startTime = System.currentTimeMillis()\n \n // Detect emotions in the image\n val results = emotionDetector.detectEmotions(imageProxy)\n \n // Calculate inference time\n val inferenceTime = System.currentTimeMillis() - startTime\n \n // Update metrics\n totalInferenceTime += inferenceTime\n inferenceCount++\n \n // Update FPS counter\n updateFps()\n \n // Update UI state with results\n _uiState.update { currentState ->\n currentState.copy(\n emotionResults = results,\n lastInferenceTime = inferenceTime,\n averageInferenceTime = if (inferenceCount > 0) {\n totalInferenceTime / inferenceCount\n } else {\n 0L\n },\n isProcessing = false\n )\n }\n \n // Log performance metrics\n if (inferenceCount % 30 == 0) { // Log every 30 frames\n Log.d(\"EmotionViewModel\", \n \"Avg inference time: ${_uiState.value.averageInferenceTime}ms, \" +\n \"FPS: ${_uiState.value.currentFps}\")\n }\n \n } catch (e: Exception) {\n Log.e(\"EmotionViewModel\", \"Error processing image\", e)\n _uiState.update { it.copy(error = e.message, isProcessing = false) }\n } finally {\n isProcessing.set(false)\n }\n }\n }\n \n /**\n * Toggle between front and back camera\n */\n fun toggleCamera() {\n cameraFacingFront = !cameraFacingFront\n _uiState.update { it.copy(cameraFacingFront = cameraFacingFront) }\n }\n \n /**\n * Update FPS counter\n */\n private fun updateFps() {\n frameCount++\n val currentTime = System.currentTimeMillis()\n val elapsedTime = currentTime - lastFpsUpdateTime\n \n // Update FPS every second\n if (elapsedTime >= 1000) {\n val fps = (frameCount * 1000 / elapsedTime.toFloat()).toInt()\n _uiState.update { it.copy(currentFps = fps) }\n frameCount = 0\n lastFpsUpdateTime = currentTime\n }\n }\n \n /**\n * Reset the emotion detection state\n */\n fun reset() {\n _uiState.update { EmotionUiState() }\n frameCount = 0\n lastFpsUpdateTime = System.currentTimeMillis()\n totalInferenceTime = 0L\n inferenceCount = 0\n }\n \n /**\n * Set error state\n */\n fun setError(message: String) {\n _uiState.update { it.copy(error = message) }\n }\n \n /**\n * Clear error state\n */\n fun clearError() {\n _uiState.update { it.copy(error = null) }\n }\n \n override fun onCleared() {\n super.onCleared()\n processingJob?.cancel()\n emotionDetector.close()\n }\n}\n\n/**\n * UI state for the emotion detection screen\n */\ndata class EmotionUiState(\n val emotionResults: List = emptyList(),\n val isProcessing: Boolean = false,\n val error: String? = null,\n val currentFps: Int = 0,\n val lastInferenceTime: Long = 0,\n val averageInferenceTime: Long = 0,\n val cameraFacingFront: Boolean = true,\n val isReady: Boolean = true // Indicates if the detector is ready to process frames\n)\n",
"size": 5704,
"language": "kotlin"
},
"app/src/main/java/com/example/emotiondetector/ui/camera/CameraScreen.kt": {
"content": "package com.example.emotiondetector.ui.camera\n\nimport android.Manifest\nimport android.content.pm.PackageManager\nimport android.util.Log\nimport android.util.Size\nimport androidx.activity.compose.rememberLauncherForActivityResult\nimport androidx.activity.result.contract.ActivityResultContracts\nimport androidx.camera.core.CameraSelector\nimport androidx.camera.core.ImageAnalysis\nimport androidx.camera.core.Preview\nimport androidx.camera.lifecycle.ProcessCameraProvider\nimport androidx.camera.view.PreviewView\nimport androidx.compose.foundation.background\nimport androidx.compose.foundation.layout.*\nimport androidx.compose.material.icons.Icons\nimport androidx.compose.material.icons.filled.Cameraswitch\nimport androidx.compose.material.icons.filled.Info\nimport androidx.compose.material3.*\nimport androidx.compose.runtime.*\nimport androidx.compose.ui.Alignment\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.graphics.Color\nimport androidx.compose.ui.platform.LocalContext\nimport androidx.compose.ui.platform.LocalLifecycleOwner\nimport androidx.compose.ui.unit.dp\nimport androidx.compose.ui.viewinterop.AndroidView\nimport androidx.core.content.ContextCompat\nimport androidx.lifecycle.Lifecycle\nimport androidx.lifecycle.LifecycleEventObserver\nimport com.example.emotiondetector.R\nimport com.example.emotiondetector.ui.theme.*\nimport com.google.accompanist.permissions.ExperimentalPermissionsApi\nimport com.google.accompanist.permissions.rememberPermissionState\nimport kotlinx.coroutines.launch\n\n@OptIn(ExperimentalMaterial3Api::class, ExperimentalPermissionsApi::class)\n@Composable\nfun CameraScreen(\n onError: (String) -> Unit = {},\n onEmotionDetected: (String, Float) -> Unit = { _, _ -> }\n) {\n val context = LocalContext.current\n val lifecycleOwner = LocalLifecycleOwner.current\n val coroutineScope = rememberCoroutineScope()\n \n // Camera state\n var hasCamPermission by remember { \n mutableStateOf(\n ContextCompat.checkSelfPermission(\n context,\n Manifest.permission.CAMERA\n ) == PackageManager.PERMISSION_GRANTED\n )\n }\n \n val cameraPermissionState = rememberPermissionState(Manifest.permission.CAMERA) {\n hasCamPermission = it\n if (!it) {\n onError(\"Camera permission is required for emotion detection\")\n }\n }\n \n // Camera provider\n val cameraProviderFuture = remember { ProcessCameraProvider.getInstance(context) }\n var previewUseCase by remember { mutableStateOf(null) }\n var cameraSelector by remember { \n mutableStateOf(CameraSelector.DEFAULT_FRONT_CAMERA) \n }\n \n // Request permission when the screen is first launched\n LaunchedEffect(Unit) {\n if (!hasCamPermission) {\n cameraPermissionState.launchPermissionRequest()\n }\n }\n \n // Set up the camera when permission is granted\n if (hasCamPermission) {\n AndroidView(\n modifier = Modifier.fillMaxSize(),\n factory = { ctx ->\n val previewView = PreviewView(ctx).apply {\n implementationMode = PreviewView.ImplementationMode.COMPATIBLE\n scaleType = PreviewView.ScaleType.FILL_CENTER\n }\n \n // Set up camera use cases\n coroutineScope.launch {\n val cameraProvider = cameraProviderFuture.await()\n \n // Set up the preview use case\n val preview = Preview.Builder().build().also {\n it.setSurfaceProvider(previewView.surfaceProvider)\n }\n previewUseCase = preview\n \n // Set up the image analysis use case\n val imageAnalysis = ImageAnalysis.Builder()\n .setTargetResolution(Size(1280, 720))\n .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)\n .build()\n \n imageAnalysis.setAnalyzer(\n ContextCompat.getMainExecutor(context),\n { imageProxy ->\n // Process the image for emotion detection\n // This will be implemented in the next step\n imageProxy.close()\n }\n )\n \n try {\n // Unbind all use cases before binding new ones\n cameraProvider.unbindAll()\n \n // Bind use cases to the lifecycle\n cameraProvider.bindToLifecycle(\n lifecycleOwner,\n cameraSelector,\n preview,\n imageAnalysis\n )\n } catch (e: Exception) {\n onError(\"Failed to start camera: ${e.message}\")\n }\n }\n \n previewView\n }\n )\n \n // Camera controls overlay\n Box(\n modifier = Modifier\n .fillMaxSize()\n .padding(16.dp)\n ) {\n // Switch camera button\n FloatingActionButton(\n onClick = {\n cameraSelector = when (cameraSelector) {\n CameraSelector.DEFAULT_FRONT_CAMERA -> CameraSelector.DEFAULT_BACK_CAMERA\n else -> CameraSelector.DEFAULT_FRONT_CAMERA\n }\n // Restart preview with new camera selector\n previewUseCase?.targetRotation = when (cameraSelector.lensFacing) {\n CameraSelector.LENS_FACING_FRONT -> 270\n else -> 90\n }\n },\n modifier = Modifier\n .align(Alignment.BottomEnd)\n .padding(bottom = 16.dp),\n containerColor = MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.9f)\n ) {\n Icon(\n imageVector = Icons.Default.Cameraswitch,\n contentDescription = \"Switch camera\"\n )\n }\n \n // Emotion result overlay\n EmotionResultOverlay(\n modifier = Modifier\n .align(Alignment.TopCenter)\n .padding(top = 32.dp)\n )\n }\n } else {\n // Permission not granted UI\n Box(\n modifier = Modifier\n .fillMaxSize()\n .background(MaterialTheme.colorScheme.surface)\n .padding(16.dp),\n contentAlignment = Alignment.Center\n ) {\n Column(\n horizontalAlignment = Alignment.CenterHorizontally,\n verticalArrangement = Arrangement.Center,\n modifier = Modifier.padding(16.dp)\n ) {\n Icon(\n imageVector = Icons.Default.Info,\n contentDescription = \"Permission required\",\n tint = MaterialTheme.colorScheme.primary,\n modifier = Modifier.size(48.dp)\n )\n Spacer(modifier = Modifier.height(16.dp))\n Text(\n text = \"Camera Permission Required\",\n style = MaterialTheme.typography.titleLarge,\n color = MaterialTheme.colorScheme.onSurface\n )\n Spacer(modifier = Modifier.height(8.dp))\n Text(\n text = \"Please grant camera permission to enable emotion detection\",\n style = MaterialTheme.typography.bodyMedium,\n color = MaterialTheme.colorScheme.onSurfaceVariant,\n textAlign = TextAlign.Center\n )\n Spacer(modifier = Modifier.height(24.dp))\n Button(onClick = { cameraPermissionState.launchPermissionRequest() }) {\n Text(\"Grant Permission\")\n }\n }\n }\n }\n}\n\n@Composable\nprivate fun EmotionResultOverlay(\n modifier: Modifier = Modifier,\n emotion: String = \"Neutral\",\n confidence: Float = 0f\n) {\n val emotionColor = when (emotion.lowercase()) {\n \"happy\" -> HappyColor\n \"sad\" -> SadColor\n \"angry\" -> AngryColor\n \"surprise\" -> SurpriseColor\n \"fear\" -> FearColor\n \"disgust\" -> DisgustColor\n else -> NeutralColor\n }\n \n Surface(\n modifier = modifier,\n color = MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.9f),\n shape = MaterialTheme.shapes.medium,\n shadowElevation = 4.dp\n ) {\n Row(\n verticalAlignment = Alignment.CenterVertically,\n modifier = Modifier.padding(horizontal = 16.dp, vertical = 8.dp)\n ) {\n Box(\n modifier = Modifier\n .size(12.dp)\n .background(emotionColor, MaterialTheme.shapes.small)\n )\n Spacer(modifier = Modifier.width(8.dp))\n Text(\n text = \"${emotion.replaceFirstChar { it.uppercase() }}: ${(confidence * 100).toInt()}%\",\n style = MaterialTheme.typography.titleMedium,\n color = MaterialTheme.colorScheme.onSurfaceVariant\n )\n }\n }\n}\n\n@Composable\nfun PreviewCameraScreen() {\n EmotionDetectorTheme {\n Surface(\n modifier = Modifier.fillMaxSize(),\n color = MaterialTheme.colorScheme.background\n ) {\n CameraScreen(\n onError = {},\n onEmotionDetected = { _, _ -> }\n )\n }\n }\n}\n",
"size": 9928,
"language": "kotlin"
},
"app/src/main/java/com/example/emotiondetector/ui/theme/Color.kt": {
"content": "package com.example.emotiondetector.ui.theme\n\nimport androidx.compose.ui.graphics.Color\n\n// Light theme colors\nval Purple80 = Color(0xFFD0BCFF)\nval PurpleGrey80 = Color(0xFFCCC2DC)\nval Pink80 = Color(0xFFEFB8C8)\n\n// Dark theme colors\nval Purple40 = Color(0xFF6650a4)\nval PurpleGrey40 = Color(0xFF625b71)\nval Pink40 = Color(0xFF7D5260)\n\n// Emotion colors\nval HappyColor = Color(0xFFFFD700) // Gold\nval SadColor = Color(0xFF1E90FF) // Dodger Blue\nval AngryColor = Color(0xFFFF4500) // Orange Red\nval SurpriseColor = Color(0xFF9932CC) // Dark Orchid\nval NeutralColor = Color(0xFF808080) // Gray\nval FearColor = Color(0xFF8B4513) // Saddle Brown\nval DisgustColor = Color(0xFF228B22) // Forest Green\n",
"size": 701,
"language": "kotlin"
},
"app/src/main/java/com/example/emotiondetector/ui/theme/Theme.kt": {
"content": "package com.example.emotiondetector.ui.theme\n\nimport android.app.Activity\nimport android.os.Build\nimport androidx.compose.foundation.isSystemInDarkTheme\nimport androidx.compose.material3.MaterialTheme\nimport androidx.compose.material3.darkColorScheme\nimport androidx.compose.material3.dynamicDarkColorScheme\nimport androidx.compose.material3.dynamicLightColorScheme\nimport androidx.compose.material3.lightColorScheme\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.SideEffect\nimport androidx.compose.ui.graphics.toArgb\nimport androidx.compose.ui.platform.LocalContext\nimport androidx.compose.ui.platform.LocalView\nimport androidx.core.view.WindowCompat\n\nprivate val DarkColorScheme = darkColorScheme(\n primary = Purple80,\n secondary = PurpleGrey80,\n tertiary = Pink80\n)\n\nprivate val LightColorScheme = lightColorScheme(\n primary = Purple40,\n secondary = PurpleGrey40,\n tertiary = Pink40\n\n /* Other default colors to override\n background = Color(0xFFFFFBFE),\n surface = Color(0xFFFFFBFE),\n onPrimary = Color.White,\n onSecondary = Color.White,\n onTertiary = Color.White,\n onBackground = Color(0xFF1C1B1F),\n onSurface = Color(0xFF1C1B1F),\n */\n)\n\n@Composable\nfun EmotionDetectorTheme(\n darkTheme: Boolean = isSystemInDarkTheme(),\n // Dynamic color is available on Android 12+\n dynamicColor: Boolean = true,\n content: @Composable () -> Unit\n) {\n val colorScheme = when {\n dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {\n val context = LocalContext.current\n if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)\n }\n darkTheme -> DarkColorScheme\n else -> LightColorScheme\n }\n val view = LocalView.current\n if (!view.isInEditMode) {\n SideEffect {\n val window = (view.context as Activity).window\n window.statusBarColor = colorScheme.primary.toArgb()\n WindowCompat.getInsetsController(window, view).isAppearanceLightStatusBars = !darkTheme\n }\n }\n\n MaterialTheme(\n colorScheme = colorScheme,\n typography = Typography,\n content = content\n )\n}\n",
"size": 2205,
"language": "kotlin"
},
"app/src/main/java/com/example/emotiondetector/ui/theme/Type.kt": {
"content": "package com.example.emotiondetector.ui.theme\n\nimport androidx.compose.material3.Typography\nimport androidx.compose.ui.text.TextStyle\nimport androidx.compose.ui.text.font.FontFamily\nimport androidx.compose.ui.text.font.FontWeight\nimport androidx.compose.ui.unit.sp\n\n// Set of Material typography styles to start with\nval Typography = Typography(\n displayLarge = TextStyle(\n fontFamily = FontFamily.Default,\n fontWeight = FontWeight.Normal,\n fontSize = 57.sp,\n lineHeight = 64.sp,\n letterSpacing = 0.sp\n ),\n displayMedium = TextStyle(\n fontFamily = FontFamily.Default,\n fontWeight = FontWeight.Normal,\n fontSize = 45.sp,\n lineHeight = 52.sp,\n letterSpacing = 0.sp\n ),\n displaySmall = TextStyle(\n fontFamily = FontFamily.Default,\n fontWeight = FontWeight.Normal,\n fontSize = 36.sp,\n lineHeight = 44.sp,\n letterSpacing = 0.sp\n ),\n headlineLarge = TextStyle(\n fontFamily = FontFamily.Default,\n fontWeight = FontWeight.SemiBold,\n fontSize = 32.sp,\n lineHeight = 40.sp,\n letterSpacing = 0.sp\n ),\n headlineMedium = TextStyle(\n fontFamily = FontFamily.Default,\n fontWeight = FontWeight.SemiBold,\n fontSize = 28.sp,\n lineHeight = 36.sp,\n letterSpacing = 0.sp\n ),\n headlineSmall = TextStyle(\n fontFamily = FontFamily.Default,\n fontWeight = FontWeight.SemiBold,\n fontSize = 24.sp,\n lineHeight = 32.sp,\n letterSpacing = 0.sp\n ),\n titleLarge = TextStyle(\n fontFamily = FontFamily.Default,\n fontWeight = FontWeight.SemiBold,\n fontSize = 22.sp,\n lineHeight = 28.sp,\n letterSpacing = 0.sp\n ),\n titleMedium = TextStyle(\n fontFamily = FontFamily.Default,\n fontWeight = FontWeight.SemiBold,\n fontSize = 18.sp,\n lineHeight = 24.sp,\n letterSpacing = 0.1.sp\n ),\n titleSmall = TextStyle(\n fontFamily = FontFamily.Default,\n fontWeight = FontWeight.SemiBold,\n fontSize = 14.sp,\n lineHeight = 20.sp,\n letterSpacing = 0.1.sp\n ),\n bodyLarge = TextStyle(\n fontFamily = FontFamily.Default,\n fontWeight = FontWeight.Normal,\n fontSize = 16.sp,\n lineHeight = 24.sp,\n letterSpacing = 0.5.sp\n ),\n bodyMedium = TextStyle(\n fontFamily = FontFamily.Default,\n fontWeight = FontWeight.Normal,\n fontSize = 14.sp,\n lineHeight = 20.sp,\n letterSpacing = 0.25.sp\n ),\n bodySmall = TextStyle(\n fontFamily = FontFamily.Default,\n fontWeight = FontWeight.Normal,\n fontSize = 12.sp,\n lineHeight = 16.sp,\n letterSpacing = 0.4.sp\n ),\n labelLarge = TextStyle(\n fontFamily = FontFamily.Default,\n fontWeight = FontWeight.Medium,\n fontSize = 14.sp,\n lineHeight = 20.sp,\n letterSpacing = 0.1.sp\n ),\n labelMedium = TextStyle(\n fontFamily = FontFamily.Default,\n fontWeight = FontWeight.Medium,\n fontSize = 12.sp,\n lineHeight = 16.sp,\n letterSpacing = 0.5.sp\n ),\n labelSmall = TextStyle(\n fontFamily = FontFamily.Default,\n fontWeight = FontWeight.Medium,\n fontSize = 10.sp,\n lineHeight = 16.sp,\n letterSpacing = 0.5.sp\n )\n)\n",
"size": 3377,
"language": "kotlin"
},
"app/src/main/java/com/example/emotiondetector/di/AppModule.kt": {
"content": "package com.example.emotiondetector.di\n\nimport android.content.Context\nimport com.example.emotiondetector.data.ModelManager\nimport com.example.emotiondetector.data.ModelPreferences\nimport com.example.emotiondetector.data.SecureStorage\nimport com.example.emotiondetector.data.TelemetryManager\nimport com.example.emotiondetector.domain.EmotionDetector\nimport com.example.emotiondetector.security.KeyManager\nimport com.example.emotiondetector.util.*\nimport com.google.gson.Gson\nimport com.google.gson.GsonBuilder\nimport dagger.Module\nimport dagger.Provides\nimport dagger.hilt.InstallIn\nimport dagger.hilt.android.qualifiers.ApplicationContext\nimport dagger.hilt.components.SingletonComponent\nimport net.sqlcipher.database.SQLiteDatabase\nimport okhttp3.OkHttpClient\nimport okhttp3.logging.HttpLoggingInterceptor\nimport java.util.concurrent.TimeUnit\nimport javax.inject.Singleton\n\n@Module\n@InstallIn(SingletonComponent::class)\nobject AppModule {\n \n @Provides\n @Singleton\n fun provideEmotionDetector(\n @ApplicationContext context: Context,\n modelManager: ModelManager\n ): EmotionDetector {\n return EmotionDetector(\n context = context,\n modelPath = modelManager.modelFile.absolutePath,\n threshold = 0.7f\n )\n }\n \n @Provides\n @Singleton\n fun provideModelManager(\n @ApplicationContext context: Context,\n workManager: WorkManager,\n modelPreferences: ModelPreferences\n ): ModelManager {\n return ModelManager(context, workManager, modelPreferences).apply {\n initialize()\n }\n }\n \n @Provides\n @Singleton\n fun provideModelPreferences(\n @ApplicationContext context: Context\n ): ModelPreferences {\n return ModelPreferences(context)\n }\n \n @Provides\n @Singleton\n fun provideWorkManager(\n @ApplicationContext context: Context\n ): WorkManager {\n return WorkManager.getInstance(context)\n }\n \n @Provides\n @Singleton\n fun provideKeyManager(\n @ApplicationContext context: Context\n ): KeyManager {\n return KeyManager(context).apply {\n migrateFromLegacyIfNeeded()\n }\n }\n \n @Provides\n @Singleton\n fun provideSecureStorage(\n @ApplicationContext context: Context,\n keyManager: KeyManager\n ): SecureStorage {\n // In a real app, you'd derive a key from the KeyManager\n // For simplicity, we're using a fixed key here\n val encryptionKey = SQLiteDatabase.getBytes(\"your-secure-encryption-key\".toCharArray())\n return SecureStorage(context, encryptionKey)\n }\n \n @Provides\n @Singleton\n fun provideTelemetryManager(\n @ApplicationContext context: Context,\n workManager: WorkManager,\n secureStorage: SecureStorage\n ): TelemetryManager {\n return TelemetryManager(context, workManager, secureStorage)\n }\n \n @Provides\n @Singleton\n fun provideGson(): Gson {\n return GsonBuilder()\n .setPrettyPrinting()\n .create()\n }\n \n @Provides\n @Singleton\n fun provideOkHttpClient(\n @ApplicationContext context: Context\n ): OkHttpClient {\n val loggingInterceptor = HttpLoggingInterceptor().apply {\n level = if (BuildConfig.DEBUG) {\n HttpLoggingInterceptor.Level.BODY\n } else {\n HttpLoggingInterceptor.Level.NONE\n }\n }\n \n return OkHttpClient.Builder()\n .connectTimeout(30, TimeUnit.SECONDS)\n .readTimeout(30, TimeUnit.SECONDS)\n .writeTimeout(30, TimeUnit.SECONDS)\n .addInterceptor(loggingInterceptor)\n .build()\n }\n \n @Provides\n @Singleton\n fun provideAppLogger(\n @ApplicationContext context: Context,\n fileUtils: FileUtils\n ): AppLogger {\n return AppLogger(context, fileUtils)\n }\n\n @Provides\n @Singleton\n fun providePrefs(@ApplicationContext context: Context): Prefs {\n return Prefs(context)\n }\n\n @Provides\n @Singleton\n fun provideJsonUtils(): JsonUtils {\n return JsonUtils()\n }\n \n @Provides\n @Singleton\n fun provideFileUtils(@ApplicationContext context: Context): FileUtils {\n return FileUtils(context)\n }\n \n @Provides\n @Singleton\n fun provideNetworkUtils(\n @ApplicationContext context: Context,\n fileUtils: FileUtils,\n appLogger: AppLogger\n ): NetworkUtils {\n return NetworkUtils(context, fileUtils, appLogger)\n }\n \n @Provides\n @Singleton\n fun providePermissionUtils(): PermissionUtils {\n return PermissionUtils\n }\n \n @Provides\n @Singleton\n fun provideDateTimeUtils(): DateTimeUtils {\n return DateTimeUtils\n }\n \n @Provides\n @Singleton\n fun provideImageUtils(\n @ApplicationContext context: Context,\n fileUtils: FileUtils\n ): ImageUtils {\n return ImageUtils(context, fileUtils)\n }\n}\n",
"size": 5028,
"language": "kotlin"
},
"app/src/main/java/com/example/emotiondetector/di/WorkerModule.kt": {
"content": "package com.example.emotiondetector.di\n\nimport android.content.Context\nimport androidx.work.ListenableWorker\nimport androidx.work.WorkerFactory\nimport androidx.work.WorkerParameters\nimport com.example.emotiondetector.worker.DownloadModelWorker\nimport com.example.emotiondetector.worker.ModelUpdateWorker\nimport com.example.emotiondetector.worker.UploadTelemetryWorker\nimport dagger.Binds\nimport dagger.MapKey\nimport dagger.Module\nimport dagger.hilt.InstallIn\nimport dagger.hilt.components.SingletonComponent\nimport dagger.multibindings.IntoMap\nimport kotlin.reflect.KClass\nimport javax.inject.Inject\nimport javax.inject.Provider\nimport javax.inject.Singleton\n\n/**\n * Hilt WorkerFactory to handle dependency injection in workers\n */\nclass HiltWorkerFactory @Inject constructor(\n private val workerFactories: Map, @JvmSuppressWildcards Provider>\n) : WorkerFactory() {\n \n override fun createWorker(\n appContext: Context,\n workerClassName: String,\n workerParameters: WorkerParameters\n ): ListenableWorker? {\n val foundEntry = workerFactories.entries.find { \n Class.forName(workerClassName).isAssignableFrom(it.key)\n }\n \n val factoryProvider = foundEntry?.value\n ?: throw IllegalArgumentException(\"Unknown worker class name: $workerClassName\")\n \n return factoryProvider.get().create(appContext, workerParameters)\n }\n}\n\n/**\n * Interface for creating workers with dependencies\n */\ninterface ChildWorkerFactory {\n fun create(appContext: Context, params: WorkerParameters): ListenableWorker\n}\n\n/**\n * Dagger key for worker factories\n */\n@MapKey\n@Retention(AnnotationRetention.RUNTIME)\n@Target(AnnotationTarget.FUNCTION)\nannotation class WorkerKey(val value: KClass)\n\n/**\n * Module for worker dependency injection\n */\n@Module\n@InstallIn(SingletonComponent::class)\ninterface WorkerModule {\n \n @Binds\n @Singleton\n fun bindWorkerFactory(factory: HiltWorkerFactory): WorkerFactory\n \n @Binds\n @IntoMap\n @WorkerKey(DownloadModelWorker::class)\n fun bindDownloadModelWorker(factory: DownloadModelWorker.Factory): ChildWorkerFactory\n \n @Binds\n @IntoMap\n @WorkerKey(ModelUpdateWorker::class)\n fun bindModelUpdateWorker(factory: ModelUpdateWorker.Factory): ChildWorkerFactory\n \n @Binds\n @IntoMap\n @WorkerKey(UploadTelemetryWorker::class)\n fun bindUploadTelemetryWorker(factory: UploadTelemetryWorker.Factory): ChildWorkerFactory\n}\n\n@Module\n@InstallIn(SingletonComponent::class)\nobject AppModule {\n @Provides\n @Singleton\n fun provideAppLogger(context: Context, fileUtils: FileUtils): AppLogger {\n return AppLogger(context, fileUtils)\n }\n\n @Provides\n @Singleton\n fun providePrefs(context: Context): Prefs {\n return Prefs(context)\n }\n\n @Provides\n @Singleton\n fun provideJsonUtils(): JsonUtils {\n return JsonUtils()\n }\n}\n\n/**\n * Factory for creating DownloadModelWorker instances with dependencies\n */\nclass DownloadModelWorkerFactory @Inject constructor(\n private val worker: Provider\n) : ChildWorkerFactory {\n \n override fun create(appContext: Context, params: WorkerParameters): ListenableWorker {\n return worker.get()\n }\n \n @AssistedFactory\n interface Factory : ChildWorkerFactory {\n override fun create(appContext: Context, params: WorkerParameters): DownloadModelWorker\n }\n}\n\n/**\n * Factory for creating ModelUpdateWorker instances with dependencies\n */\nclass ModelUpdateWorkerFactory @Inject constructor(\n private val worker: Provider\n) : ChildWorkerFactory {\n \n override fun create(appContext: Context, params: WorkerParameters): ListenableWorker {\n return worker.get()\n }\n \n @AssistedFactory\n interface Factory : ChildWorkerFactory {\n override fun create(appContext: Context, params: WorkerParameters): ModelUpdateWorker\n }\n}\n\n/**\n * Factory for creating UploadTelemetryWorker instances with dependencies\n */\nclass UploadTelemetryWorkerFactory @Inject constructor(\n private val worker: Provider\n) : ChildWorkerFactory {\n \n override fun create(appContext: Context, params: WorkerParameters): ListenableWorker {\n return worker.get()\n }\n \n @AssistedFactory\n interface Factory : ChildWorkerFactory {\n override fun create(appContext: Context, params: WorkerParameters): UploadTelemetryWorker\n }\n}\n",
"size": 4540,
"language": "kotlin"
},
"app/src/main/java/com/example/emotiondetector/util/JsonUtils.kt": {
"content": "package com.example.emotiondetector.util\n\nimport com.google.gson.*\nimport com.google.gson.reflect.TypeToken\nimport java.lang.reflect.Type\nimport java.util.*\nimport javax.inject.Inject\nimport javax.inject.Singleton\n\n/**\n * Utility class for JSON serialization and deserialization using Gson\n */\n@Singleton\nclass JsonUtils @Inject constructor() {\n /**\n * Custom Gson instance with custom type adapters and settings\n */\n val gson: Gson = GsonBuilder()\n .setPrettyPrinting()\n .setDateFormat(\"yyyy-MM-dd'T'HH:mm:ss.SSS'Z'\")\n .registerTypeAdapter(Date::class.java, DateDeserializer())\n .registerTypeAdapter(Date::class.java, DateSerializer())\n .create()\n \n /**\n * Convert an object to JSON string\n */\n fun toJson(obj: Any?): String = gson.toJson(obj)\n \n /**\n * Convert a JSON string to an object of the specified type\n */\n inline fun fromJson(json: String?): T? {\n return try {\n if (json.isNullOrBlank()) null else gson.fromJson(json, object : TypeToken() {}.type)\n } catch (e: Exception) {\n null\n }\n }\n \n /**\n * Convert a JSON string to a list of objects of the specified type\n */\n inline fun listFromJson(json: String?): List {\n return try {\n val type = object : TypeToken>() {}.type\n if (json.isNullOrBlank()) emptyList() else gson.fromJson(json, type) ?: emptyList()\n } catch (e: Exception) {\n emptyList()\n }\n }\n \n /**\n * Convert a JSON string to a map with string keys and values of the specified type\n */\n inline fun mapFromJson(json: String?): Map {\n return try {\n val type = object : TypeToken