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/README.md b/README.md index b620652..fc4a513 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,10 @@ -# Gambit challenge +## TUF-2000M Mobile App -## Problem description +## Product Description +The TUF-2000M Mobile App is designed to provide a user-friendly interface for viewing data from the TUF-2000M ultrasonic energy meter. This app fetches data from the associated REST API and presents it to the user in a readable and intuitive manner. -TUF-2000M is an ultrasonic energy meter that has a [Modbus](https://en.wikipedia.org/wiki/Modbus) interface described in docs/tuf-2000m.pdf. +Gambit has access to one of these meters and it is providing you a live text feed that shows the time of the reading followed by the first 100 register values which look like this: -Gambit has access to one of these meters and it is providing you a [live text feed](http://tuftuf.gambitlabs.fi/feed.txt) that shows the time of the reading followed by the first 100 register values which look like this: - -``` 2017-01-11 19:12 1:7579 2:48988 @@ -108,36 +106,72 @@ Gambit has access to one of these meters and it is providing you a [live text fe 98:17105 99:3374 100:17839 -``` -To help you on your way with data conversion I will give you a few clues based on the example data above: +## Features +- Display parsed data from the TUF-2000M ultrasonic energy meter. +- User-friendly interface for easy data visualization. +- Secure authentication for user access. + +## User Manual +Information on the registries, variable names, units, notes, and formats are available on pages 39-42. docs/tuf-2000m.pdf -- Register 21-22, Negative energy accumulator is -56. -- Register 33-34, Temperature #1/inlet is 7.101173400878906. -- Register 92, Signal Quality is 38. +## Getting Started +Follow these steps to set up the project and run the app locally: -The registers and their respective datatypes are explained in detail in [docs/tuf-2000m.pdf](https://github.com/gambit-labs/tuf-2000m/blob/master/docs/tuf-2000m.pdf) on pages 39-42. +1. Clone the repository to your local machine. +2. Open the project in Android Studio. +3. Build and run the app on an Android emulator or a physical device. -## Your task +## Usage +1. Launch the app. +2. Tap on the Sign-up text to register or use dummy credentials to log in (test@gmail.com / Atea@123#) +3. View the parsed data from the TUF-2000M ultrasonic energy meter in the home view pager screen. -Select one task from below. There is no single solution to these problems and we don't expect a complete solution to consider you for a position. +## Technologies Used +- Kotlin +- Android Studio -### Option 1: Create an REST API that parses the data to a readable form -- .NET Core or maybe node.js? -- JSON output is greatly appreciated -- Easily deployable and testable -- Authentication +## Dependencies +- [Retrofit](https://square.github.io/retrofit/) for making HTTP requests. +- [Gson](https://github.com/google/gson) for JSON serialization and deserialization. +- Hilt for dependency injection +- Jetpack components +- Firebase +- -Etc... -### Option 2: Create UI for showing the data for user -- Modern javascript frameworks or maybe an mobile app. It's up to you -- Easily deployable and testable -- UX will be also a key factor on this -- Authentication +## Testing +1. Signed apk distributed link for internal testing: https://appdistribution.firebase.dev/i/0bde28dffa675f6a +(Please follow the instructions, once registered I need to approve and you can install the application, its a signed build, but need to allow from unknown source) +2. To view the demo video, here is the link - https://drive.google.com/drive/folders/1b5gtmzvwHqkAr0e5juCrw9oakHomrRjF +3. Unit and UI tests are yet to be implemented to ensure the app's functionality and user interface work as expected. -## Presenting your solution +## Additional info: +1. Some supporting information in this Google Drive link - https://drive.google.com/drive/folders/1b5gtmzvwHqkAr0e5juCrw9oakHomrRjF +2. https://my-json-server.typicode.com/MinchalaVenkatSunil/demo - "my-json-server.typicode.com", used the restricted sample with limitations to fetch JSON response. -Provide your solution as a *private* Git repository, then share your private repo with our GitHub account `ateaimf` (https://github.com/ateaimf). +## Additional Thoughts: + 1. **Localization** support based on language ID stored in the registry. Initiated but still needs to be completed fully. + 2. **Offline** support to access previously fetched data and other few timestamps. + 3. Ask for **user feedback** once on a scheduled basis. + 4. **Charts, graphs** presentation, maybe on the favorite items for better visualization. + 5. User settings for font size, **dark mode** support + 6. Add more UI and unit test coverage. + 7. Support for multiple form factors. + 8. Extend the support to Apple devices and others using **cross-platform technologies** using React Xamarin forms. + 9. An eye on code quality and best practices. + 10. Automatically publish to Play or App Store on production repo commit using **CICD pipelines**. Just to let you know, it was started and is in progress. + 11. Need to utilize **analytics** to analyze user behavior and generate reports for decision-making and UI improvements. + 12. iPasS **serverless triggers** (e.g., logic apps, functions, Azure stream job analytics) for data processing and desired output transformation. + 13. Tokens to keep all the information shared between the app and the server safe and secure. + 14. I believe we can implement some features to the existing list to make a user-friendly UI. + a. Sort by category + b. Sort by Alphabet + c. Favorites (if the user wants to see specific Readings only) + d. Search as the list might be big + e. Grouping the related content -We appreciate if you can host your solution somewhere in the cloud or VPS so we can see an actual demo of it, rather than just looking at code. +## Backend +[TODO: Once the actual backend is ready in a separate repo we will add more info on how to consume those API's later] -Good use of Git version control is appreciated. +## License +This project is licensed under the [MIT License](LICENSE). 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 0000000..822d725 Binary files /dev/null and b/app/src/main/res/drawable/ic_shutdown.png differ diff --git a/app/src/main/res/drawable/layout_bg.xml b/app/src/main/res/drawable/layout_bg.xml new file mode 100644 index 0000000..746c633 --- /dev/null +++ b/app/src/main/res/drawable/layout_bg.xml @@ -0,0 +1,7 @@ + + + + + + \ 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 @@ + + + + + + + +