From 7011337caf9a9478f9a954ef1750009074d9b70d Mon Sep 17 00:00:00 2001 From: MinchalaVenkatSunil Date: Fri, 13 Oct 2023 00:10:49 +0530 Subject: [PATCH] NOTE: Initially i have create a private repo and committed feature by feature and bug fixes and merged to the master by rasing a pull request . Here I am commiting all at once. latest screen: to display the latest timestamp data recent screen: to display all the recent timestamp data Login visibility Firebase authentication Error handling Added progress bar on Sign up or sign in Included navigation map functionality as we have more than one screen redefine folder structure for maintainability and scalability Bug fixing for auth logic partial cleanup Syntax rules Code comments or documentation Few proper naming conventions or corrections TODO: test cases Logic implementation for sorting with name and category use EncryptedSharedPreferences to store token or secure credential --- .gitignore | 15 ++ .idea/.gitignore | 3 + .idea/.name | 1 + .idea/compiler.xml | 6 + .idea/deploymentTargetDropDown.xml | 17 ++ .idea/gradle.xml | 20 ++ .idea/kotlinc.xml | 6 + .idea/misc.xml | 9 + .idea/vcs.xml | 6 + app/.gitignore | 1 + app/build.gradle | 83 ++++++++ app/google-services.json | 48 +++++ app/proguard-rules.pro | 21 ++ .../energymeter/ExampleInstrumentedTest.kt | 22 +++ app/src/main/AndroidManifest.xml | 34 ++++ .../energymeter/EnergyMeterActivity.kt | 18 ++ .../com/tuf2000m/energymeter/MyApplication.kt | 13 ++ .../energymeter/base/BaseRepository.kt | 32 +++ .../energymeter/data/model/auth/AuthResult.kt | 8 + .../data/model/auth/UserProfile.kt | 6 + .../energymeter/data/model/meterdata/Data.kt | 8 + .../data/model/meterdata/MeterData.kt | 5 + .../data/model/meterdata/TimeStamp.kt | 7 + .../data/model/recent/RecentData.kt | 10 + .../energymeter/data/remote/ApiService.kt | 13 ++ .../energymeter/data/remote/NetworkResult.kt | 7 + .../data/repository/MeterDataRepo.kt | 19 ++ .../tuf2000m/energymeter/di/NetworkModule.kt | 57 ++++++ .../tuf2000m/energymeter/utils/Constant.kt | 28 +++ .../utils/SharedPreferenceManager.kt | 31 +++ .../views/authentication/AuthFragment.kt | 122 ++++++++++++ .../views/authentication/AuthViewModel.kt | 52 +++++ .../energymeter/views/home/HomeAdapater.kt | 44 +++++ .../energymeter/views/home/HomeFragment.kt | 77 ++++++++ .../views/home/latest/LatestReadingAdapter.kt | 60 ++++++ .../home/latest/LatestReadingFragment.kt | 139 +++++++++++++ .../home/latest/LatestReadingViewModel.kt | 70 +++++++ .../recenthistory/RecentHistoryAdapter.kt | 40 ++++ .../recenthistory/RecentHistoryFragment.kt | 86 ++++++++ .../drawable-v24/ic_launcher_foreground.xml | 30 +++ .../res/drawable/ic_launcher_background.xml | 170 ++++++++++++++++ app/src/main/res/drawable/ic_shutdown.png | Bin 0 -> 2404 bytes app/src/main/res/drawable/layout_bg.xml | 7 + app/src/main/res/layout/activity_main.xml | 21 ++ app/src/main/res/layout/fragment_auth.xml | 90 +++++++++ app/src/main/res/layout/fragment_latest.xml | 114 +++++++++++ app/src/main/res/layout/fragment_recent.xml | 17 ++ app/src/main/res/layout/fragment_tabs.xml | 53 +++++ app/src/main/res/layout/item_energycell.xml | 53 +++++ .../main/res/layout/item_recycler_recents.xml | 33 ++++ .../res/mipmap-anydpi-v26/ic_launcher.xml | 6 + .../mipmap-anydpi-v26/ic_launcher_round.xml | 6 + app/src/main/res/mipmap-hdpi/ic_launcher.webp | Bin 0 -> 1404 bytes .../res/mipmap-hdpi/ic_launcher_round.webp | Bin 0 -> 2898 bytes app/src/main/res/mipmap-mdpi/ic_launcher.webp | Bin 0 -> 982 bytes .../res/mipmap-mdpi/ic_launcher_round.webp | Bin 0 -> 1772 bytes .../main/res/mipmap-xhdpi/ic_launcher.webp | Bin 0 -> 1900 bytes .../res/mipmap-xhdpi/ic_launcher_round.webp | Bin 0 -> 3918 bytes .../main/res/mipmap-xxhdpi/ic_launcher.webp | Bin 0 -> 2884 bytes .../res/mipmap-xxhdpi/ic_launcher_round.webp | Bin 0 -> 5914 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.webp | Bin 0 -> 3844 bytes .../res/mipmap-xxxhdpi/ic_launcher_round.webp | Bin 0 -> 7778 bytes app/src/main/res/navigation/nav_graph.xml | 19 ++ app/src/main/res/values-night/themes.xml | 7 + app/src/main/res/values-zh-rCN/strings.xml | 20 ++ app/src/main/res/values/colors.xml | 5 + app/src/main/res/values/strings.xml | 25 +++ app/src/main/res/values/themes.xml | 10 + app/src/main/res/xml/backup_rules.xml | 13 ++ .../main/res/xml/data_extraction_rules.xml | 19 ++ .../tuf2000m/energymeter/ExampleUnitTest.kt | 16 ++ build.gradle | 8 + gradle.properties | 24 +++ gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 59203 bytes gradle/wrapper/gradle-wrapper.properties | 6 + gradlew | 185 ++++++++++++++++++ gradlew.bat | 89 +++++++++ settings.gradle | 29 +++ 78 files changed, 2319 insertions(+) create mode 100644 .gitignore create mode 100644 .idea/.gitignore create mode 100644 .idea/.name create mode 100644 .idea/compiler.xml create mode 100644 .idea/deploymentTargetDropDown.xml create mode 100644 .idea/gradle.xml create mode 100644 .idea/kotlinc.xml create mode 100644 .idea/misc.xml create mode 100644 .idea/vcs.xml create mode 100644 app/.gitignore create mode 100644 app/build.gradle create mode 100644 app/google-services.json create mode 100644 app/proguard-rules.pro create mode 100644 app/src/androidTest/java/com/tuf2000m/energymeter/ExampleInstrumentedTest.kt create mode 100644 app/src/main/AndroidManifest.xml create mode 100644 app/src/main/java/com/tuf2000m/energymeter/EnergyMeterActivity.kt create mode 100644 app/src/main/java/com/tuf2000m/energymeter/MyApplication.kt create mode 100644 app/src/main/java/com/tuf2000m/energymeter/base/BaseRepository.kt create mode 100644 app/src/main/java/com/tuf2000m/energymeter/data/model/auth/AuthResult.kt create mode 100644 app/src/main/java/com/tuf2000m/energymeter/data/model/auth/UserProfile.kt create mode 100644 app/src/main/java/com/tuf2000m/energymeter/data/model/meterdata/Data.kt create mode 100644 app/src/main/java/com/tuf2000m/energymeter/data/model/meterdata/MeterData.kt create mode 100644 app/src/main/java/com/tuf2000m/energymeter/data/model/meterdata/TimeStamp.kt create mode 100644 app/src/main/java/com/tuf2000m/energymeter/data/model/recent/RecentData.kt create mode 100644 app/src/main/java/com/tuf2000m/energymeter/data/remote/ApiService.kt create mode 100644 app/src/main/java/com/tuf2000m/energymeter/data/remote/NetworkResult.kt create mode 100644 app/src/main/java/com/tuf2000m/energymeter/data/repository/MeterDataRepo.kt create mode 100644 app/src/main/java/com/tuf2000m/energymeter/di/NetworkModule.kt create mode 100644 app/src/main/java/com/tuf2000m/energymeter/utils/Constant.kt create mode 100644 app/src/main/java/com/tuf2000m/energymeter/utils/SharedPreferenceManager.kt create mode 100644 app/src/main/java/com/tuf2000m/energymeter/views/authentication/AuthFragment.kt create mode 100644 app/src/main/java/com/tuf2000m/energymeter/views/authentication/AuthViewModel.kt create mode 100644 app/src/main/java/com/tuf2000m/energymeter/views/home/HomeAdapater.kt create mode 100644 app/src/main/java/com/tuf2000m/energymeter/views/home/HomeFragment.kt create mode 100644 app/src/main/java/com/tuf2000m/energymeter/views/home/latest/LatestReadingAdapter.kt create mode 100644 app/src/main/java/com/tuf2000m/energymeter/views/home/latest/LatestReadingFragment.kt create mode 100644 app/src/main/java/com/tuf2000m/energymeter/views/home/latest/LatestReadingViewModel.kt create mode 100644 app/src/main/java/com/tuf2000m/energymeter/views/home/recenthistory/RecentHistoryAdapter.kt create mode 100644 app/src/main/java/com/tuf2000m/energymeter/views/home/recenthistory/RecentHistoryFragment.kt create mode 100644 app/src/main/res/drawable-v24/ic_launcher_foreground.xml create mode 100644 app/src/main/res/drawable/ic_launcher_background.xml create mode 100644 app/src/main/res/drawable/ic_shutdown.png create mode 100644 app/src/main/res/drawable/layout_bg.xml create mode 100644 app/src/main/res/layout/activity_main.xml create mode 100644 app/src/main/res/layout/fragment_auth.xml create mode 100644 app/src/main/res/layout/fragment_latest.xml create mode 100644 app/src/main/res/layout/fragment_recent.xml create mode 100644 app/src/main/res/layout/fragment_tabs.xml create mode 100644 app/src/main/res/layout/item_energycell.xml create mode 100644 app/src/main/res/layout/item_recycler_recents.xml create mode 100644 app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml create mode 100644 app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml create mode 100644 app/src/main/res/mipmap-hdpi/ic_launcher.webp create mode 100644 app/src/main/res/mipmap-hdpi/ic_launcher_round.webp create mode 100644 app/src/main/res/mipmap-mdpi/ic_launcher.webp create mode 100644 app/src/main/res/mipmap-mdpi/ic_launcher_round.webp create mode 100644 app/src/main/res/mipmap-xhdpi/ic_launcher.webp create mode 100644 app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp create mode 100644 app/src/main/res/mipmap-xxhdpi/ic_launcher.webp create mode 100644 app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp create mode 100644 app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp create mode 100644 app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp create mode 100644 app/src/main/res/navigation/nav_graph.xml create mode 100644 app/src/main/res/values-night/themes.xml create mode 100644 app/src/main/res/values-zh-rCN/strings.xml create mode 100644 app/src/main/res/values/colors.xml create mode 100644 app/src/main/res/values/strings.xml create mode 100644 app/src/main/res/values/themes.xml create mode 100644 app/src/main/res/xml/backup_rules.xml create mode 100644 app/src/main/res/xml/data_extraction_rules.xml create mode 100644 app/src/test/java/com/tuf2000m/energymeter/ExampleUnitTest.kt create mode 100644 build.gradle create mode 100644 gradle.properties create mode 100644 gradle/wrapper/gradle-wrapper.jar create mode 100644 gradle/wrapper/gradle-wrapper.properties create mode 100644 gradlew create mode 100644 gradlew.bat create mode 100644 settings.gradle diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..aa724b7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,15 @@ +*.iml +.gradle +/local.properties +/.idea/caches +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +/.idea/navEditor.xml +/.idea/assetWizardSettings.xml +.DS_Store +/build +/captures +.externalNativeBuild +.cxx +local.properties diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..26d3352 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,3 @@ +# Default ignored files +/shelf/ +/workspace.xml diff --git a/.idea/.name b/.idea/.name new file mode 100644 index 0000000..1332cf3 --- /dev/null +++ b/.idea/.name @@ -0,0 +1 @@ +Meter Reader \ No newline at end of file diff --git a/.idea/compiler.xml b/.idea/compiler.xml new file mode 100644 index 0000000..b589d56 --- /dev/null +++ b/.idea/compiler.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/deploymentTargetDropDown.xml b/.idea/deploymentTargetDropDown.xml new file mode 100644 index 0000000..99e738f --- /dev/null +++ b/.idea/deploymentTargetDropDown.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/gradle.xml b/.idea/gradle.xml new file mode 100644 index 0000000..ae388c2 --- /dev/null +++ b/.idea/gradle.xml @@ -0,0 +1,20 @@ + + + + + + + \ No newline at end of file diff --git a/.idea/kotlinc.xml b/.idea/kotlinc.xml new file mode 100644 index 0000000..2b8a50f --- /dev/null +++ b/.idea/kotlinc.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..773fe0f --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,9 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 0000000..f0d2aec --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,83 @@ +plugins { + id 'com.android.application' + id 'org.jetbrains.kotlin.android' + id 'kotlin-kapt' + id 'com.google.dagger.hilt.android' + id 'com.google.gms.google-services' +} + +android { + namespace 'com.tuf2000m.energymeter' + compileSdk 34 + + defaultConfig { + applicationId "com.tuf2000m.energymeter" + minSdk 24 + targetSdk 34 + versionCode 1 + versionName "1.0" + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + + compileOptions { + sourceCompatibility JavaVersion.VERSION_17 + targetCompatibility JavaVersion.VERSION_17 + } + + kotlinOptions { + jvmTarget = '17' + } + + buildFeatures { + dataBinding true + viewBinding true + } +} + +dependencies { + implementation 'androidx.core:core-ktx:1.12.0' + implementation 'androidx.appcompat:appcompat:1.6.1' + implementation 'com.google.android.material:material:1.9.0' + implementation 'androidx.constraintlayout:constraintlayout:2.1.4' + testImplementation 'junit:junit:4.13.2' + androidTestImplementation 'androidx.test.ext:junit:1.1.5' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' + + // Retrofit + def retrofitVersion = '2.9.0' + implementation "com.squareup.retrofit2:retrofit:$retrofitVersion" + implementation "com.squareup.retrofit2:converter-gson:$retrofitVersion" + implementation "com.squareup.okhttp3:okhttp:4.9.3" + implementation "com.squareup.okhttp3:logging-interceptor:4.9.3" + + // Hilt + def hiltVersion = '2.44' + implementation("com.google.dagger:hilt-android:$hiltVersion") + kapt "com.google.dagger:hilt-android-compiler:$hiltVersion" + + // Coroutines + def coroutineVersion = '1.5.2' + implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutineVersion" + + // Kotlin Extensions + implementation "androidx.activity:activity-ktx:1.8.0" + implementation "androidx.fragment:fragment-ktx:1.6.1" + implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.2" + implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.6.2" + implementation 'androidx.savedstate:savedstate:1.2.1' + + // Firebase + implementation(platform("com.google.firebase:firebase-bom:32.3.1")) + implementation("com.google.firebase:firebase-auth-ktx") + + // Navigation Components + implementation "androidx.navigation:navigation-fragment-ktx:2.2.1" + implementation "androidx.navigation:navigation-ui:2.7.4" +} diff --git a/app/google-services.json b/app/google-services.json new file mode 100644 index 0000000..d79bbae --- /dev/null +++ b/app/google-services.json @@ -0,0 +1,48 @@ +{ + "project_info": { + "project_number": "154914327689", + "project_id": "tuf-2000mmobileapp", + "storage_bucket": "tuf-2000mmobileapp.appspot.com" + }, + "client": [ + { + "client_info": { + "mobilesdk_app_id": "1:154914327689:android:5e7ed85d6e06c7e71d0a58", + "android_client_info": { + "package_name": "com.tuf2000m.energymeter" + } + }, + "oauth_client": [], + "api_key": [ + { + "current_key": "AIzaSyDVbXOCACRUmIahXhLIYgljK4x8YOow1Ys" + } + ], + "services": { + "appinvite_service": { + "other_platform_oauth_client": [] + } + } + }, + { + "client_info": { + "mobilesdk_app_id": "1:154914327689:android:088275fa4f98382f1d0a58", + "android_client_info": { + "package_name": "com.venkat.tuf2000m" + } + }, + "oauth_client": [], + "api_key": [ + { + "current_key": "AIzaSyDVbXOCACRUmIahXhLIYgljK4x8YOow1Ys" + } + ], + "services": { + "appinvite_service": { + "other_platform_oauth_client": [] + } + } + } + ], + "configuration_version": "1" +} \ No newline at end of file diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/app/src/androidTest/java/com/tuf2000m/energymeter/ExampleInstrumentedTest.kt b/app/src/androidTest/java/com/tuf2000m/energymeter/ExampleInstrumentedTest.kt new file mode 100644 index 0000000..d23c9ca --- /dev/null +++ b/app/src/androidTest/java/com/tuf2000m/energymeter/ExampleInstrumentedTest.kt @@ -0,0 +1,22 @@ +package com.tuf2000m.energymeter + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.platform.app.InstrumentationRegistry +import org.junit.Assert.* +import org.junit.Test +import org.junit.runner.RunWith + +/** + * Instrumented test, which will execute on an Android device. + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +@RunWith(AndroidJUnit4::class) +class ExampleInstrumentedTest { + @Test + fun useAppContext() { + // Context of the app under test. + val appContext = InstrumentationRegistry.getInstrumentation().targetContext + assertEquals("com.tuf2000m.energymeter", appContext.packageName) + } +} \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..1bb939d --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/java/com/tuf2000m/energymeter/EnergyMeterActivity.kt b/app/src/main/java/com/tuf2000m/energymeter/EnergyMeterActivity.kt new file mode 100644 index 0000000..5e43e45 --- /dev/null +++ b/app/src/main/java/com/tuf2000m/energymeter/EnergyMeterActivity.kt @@ -0,0 +1,18 @@ +package com.tuf2000m.energymeter + +import android.os.Bundle +import androidx.appcompat.app.AppCompatActivity +import com.tuf2000m.energymeter.databinding.ActivityMainBinding +import dagger.hilt.android.AndroidEntryPoint + +@AndroidEntryPoint +class EnergyMeterActivity : AppCompatActivity() { + private lateinit var binding: ActivityMainBinding + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + binding = ActivityMainBinding.inflate(layoutInflater) + setContentView(binding.root) + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/tuf2000m/energymeter/MyApplication.kt b/app/src/main/java/com/tuf2000m/energymeter/MyApplication.kt new file mode 100644 index 0000000..90da5dc --- /dev/null +++ b/app/src/main/java/com/tuf2000m/energymeter/MyApplication.kt @@ -0,0 +1,13 @@ +package com.tuf2000m.energymeter + +import android.app.Application +import com.google.firebase.FirebaseApp +import dagger.hilt.android.HiltAndroidApp + +@HiltAndroidApp +class MyApplication : Application() { + override fun onCreate() { + super.onCreate() + FirebaseApp.initializeApp(this) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tuf2000m/energymeter/base/BaseRepository.kt b/app/src/main/java/com/tuf2000m/energymeter/base/BaseRepository.kt new file mode 100644 index 0000000..227b1e7 --- /dev/null +++ b/app/src/main/java/com/tuf2000m/energymeter/base/BaseRepository.kt @@ -0,0 +1,32 @@ +package com.tuf2000m.energymeter.base + +import com.tuf2000m.energymeter.data.remote.NetworkResult +import kotlinx.coroutines.flow.flow +import retrofit2.HttpException +import java.io.IOException + +abstract class BaseRepository { + + /** + * Executes a safe API call using Kotlin Flow. + * + * @param apiCall The API call to be executed. + * @return Flow emitting [NetworkResult] with the API response or error. + */ + suspend fun safeApiCall(apiCall: suspend () -> T) = flow { + try { + emit(NetworkResult.Loading(true)) + emit(NetworkResult.Success(apiCall.invoke())) + } catch (e: HttpException) { + emit( + NetworkResult.Failure( + e.localizedMessage ?: "Something went wrong, please try again later." + ) + ) + } catch (e: IOException) { + emit(NetworkResult.Failure("No Internet. Please check your internet connection.")) + } catch (e: Exception) { + emit(NetworkResult.Failure(e.localizedMessage ?: "Error")) + } + } +} diff --git a/app/src/main/java/com/tuf2000m/energymeter/data/model/auth/AuthResult.kt b/app/src/main/java/com/tuf2000m/energymeter/data/model/auth/AuthResult.kt new file mode 100644 index 0000000..faba82e --- /dev/null +++ b/app/src/main/java/com/tuf2000m/energymeter/data/model/auth/AuthResult.kt @@ -0,0 +1,8 @@ +package com.tuf2000m.energymeter.data.model.auth + +import com.google.firebase.auth.FirebaseUser + +sealed class AuthResult { + data class Success(val user: FirebaseUser?) : AuthResult() + data class Failure(val exception: Exception?) : AuthResult() +} diff --git a/app/src/main/java/com/tuf2000m/energymeter/data/model/auth/UserProfile.kt b/app/src/main/java/com/tuf2000m/energymeter/data/model/auth/UserProfile.kt new file mode 100644 index 0000000..1b3bc0a --- /dev/null +++ b/app/src/main/java/com/tuf2000m/energymeter/data/model/auth/UserProfile.kt @@ -0,0 +1,6 @@ +package com.tuf2000m.energymeter.data.model.auth + +data class UserProfile( + val email: String?, + val displayName: String? +) diff --git a/app/src/main/java/com/tuf2000m/energymeter/data/model/meterdata/Data.kt b/app/src/main/java/com/tuf2000m/energymeter/data/model/meterdata/Data.kt new file mode 100644 index 0000000..fd70e12 --- /dev/null +++ b/app/src/main/java/com/tuf2000m/energymeter/data/model/meterdata/Data.kt @@ -0,0 +1,8 @@ +package com.tuf2000m.energymeter.data.model.meterdata + +data class Data( + val category: String, + val unit: String, + val value: Double, + val variableName: String +) \ No newline at end of file diff --git a/app/src/main/java/com/tuf2000m/energymeter/data/model/meterdata/MeterData.kt b/app/src/main/java/com/tuf2000m/energymeter/data/model/meterdata/MeterData.kt new file mode 100644 index 0000000..fd600ca --- /dev/null +++ b/app/src/main/java/com/tuf2000m/energymeter/data/model/meterdata/MeterData.kt @@ -0,0 +1,5 @@ +package com.tuf2000m.energymeter.data.model.meterdata + +data class MeterData( + val timeStamp: List +) \ No newline at end of file diff --git a/app/src/main/java/com/tuf2000m/energymeter/data/model/meterdata/TimeStamp.kt b/app/src/main/java/com/tuf2000m/energymeter/data/model/meterdata/TimeStamp.kt new file mode 100644 index 0000000..f067713 --- /dev/null +++ b/app/src/main/java/com/tuf2000m/energymeter/data/model/meterdata/TimeStamp.kt @@ -0,0 +1,7 @@ +package com.tuf2000m.energymeter.data.model.meterdata + +data class TimeStamp( + val data: List, + val guid: String, + val timeStamp: String +) \ No newline at end of file diff --git a/app/src/main/java/com/tuf2000m/energymeter/data/model/recent/RecentData.kt b/app/src/main/java/com/tuf2000m/energymeter/data/model/recent/RecentData.kt new file mode 100644 index 0000000..5e57497 --- /dev/null +++ b/app/src/main/java/com/tuf2000m/energymeter/data/model/recent/RecentData.kt @@ -0,0 +1,10 @@ +package com.tuf2000m.energymeter.data.model.recent + +data class RecentData( + val recentList: List +){ + data class RecentList( + val guid: String, + val timeStamp: String + ) +} \ No newline at end of file diff --git a/app/src/main/java/com/tuf2000m/energymeter/data/remote/ApiService.kt b/app/src/main/java/com/tuf2000m/energymeter/data/remote/ApiService.kt new file mode 100644 index 0000000..0aa1472 --- /dev/null +++ b/app/src/main/java/com/tuf2000m/energymeter/data/remote/ApiService.kt @@ -0,0 +1,13 @@ +package com.tuf2000m.energymeter.data.remote + +import com.tuf2000m.energymeter.data.model.meterdata.MeterData +import com.tuf2000m.energymeter.data.model.recent.RecentData +import retrofit2.http.GET + +interface ApiService { + @GET("meterData") + suspend fun timeStamps(): MeterData + + @GET("recentData") + suspend fun recent(): RecentData +} \ No newline at end of file diff --git a/app/src/main/java/com/tuf2000m/energymeter/data/remote/NetworkResult.kt b/app/src/main/java/com/tuf2000m/energymeter/data/remote/NetworkResult.kt new file mode 100644 index 0000000..58a19f8 --- /dev/null +++ b/app/src/main/java/com/tuf2000m/energymeter/data/remote/NetworkResult.kt @@ -0,0 +1,7 @@ +package com.tuf2000m.energymeter.data.remote + +sealed class NetworkResult { + data class Loading(val isLoading: Boolean) : NetworkResult() + data class Success(val data: T,val apiName: String = "") : NetworkResult() + data class Failure(val errorMessage: String) : NetworkResult() +} \ No newline at end of file diff --git a/app/src/main/java/com/tuf2000m/energymeter/data/repository/MeterDataRepo.kt b/app/src/main/java/com/tuf2000m/energymeter/data/repository/MeterDataRepo.kt new file mode 100644 index 0000000..3ae087d --- /dev/null +++ b/app/src/main/java/com/tuf2000m/energymeter/data/repository/MeterDataRepo.kt @@ -0,0 +1,19 @@ +package com.tuf2000m.energymeter.data.repository + +import com.tuf2000m.energymeter.base.BaseRepository +import com.tuf2000m.energymeter.data.remote.ApiService +import javax.inject.Inject + +class MeterDataRepo @Inject constructor(private val apiService: ApiService) : BaseRepository() { + + suspend fun getTimeStamps() = safeApiCall { + // TODO: Pull individual timestamps when we have the right API in the backend service. + // For now pulling all three mock responses as time stamps from a Json free service. + apiService.timeStamps() + } + + suspend fun getRecents() = safeApiCall { + apiService.recent() + } +} + diff --git a/app/src/main/java/com/tuf2000m/energymeter/di/NetworkModule.kt b/app/src/main/java/com/tuf2000m/energymeter/di/NetworkModule.kt new file mode 100644 index 0000000..0cbb97f --- /dev/null +++ b/app/src/main/java/com/tuf2000m/energymeter/di/NetworkModule.kt @@ -0,0 +1,57 @@ +package com.tuf2000m.energymeter.di + +import com.google.firebase.auth.FirebaseAuth +import com.tuf2000m.energymeter.BuildConfig +import com.tuf2000m.energymeter.data.remote.ApiService +import com.tuf2000m.energymeter.utils.Constant +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import okhttp3.OkHttpClient +import okhttp3.logging.HttpLoggingInterceptor +import retrofit2.Retrofit +import retrofit2.converter.gson.GsonConverterFactory +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +object NetworkModule { + + @Singleton + @Provides + fun provideLoggingInterceptor(): HttpLoggingInterceptor { + return HttpLoggingInterceptor().apply { + this.level = HttpLoggingInterceptor.Level.BODY + } + } + + @Singleton + @Provides + fun provideOkHttp(): OkHttpClient { + return OkHttpClient.Builder().apply { + if (BuildConfig.DEBUG) { + this.addInterceptor(HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY)) + } + }.build() + } + + + @Provides + fun provideRetrofit(okHttpClient: OkHttpClient): Retrofit { + return Retrofit.Builder().baseUrl(Constant.API.BASE) + .addConverterFactory(GsonConverterFactory.create()).client(okHttpClient).build() + } + + @Provides + fun provideApiClient(retrofit: Retrofit): ApiService { + return retrofit.create(ApiService::class.java) + } + + @Singleton + @Provides + fun provideFirebaseAuth(): FirebaseAuth { + return FirebaseAuth.getInstance() + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/tuf2000m/energymeter/utils/Constant.kt b/app/src/main/java/com/tuf2000m/energymeter/utils/Constant.kt new file mode 100644 index 0000000..818eeb9 --- /dev/null +++ b/app/src/main/java/com/tuf2000m/energymeter/utils/Constant.kt @@ -0,0 +1,28 @@ +package com.tuf2000m.energymeter.utils + +import java.text.SimpleDateFormat +import java.util.TimeZone + +object Constant { + object API { + const val BASE = "https://my-json-server.typicode.com/MinchalaVenkatSunil/demo/" + } + + + object TimeFormatter { + fun convertUTCFormat(date: String): String? { + val inputFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'") + inputFormat.timeZone = TimeZone.getTimeZone("UTC") + val outputFormat = SimpleDateFormat("yyyy-MM-dd hh:mm a") + // TODO: Need tweaks + outputFormat.timeZone = TimeZone.getDefault() + try { + val parserDate = inputFormat.parse(date) + return outputFormat.format(parserDate) + } catch (e: Exception) { + e.printStackTrace() + } + return "" + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tuf2000m/energymeter/utils/SharedPreferenceManager.kt b/app/src/main/java/com/tuf2000m/energymeter/utils/SharedPreferenceManager.kt new file mode 100644 index 0000000..133e5ca --- /dev/null +++ b/app/src/main/java/com/tuf2000m/energymeter/utils/SharedPreferenceManager.kt @@ -0,0 +1,31 @@ +package com.tuf2000m.energymeter.utils + +import android.content.Context +import android.content.SharedPreferences +import com.google.gson.Gson +import com.tuf2000m.energymeter.data.model.auth.UserProfile +import dagger.hilt.android.qualifiers.ApplicationContext +import javax.inject.Inject + +class SharedPreferenceManager @Inject constructor( + @ApplicationContext val context: Context +) { + + private val prefName = "user-pref" + private val sharedPref: SharedPreferences = + context.getSharedPreferences(prefName, Context.MODE_PRIVATE) + + fun saveUSer(user: UserProfile) { + sharedPref.edit().putString("user", Gson().toJson(user)).apply() + } + + fun getUser(): UserProfile? { + val data = sharedPref.getString("user", null) ?: return null + return Gson().fromJson(data, UserProfile::class.java) + } + + fun clearData() { + context.getSharedPreferences(prefName, Context.MODE_PRIVATE).edit().clear().apply() + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/tuf2000m/energymeter/views/authentication/AuthFragment.kt b/app/src/main/java/com/tuf2000m/energymeter/views/authentication/AuthFragment.kt new file mode 100644 index 0000000..1f171a8 --- /dev/null +++ b/app/src/main/java/com/tuf2000m/energymeter/views/authentication/AuthFragment.kt @@ -0,0 +1,122 @@ +package com.tuf2000m.energymeter.views.authentication + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.Toast +import androidx.fragment.app.Fragment +import androidx.fragment.app.viewModels +import androidx.navigation.fragment.findNavController +import com.tuf2000m.energymeter.R +import com.tuf2000m.energymeter.data.model.auth.UserProfile +import com.tuf2000m.energymeter.data.model.auth.AuthResult +import com.tuf2000m.energymeter.databinding.FragmentAuthBinding +import com.tuf2000m.energymeter.utils.SharedPreferenceManager +import dagger.hilt.android.AndroidEntryPoint +import javax.inject.Inject + +@AndroidEntryPoint +class AuthFragment : Fragment() { + + + // Inject ViewModel + private val viewModel: AuthViewModel by viewModels() + + private var _binding: FragmentAuthBinding? = null + private val binding get() = _binding!! + private var isLogin = true + + @Inject + lateinit var sharedPreferences: SharedPreferenceManager + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? + ): View { + _binding = FragmentAuthBinding.inflate(inflater, container, false) + return binding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + binding.buttonLogin.setOnClickListener { + val email = binding.editTextEmail.text.toString() + val password = binding.editTextPassword.text.toString() + + if (email.isNotEmpty() && password.isNotEmpty()) { + binding.buttonLogin.visibility = View.INVISIBLE + binding.pb.visibility = View.VISIBLE + + val authLiveData = if (isLogin) { + viewModel.signIn(email, password) + } else { + viewModel.createAccount(email, password) + } + + authLiveData.observe(viewLifecycleOwner) { authResult -> + // Handle authentication result + when (authResult) { + is AuthResult.Success -> { + sharedPreferences.saveUSer( + UserProfile( + authResult.user?.email, + authResult.user?.displayName + ) + ) + navigateToHome() + } + + is AuthResult.Failure -> { + showHideViews() + Toast.makeText( + requireContext(), + authResult.exception?.message, + Toast.LENGTH_SHORT + ).show() + } + } + } + } + } + + binding.tvSignup.setOnClickListener { + viewModel.toggleLoginSignupMode() + } + + viewModel.isLoginMode.observe(viewLifecycleOwner) { isSigning -> + isLogin = isSigning + setLoginSignupView(isSigning) + } + } + + private fun showHideViews() { + binding.buttonLogin.visibility = View.VISIBLE + binding.pb.visibility = View.GONE + } + + private fun navigateToHome() { + findNavController().navigate(R.id.action_authFragment_to_homeFragment) + binding.editTextEmail.text.clear() + binding.editTextPassword.text.clear() + } + + private fun setLoginSignupView(isSigning: Boolean) { + if (isSigning) { + binding.buttonLogin.text = getString(R.string.login) + binding.tvSignup.text = getString(R.string.dha) + } else { + binding.buttonLogin.text = getString(R.string.signup) + binding.tvSignup.text = getString(R.string.btl) + } + } + + override fun onResume() { + super.onResume() + validateUser() + } + + private fun validateUser() { + if (sharedPreferences.getUser() != null) + navigateToHome() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tuf2000m/energymeter/views/authentication/AuthViewModel.kt b/app/src/main/java/com/tuf2000m/energymeter/views/authentication/AuthViewModel.kt new file mode 100644 index 0000000..e549e1d --- /dev/null +++ b/app/src/main/java/com/tuf2000m/energymeter/views/authentication/AuthViewModel.kt @@ -0,0 +1,52 @@ +package com.tuf2000m.energymeter.views.authentication + +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import com.google.firebase.auth.FirebaseAuth +import com.tuf2000m.energymeter.data.model.auth.AuthResult +import dagger.hilt.android.lifecycle.HiltViewModel +import javax.inject.Inject + +@HiltViewModel +class AuthViewModel @Inject constructor( + private val auth: FirebaseAuth +) : ViewModel() { + + private val _isLoginMode = MutableLiveData(true) + val isLoginMode: LiveData get() = _isLoginMode + + // Function to handle account creation + fun createAccount(email: String, password: String): LiveData { + val result = MutableLiveData() + auth.createUserWithEmailAndPassword(email, password) + .addOnCompleteListener { task -> + if (task.isSuccessful) { + result.value = AuthResult.Success(auth.currentUser) + } else { + result.value = AuthResult.Failure(task.exception) + } + } + return result + } + + // Function to handle sign in + fun signIn(email: String, password: String): LiveData { + val result = MutableLiveData() + auth.signInWithEmailAndPassword(email, password) + .addOnCompleteListener { task -> + if (task.isSuccessful) { + result.value = AuthResult.Success(auth.currentUser) + } else { + result.value = AuthResult.Failure(task.exception) + } + } + return result + + } + + fun toggleLoginSignupMode() { + _isLoginMode.value = !_isLoginMode.value!! + } + +} diff --git a/app/src/main/java/com/tuf2000m/energymeter/views/home/HomeAdapater.kt b/app/src/main/java/com/tuf2000m/energymeter/views/home/HomeAdapater.kt new file mode 100644 index 0000000..cdcbff4 --- /dev/null +++ b/app/src/main/java/com/tuf2000m/energymeter/views/home/HomeAdapater.kt @@ -0,0 +1,44 @@ +package com.tuf2000m.energymeter.views.home + +import androidx.fragment.app.Fragment +import androidx.fragment.app.FragmentManager +import androidx.lifecycle.Lifecycle +import androidx.viewpager2.adapter.FragmentStateAdapter +import com.tuf2000m.energymeter.views.home.latest.LatestReadingFragment +import com.tuf2000m.energymeter.views.home.recenthistory.RecentHistoryFragment + + +/** + * The ViewPagerAdapter for managing fragments in a ViewPager. + * + * @param fragmentManager The fragment manager. + * @param lifecycle The lifecycle. + */ + +class ViewPagerAdapter(fragmentManager: FragmentManager, lifecycle: Lifecycle) : + FragmentStateAdapter(fragmentManager, lifecycle) { + + private val numOfTabs = 2 + + /** + * Get the number of tabs. + * + * @return The number of tabs. + */ + override fun getItemCount(): Int { + return numOfTabs + } + + /** + * Create a fragment based on the position. + * + * @param position The position of the fragment. + * @return The corresponding fragment. + */ + override fun createFragment(position: Int): Fragment { + return when (position) { + 0 -> LatestReadingFragment() + else -> RecentHistoryFragment() + } + } +} diff --git a/app/src/main/java/com/tuf2000m/energymeter/views/home/HomeFragment.kt b/app/src/main/java/com/tuf2000m/energymeter/views/home/HomeFragment.kt new file mode 100644 index 0000000..9bb30de --- /dev/null +++ b/app/src/main/java/com/tuf2000m/energymeter/views/home/HomeFragment.kt @@ -0,0 +1,77 @@ +package com.tuf2000m.energymeter.views.home + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.Fragment +import androidx.navigation.fragment.findNavController +import com.google.android.material.tabs.TabLayoutMediator +import com.tuf2000m.energymeter.R +import com.tuf2000m.energymeter.databinding.FragmentTabsBinding +import com.tuf2000m.energymeter.utils.SharedPreferenceManager +import dagger.hilt.android.AndroidEntryPoint +import javax.inject.Inject + +@AndroidEntryPoint +class HomeFragment : Fragment() { + private var _binding: FragmentTabsBinding? = null + private val binding get() = _binding!! + + @Inject + lateinit var sharedPreferences: SharedPreferenceManager + private lateinit var animalsArray : Array + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + _binding = FragmentTabsBinding.inflate(inflater, container, false) + return binding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + animalsArray = arrayOf( + getString(R.string.latestReading), + getString(R.string.recentHistory) + ) + + setTabs() + setupLogoutButton() + } + + /** + * Sets up the tabs in the UI using a ViewPager and TabLayout. + */ + private fun setTabs() { + val viewPager = binding.viewPager + val tabLayout = binding.tabLayout + val adapter = ViewPagerAdapter(childFragmentManager, lifecycle) + + viewPager.adapter = adapter + // Attach the TabLayout with the ViewPager + TabLayoutMediator(tabLayout, viewPager) { tab, position -> + tab.text = animalsArray[position] + }.attach() + } + + /** + * Sets up the logout button to handle logout logic. + */ + private fun setupLogoutButton() { + // Handle logout button click + binding.imageLogout.setOnClickListener { + // Clear data and navigate back to previous screen + sharedPreferences.clearData() + findNavController().popBackStack() + } + } + + override fun onDestroyView() { + super.onDestroyView() + _binding = null + } +} diff --git a/app/src/main/java/com/tuf2000m/energymeter/views/home/latest/LatestReadingAdapter.kt b/app/src/main/java/com/tuf2000m/energymeter/views/home/latest/LatestReadingAdapter.kt new file mode 100644 index 0000000..8f4cda6 --- /dev/null +++ b/app/src/main/java/com/tuf2000m/energymeter/views/home/latest/LatestReadingAdapter.kt @@ -0,0 +1,60 @@ +package com.tuf2000m.energymeter.views.home.latest + +import android.annotation.SuppressLint +import android.util.Log +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView +import com.tuf2000m.energymeter.data.model.meterdata.Data +import com.tuf2000m.energymeter.databinding.ItemEnergycellBinding +import com.tuf2000m.energymeter.views.home.recenthistory.OnItemClickListener + + +/** + * Adapter for the home screen RecyclerView displaying energy data. + * + * @param items List of energy data to be displayed. + * @param listener Item click listener. + */ +class LatestReadingAdapter( + private var items: List, + private val listener: OnItemClickListener +) : RecyclerView.Adapter() { + + /** + * ViewHolder for the energy data item view. + * + * @param viewHolder Item's binding. + */ + class ViewHolder(val viewHolder: ItemEnergycellBinding) : + RecyclerView.ViewHolder(viewHolder.root) + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { + // Inflate the item layout and create a ViewHolder for it + val binding = + ItemEnergycellBinding.inflate(LayoutInflater.from(parent.context), parent, false) + return ViewHolder(binding) + } + + override fun onBindViewHolder(holder: ViewHolder, position: Int) { + // Bind data to the item view + val item = items[position] + holder.viewHolder.tvVariable.text = item.variableName + holder.viewHolder.tvVariablevalue.text = item.value.toString() + holder.viewHolder.tvVariablcategory.text = item.category + holder.viewHolder.tvVariableunit.text = item.unit + // Notify the click listener about item click + listener.onItemClick(position) + } + + override fun getItemCount(): Int { + return items.size + } + + @SuppressLint("NotifyDataSetChanged") + fun setContentData(data: List) { + items = data + Log.d("TAG", "set content DataSize: ${items.size}") + notifyDataSetChanged() + } +} diff --git a/app/src/main/java/com/tuf2000m/energymeter/views/home/latest/LatestReadingFragment.kt b/app/src/main/java/com/tuf2000m/energymeter/views/home/latest/LatestReadingFragment.kt new file mode 100644 index 0000000..e830b7e --- /dev/null +++ b/app/src/main/java/com/tuf2000m/energymeter/views/home/latest/LatestReadingFragment.kt @@ -0,0 +1,139 @@ +package com.tuf2000m.energymeter.views.home.latest + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.Toast +import androidx.core.widget.doOnTextChanged +import androidx.fragment.app.Fragment +import androidx.fragment.app.activityViewModels +import com.tuf2000m.energymeter.data.remote.NetworkResult +import com.tuf2000m.energymeter.data.model.meterdata.TimeStamp +import com.tuf2000m.energymeter.databinding.FragmentLatestBinding +import com.tuf2000m.energymeter.utils.Constant +import com.tuf2000m.energymeter.views.home.recenthistory.OnItemClickListener +import dagger.hilt.android.AndroidEntryPoint + +@AndroidEntryPoint +class LatestReadingFragment : Fragment() { + private var _binding: FragmentLatestBinding? = null + private val binding get() = _binding!! + private val viewModel: LatestReadingViewModel by activityViewModels() + private var currentIndex = 0 + private var timeStamps = mutableListOf() + private lateinit var latestReadingAdapter: LatestReadingAdapter + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + _binding = FragmentLatestBinding.inflate(inflater, container, false) + return binding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + setupObservers() + setupUI() + } + + override fun onResume() { + super.onResume() + viewModel.getTimeStamps() + } + + /** + * Sets up UI event handling for buttons and input fields. + */ + private fun setupUI() { + binding.imageView.setOnClickListener { + handleNextButtonClick() + } + + binding.appCompatEditText.doOnTextChanged { text, _, _, _ -> + handleSearchTextChange(text) + } + } + + /** + * Handles the logic when the user clicks the "Next" button to display the next set of data. + */ + private fun handleNextButtonClick() { + if (currentIndex < timeStamps.size - 1) { + currentIndex += 1 + binding.tvTimespam.text = + Constant.TimeFormatter.convertUTCFormat(timeStamps[currentIndex].timeStamp) + latestReadingAdapter.setContentData(timeStamps[currentIndex].data) + + } else { + Toast.makeText(context, "No More Records", Toast.LENGTH_SHORT).show() + } + } + + /** + * Handles changes in the search text input. + * + * If the search text is blank or null, it resets the current index and displays the initial data. + * If there's a non-blank search text, it triggers a search using the ViewModel. + * + * @param text The search text input. + */ + private fun handleSearchTextChange(text: CharSequence?) { + if (text.isNullOrBlank()) { + currentIndex = 0 + latestReadingAdapter.setContentData(timeStamps[currentIndex].data) + } else { + viewModel.searchData(timeStamps, text.toString()) + } + } + + /** + * Sets up observers for LiveData to update UI based on ViewModel updates. + */ + private fun setupObservers() { + viewModel.meterData.observe(viewLifecycleOwner) { result -> + when (result) { + is NetworkResult.Failure -> { + Toast.makeText(requireContext(), result.errorMessage, Toast.LENGTH_SHORT).show() + } + + is NetworkResult.Loading -> { + // Handle loading here + } + + is NetworkResult.Success -> { + result.data.let { + timeStamps.addAll(it.timeStamp) + setRecyclerData() + } + } + } + } + + viewModel.searchData.observe(viewLifecycleOwner) { + it?.let { + latestReadingAdapter.setContentData(it) + } + } + } + + private fun setRecyclerData() { + if (timeStamps.isNotEmpty()) { + binding.tvTimespam.text = + Constant.TimeFormatter.convertUTCFormat(timeStamps[currentIndex].timeStamp) + latestReadingAdapter = LatestReadingAdapter(timeStamps[currentIndex].data, object : OnItemClickListener { + override fun onItemClick(position: Int) { + // Handle item click + } + }) + binding.rvLatest.adapter = latestReadingAdapter + } + } + + override fun onDestroyView() { + super.onDestroyView() + _binding = null + } +} diff --git a/app/src/main/java/com/tuf2000m/energymeter/views/home/latest/LatestReadingViewModel.kt b/app/src/main/java/com/tuf2000m/energymeter/views/home/latest/LatestReadingViewModel.kt new file mode 100644 index 0000000..d653021 --- /dev/null +++ b/app/src/main/java/com/tuf2000m/energymeter/views/home/latest/LatestReadingViewModel.kt @@ -0,0 +1,70 @@ +package com.tuf2000m.energymeter.views.home.latest + +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.tuf2000m.energymeter.data.model.meterdata.Data +import com.tuf2000m.energymeter.data.model.meterdata.MeterData +import com.tuf2000m.energymeter.data.model.meterdata.TimeStamp +import com.tuf2000m.energymeter.data.model.recent.RecentData +import com.tuf2000m.energymeter.data.remote.NetworkResult +import com.tuf2000m.energymeter.data.repository.MeterDataRepo +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.launch +import javax.inject.Inject + +@HiltViewModel +class LatestReadingViewModel @Inject constructor( + private val meterDataRepo: MeterDataRepo, +) : ViewModel() { + + // LiveData to hold meter data + private var _meterData = MutableLiveData>() + val meterData: LiveData> = _meterData + + // LiveData to hold recent data + private var _recentData = MutableLiveData>() + val recentData: LiveData> = _recentData + + // LiveData to hold search data results + private var _searchData = MutableLiveData>() + val searchData: LiveData> = _searchData + + /** + * Fetches timestamps for meter data. + */ + fun getTimeStamps() { + viewModelScope.launch { + meterDataRepo.getTimeStamps().collect { + _meterData.postValue(it) + } + } + } + + /** + * Fetches recent data. + */ + fun getRecent() { + viewModelScope.launch { + meterDataRepo.getRecents().collect { + _recentData.postValue(it) + } + } + } + + /** + * Searches for data based on the given query within provided timestamps. + * + * @param timeStamps List of timestamps to search in. + * @param query The query to search for. + */ + fun searchData(timeStamps: List, query: CharSequence?) { + timeStamps.map { timeDto -> + val dataList = timeDto.data.filter { dataDto -> + dataDto.variableName.lowercase().contains(query.toString().lowercase()) + } + _searchData.postValue(dataList) + } + } +} diff --git a/app/src/main/java/com/tuf2000m/energymeter/views/home/recenthistory/RecentHistoryAdapter.kt b/app/src/main/java/com/tuf2000m/energymeter/views/home/recenthistory/RecentHistoryAdapter.kt new file mode 100644 index 0000000..5f26e69 --- /dev/null +++ b/app/src/main/java/com/tuf2000m/energymeter/views/home/recenthistory/RecentHistoryAdapter.kt @@ -0,0 +1,40 @@ +package com.tuf2000m.energymeter.views.home.recenthistory + +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView +import com.tuf2000m.energymeter.data.model.recent.RecentData +import com.tuf2000m.energymeter.databinding.ItemRecyclerRecentsBinding + +interface OnItemClickListener { + fun onItemClick( + position: Int + ) +} + +class RecentAdapter( + private val items: List, + private val listener: OnItemClickListener +) : RecyclerView.Adapter() { + + class ViewHolder(val viewHolder: ItemRecyclerRecentsBinding) : + RecyclerView.ViewHolder(viewHolder.root) + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { + val binding = + ItemRecyclerRecentsBinding.inflate(LayoutInflater.from(parent.context), parent, false) + return ViewHolder(binding) + } + + override fun onBindViewHolder(holder: ViewHolder, position: Int) { + listener.onItemClick(position) + holder.viewHolder.tvRecent.text = items[position].timeStamp + + + } + + + override fun getItemCount(): Int { + return items.size + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tuf2000m/energymeter/views/home/recenthistory/RecentHistoryFragment.kt b/app/src/main/java/com/tuf2000m/energymeter/views/home/recenthistory/RecentHistoryFragment.kt new file mode 100644 index 0000000..74b2095 --- /dev/null +++ b/app/src/main/java/com/tuf2000m/energymeter/views/home/recenthistory/RecentHistoryFragment.kt @@ -0,0 +1,86 @@ +package com.tuf2000m.energymeter.views.home.recenthistory + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.Fragment +import androidx.fragment.app.activityViewModels +import com.tuf2000m.energymeter.data.model.recent.RecentData +import com.tuf2000m.energymeter.data.remote.NetworkResult +import com.tuf2000m.energymeter.databinding.FragmentRecentBinding +import com.tuf2000m.energymeter.views.home.latest.LatestReadingViewModel +import dagger.hilt.android.AndroidEntryPoint + +@AndroidEntryPoint +class RecentHistoryFragment : Fragment() { + + private var _binding: FragmentRecentBinding? = null + private val binding get() = _binding!! + private val viewModel: LatestReadingViewModel by activityViewModels() + private lateinit var recentAdapter: RecentAdapter + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + _binding = FragmentRecentBinding.inflate(inflater, container, false) + return binding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + // Observe recent data to update UI accordingly + observeRecentData() + } + + /** + * Observes recent data and updates the UI accordingly. + */ + private fun observeRecentData() { + viewModel.recentData.observe(viewLifecycleOwner) { result -> + when (result) { + // Handle network failure and loading states if needed... + + is NetworkResult.Success -> { + // Set recycler data with the recent list from the result + setRecyclerData(result.data.recentList) + } + + else -> { /* Handle other cases if needed */ + } + } + } + } + + override fun onResume() { + super.onResume() + + // Request recent data from the ViewModel + viewModel.getRecent() + } + + /** + * Sets the recycler data based on the provided recent list. + * + * @param recentDataList The list of recent items. + */ + private fun setRecyclerData(recentDataList: List) { + if (recentDataList.isNotEmpty()) { + recentAdapter = RecentAdapter(recentDataList, object : OnItemClickListener { + override fun onItemClick(position: Int) { + // Handle item click if needed + } + }) + binding.rvRecent.adapter = recentAdapter + } + } + + override fun onDestroyView() { + super.onDestroyView() + + // Clean up the binding instance + _binding = null + } +} diff --git a/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml new file mode 100644 index 0000000..2b068d1 --- /dev/null +++ b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 0000000..07d5da9 --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_shutdown.png b/app/src/main/res/drawable/ic_shutdown.png new file mode 100644 index 0000000000000000000000000000000000000000..822d72533f4f7330259ae386b197bc1f96e40f0a GIT binary patch literal 2404 zcmV-q37htbP)1PEVPh*n*NyUz z)v;>Fij5iq)E5FBA03?*+nFk(ZKrBmt3{v{X{+rhb~>G+Nze=^c~qgqfsQlQ`Y0uk zggjw)@BaG7CS-43n`F-}wcnXc_MCfu-}61cd(OS*{LZ;Tn$fxR(iud;OAzLP%mpqG zWr_-ufr7qbU81zB&<1)CkpslEnW+0#Vcoke>C`9bAngv8E^-aa1icCUL1-4$=Xhg)}%N!K879NW& zm!_sjva(4Xpd}?)-IL586|S)(FJpME;8^U&WKI}$2q;MRB=g+ou<-!bb`_b;$%ezI z4(JR89#^>Cs;o>xxTQN3cx?0{BcQW<(T%9QY+qHzp^Ds4R8#-Ta2JPlKzpcUwu5N` zCZ$zc=Fsl?WZ`i0gdsyfK^%3q@c+|zfXPloHx2hZ7~+7=D;8B|Y=&n{{4Wy_g5BkT zTZTwTOthDkO%ag|nb70Ayqx^))#Pul#&=nHCaOf0AGKat;_2BQ2gFx(OwWXv?A7-p zdHG0QKH019%S4p`GqYmOJ&7479dKyr{Cv>6Go?hP46K}G>a*T6t>pWFCrm9nc*tT?EWKWr{M7;;vv|0rWY*H7rY4 zP3GM5@K;pfEG!+fjZ<<7{)#Hhx#y)z7C5pDzz5J5y|oJw{5MtLue<|%K2W9ip>??R zyGPe`$zpPU_>;K(H>(J5dWP^zTS3*TJnd_<>0Em04C0|{dk{nR+V9|Bc~89Y03!Gn z29lck76w52CTu<*{#C2VzV`tT%M&K$;*Ma+X@*F636}Q<{44J!>#JY$X1XrXx9gJH zMt9eN>w5CCuDt<&)txpa0tm)gV&XeC_TXE5IsTg}JbBvQM(;ywargc$slB_miQb3T zq3vy+eE&CY#rKsZHl-qHu>s~-Bh1Ysd-eUEJU!Av@4YK<8~1qgJ3AA8cX+P34e!vq zb`^T`NCLT#z2*TVC)bu-6y_L^3v3bkuD%YLp0Gs_jnMnO`-pw|@sP6N&A)_L3|yD+ zre}s+kA3_R$2Y789SHZ!^qKgszRs3hfH~3;D*dORUr3osguJb|lY>xP?8remLdLQlo@Da!>A{Cl-@h=b zjdmOxwebMqmtTzcl>U0oJPUK4ol&bopo{_LtBq}$t?ys%v5ifBGzg}GBM$Z0sV&+ zvZLOkJm&0KHfRBmv%PO=&jcwo95lc|8%(heyfikat%JD+*ELZH95g8VZIHS3Z$rXZ zHRsI6nYSQiUgx3(m|5ozWJJQa4R5E+3!q4|A00(EA6;|NH(?3F6{*zFX;IF!qob?-!nP-e_*k63+8RV?r5D$PPI86^9;>6E> zY)d9!wn0K9s52DU2V7)@`uw@%J^37D=XfuK!$fNSio5Gg+yl*MTPqOXByr{z|2Jv^5%y|L>0* z4g+m#<@h5H;_lyTOBy1qEvRmI5)w8>x}4+B0@h|2?*6^>+;uzdf##75`Fx``-hF?6 zoWo+{fulWc&-48W&y%S$v}irZBQ|S81m9&L{44IpoO#xmO!V*}!Y^(n^5^ZIh>#7a z=)*DV~q7EQoXQLS)+XlN+Sk)`}i!!F{ih=&rYL z-{0p6f>?v_@37y@o%WYK9Rpc|&{bY~o2qWHU;8lV@BaPIf;2-^ZYij(_q_3jI6y(V zLV=yYWf@VGF=%Z;O=GzXn)MEurwXXg#43Q+jA=_h+FYNh8r*mo;hbRZ8%_V}J0ECw zL+;c-Rb=@{C&@=#sT9>T{7K;P^y$hJ^wFZ~`d3G~I_d;nLAuLJH-26YNe_m?*20>G zTZilV{TOwvDxmr8?j68u*43pOBHIgN`IVzJ9>(y!fHXBl3Z~7v9@uD2T$-VxzbR;U zzb-p>CVjt-d5x+d-J!r^3J;9OF2hjJM+<5iznkpN_hbB@%v`bPS`pby=DrXcD~=($ zqOiL0592YK(4A}xetDM9xLbhBCt`0OTI<8D%&mF%qw!j%)Rj9zfolYQ0gP>xV;_e^ z<-z>g`WMF9vD|uP$)v29Sq-cqa|bP3RP?7Oj%PnTeb=s(cDAfN%nk>O3-V0v z3PqM + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml new file mode 100644 index 0000000..dc88112 --- /dev/null +++ b/app/src/main/res/layout/activity_main.xml @@ -0,0 +1,21 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_auth.xml b/app/src/main/res/layout/fragment_auth.xml new file mode 100644 index 0000000..1799c79 --- /dev/null +++ b/app/src/main/res/layout/fragment_auth.xml @@ -0,0 +1,90 @@ + + + + + + + +