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 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_latest.xml b/app/src/main/res/layout/fragment_latest.xml
new file mode 100644
index 0000000..e6190ae
--- /dev/null
+++ b/app/src/main/res/layout/fragment_latest.xml
@@ -0,0 +1,114 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_recent.xml b/app/src/main/res/layout/fragment_recent.xml
new file mode 100644
index 0000000..0cb9048
--- /dev/null
+++ b/app/src/main/res/layout/fragment_recent.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_tabs.xml b/app/src/main/res/layout/fragment_tabs.xml
new file mode 100644
index 0000000..2dd34fc
--- /dev/null
+++ b/app/src/main/res/layout/fragment_tabs.xml
@@ -0,0 +1,53 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/item_energycell.xml b/app/src/main/res/layout/item_energycell.xml
new file mode 100644
index 0000000..302a165
--- /dev/null
+++ b/app/src/main/res/layout/item_energycell.xml
@@ -0,0 +1,53 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/item_recycler_recents.xml b/app/src/main/res/layout/item_recycler_recents.xml
new file mode 100644
index 0000000..d24ba91
--- /dev/null
+++ b/app/src/main/res/layout/item_recycler_recents.xml
@@ -0,0 +1,33 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
new file mode 100644
index 0000000..6f3b755
--- /dev/null
+++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
new file mode 100644
index 0000000..6f3b755
--- /dev/null
+++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.webp b/app/src/main/res/mipmap-hdpi/ic_launcher.webp
new file mode 100644
index 0000000..c209e78
Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher.webp differ
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..b2dfe3d
Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.webp b/app/src/main/res/mipmap-mdpi/ic_launcher.webp
new file mode 100644
index 0000000..4f0f1d6
Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher.webp differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..62b611d
Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xhdpi/ic_launcher.webp
new file mode 100644
index 0000000..948a307
Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher.webp differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..1b9a695
Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp
new file mode 100644
index 0000000..28d4b77
Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..9287f50
Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
new file mode 100644
index 0000000..aa7d642
Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..9126ae3
Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp differ
diff --git a/app/src/main/res/navigation/nav_graph.xml b/app/src/main/res/navigation/nav_graph.xml
new file mode 100644
index 0000000..b4be4df
--- /dev/null
+++ b/app/src/main/res/navigation/nav_graph.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values-night/themes.xml b/app/src/main/res/values-night/themes.xml
new file mode 100644
index 0000000..021ccc5
--- /dev/null
+++ b/app/src/main/res/values-night/themes.xml
@@ -0,0 +1,7 @@
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml
new file mode 100644
index 0000000..bb8ae48
--- /dev/null
+++ b/app/src/main/res/values-zh-rCN/strings.xml
@@ -0,0 +1,20 @@
+
+
+ Meter Reader
+ "Positive Energy "
+ "Negative Energy "
+ Temperature
+ Signal Quality
+ Login
+ Signup
+ Welcom to Meter Reading App
+ Back to Login
+ TextView
+ Refresh icon
+ Search here
+ Email
+ Password
+ Welcom to Meter Reading App
+ Latest Reading
+ Recent History
+
\ No newline at end of file
diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml
new file mode 100644
index 0000000..c8524cd
--- /dev/null
+++ b/app/src/main/res/values/colors.xml
@@ -0,0 +1,5 @@
+
+
+ #FF000000
+ #FFFFFFFF
+
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
new file mode 100644
index 0000000..d0b971c
--- /dev/null
+++ b/app/src/main/res/values/strings.xml
@@ -0,0 +1,25 @@
+
+ Meter Reader
+ "Positive Energy "
+ "Negative Energy "
+ Temperature
+ Signal Quality
+ Login
+ Signup
+ Don\'t have account Signup here\n
+ Back to Login
+ TextView
+ Refresh icon
+ Search here
+ Email
+ Password
+ Welcom to Meter Reading App
+ Latest Reading
+ Recent History
+
+
+ Category
+ Name
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml
new file mode 100644
index 0000000..02bafc3
--- /dev/null
+++ b/app/src/main/res/values/themes.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/xml/backup_rules.xml b/app/src/main/res/xml/backup_rules.xml
new file mode 100644
index 0000000..fa0f996
--- /dev/null
+++ b/app/src/main/res/xml/backup_rules.xml
@@ -0,0 +1,13 @@
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/xml/data_extraction_rules.xml b/app/src/main/res/xml/data_extraction_rules.xml
new file mode 100644
index 0000000..9ee9997
--- /dev/null
+++ b/app/src/main/res/xml/data_extraction_rules.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/test/java/com/tuf2000m/energymeter/ExampleUnitTest.kt b/app/src/test/java/com/tuf2000m/energymeter/ExampleUnitTest.kt
new file mode 100644
index 0000000..e0ee272
--- /dev/null
+++ b/app/src/test/java/com/tuf2000m/energymeter/ExampleUnitTest.kt
@@ -0,0 +1,16 @@
+package com.tuf2000m.energymeter
+
+import org.junit.Assert.assertEquals
+import org.junit.Test
+
+/**
+ * Example local unit test, which will execute on the development machine (host).
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+class ExampleUnitTest {
+ @Test
+ fun addition_isCorrect() {
+ assertEquals(4, 2 + 2)
+ }
+}
\ No newline at end of file
diff --git a/build.gradle b/build.gradle
new file mode 100644
index 0000000..cdbac48
--- /dev/null
+++ b/build.gradle
@@ -0,0 +1,8 @@
+// Top-level build file where you can add configuration options common to all sub-projects/modules.
+plugins {
+ id 'com.android.application' version '7.3.0' apply false
+ id 'com.android.library' version '7.3.0' apply false
+ id 'org.jetbrains.kotlin.android' version '1.8.0' apply false
+ id 'com.google.dagger.hilt.android' version "2.44" apply false
+ id 'com.google.gms.google-services' version "4.3.4" apply false
+}
\ No newline at end of file
diff --git a/gradle.properties b/gradle.properties
new file mode 100644
index 0000000..3350001
--- /dev/null
+++ b/gradle.properties
@@ -0,0 +1,24 @@
+# Project-wide Gradle settings.
+# IDE (e.g. Android Studio) users:
+# Gradle settings configured through the IDE *will override*
+# any settings specified in this file.
+# For more details on how to configure your build environment visit
+# http://www.gradle.org/docs/current/userguide/build_environment.html
+# Specifies the JVM arguments used for the daemon process.
+# The setting is particularly useful for tweaking memory settings.
+org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
+# When configured, Gradle will run in incubating parallel mode.
+# This option should only be used with decoupled projects. More details, visit
+# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
+# org.gradle.parallel=true
+# AndroidX package structure to make it clearer which packages are bundled with the
+# Android operating system, and which are packaged with your app's APK
+# https://developer.android.com/topic/libraries/support-library/androidx-rn
+android.useAndroidX=true
+# Kotlin code style for this project: "official" or "obsolete":
+kotlin.code.style=official
+android.enableJetifier=true
+# Enables namespacing of each library's R class so that its R class includes only the
+# resources declared in the library itself and none from the library's dependencies,
+# thereby reducing the size of the R class for that library
+android.nonTransitiveRClass=true
\ No newline at end of file
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..e708b1c
Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..a64caaa
--- /dev/null
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Thu Oct 05 10:04:05 PKT 2023
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-bin.zip
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
diff --git a/gradlew b/gradlew
new file mode 100644
index 0000000..4f906e0
--- /dev/null
+++ b/gradlew
@@ -0,0 +1,185 @@
+#!/usr/bin/env sh
+
+#
+# Copyright 2015 the original author or authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+##############################################################################
+##
+## Gradle start up script for UN*X
+##
+##############################################################################
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG=`dirname "$PRG"`"/$link"
+ fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn () {
+ echo "$*"
+}
+
+die () {
+ echo
+ echo "$*"
+ echo
+ exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "`uname`" in
+ CYGWIN* )
+ cygwin=true
+ ;;
+ Darwin* )
+ darwin=true
+ ;;
+ MINGW* )
+ msys=true
+ ;;
+ NONSTOP* )
+ nonstop=true
+ ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD="java"
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
+ MAX_FD_LIMIT=`ulimit -H -n`
+ if [ $? -eq 0 ] ; then
+ if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+ MAX_FD="$MAX_FD_LIMIT"
+ fi
+ ulimit -n $MAX_FD
+ if [ $? -ne 0 ] ; then
+ warn "Could not set maximum file descriptor limit: $MAX_FD"
+ fi
+ else
+ warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+ fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+ GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin or MSYS, switch paths to Windows format before running java
+if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
+ APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+ CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+
+ JAVACMD=`cygpath --unix "$JAVACMD"`
+
+ # We build the pattern for arguments to be converted via cygpath
+ ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+ SEP=""
+ for dir in $ROOTDIRSRAW ; do
+ ROOTDIRS="$ROOTDIRS$SEP$dir"
+ SEP="|"
+ done
+ OURCYGPATTERN="(^($ROOTDIRS))"
+ # Add a user-defined pattern to the cygpath arguments
+ if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+ OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+ fi
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ i=0
+ for arg in "$@" ; do
+ CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+ CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
+
+ if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
+ eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+ else
+ eval `echo args$i`="\"$arg\""
+ fi
+ i=`expr $i + 1`
+ done
+ case $i in
+ 0) set -- ;;
+ 1) set -- "$args0" ;;
+ 2) set -- "$args0" "$args1" ;;
+ 3) set -- "$args0" "$args1" "$args2" ;;
+ 4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+ 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+ 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+ 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+ 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+ 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+ esac
+fi
+
+# Escape application args
+save () {
+ for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
+ echo " "
+}
+APP_ARGS=`save "$@"`
+
+# Collect all arguments for the java command, following the shell quoting and substitution rules
+eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
+
+exec "$JAVACMD" "$@"
diff --git a/gradlew.bat b/gradlew.bat
new file mode 100644
index 0000000..107acd3
--- /dev/null
+++ b/gradlew.bat
@@ -0,0 +1,89 @@
+@rem
+@rem Copyright 2015 the original author or authors.
+@rem
+@rem Licensed under the Apache License, Version 2.0 (the "License");
+@rem you may not use this file except in compliance with the License.
+@rem You may obtain a copy of the License at
+@rem
+@rem https://www.apache.org/licenses/LICENSE-2.0
+@rem
+@rem Unless required by applicable law or agreed to in writing, software
+@rem distributed under the License is distributed on an "AS IS" BASIS,
+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+@rem See the License for the specific language governing permissions and
+@rem limitations under the License.
+@rem
+
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Resolve any "." and ".." in APP_HOME to make it shorter.
+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto execute
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto execute
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/settings.gradle b/settings.gradle
new file mode 100644
index 0000000..ea49c58
--- /dev/null
+++ b/settings.gradle
@@ -0,0 +1,29 @@
+pluginManagement {
+ repositories {
+ google()
+ mavenCentral()
+ gradlePluginPortal()
+ }
+ buildscript {
+ repositories {
+ mavenCentral()
+ maven {
+ // r8 maven
+ url = uri("https://storage.googleapis.com/r8-releases/raw")
+ }
+ }
+ dependencies {
+ // r8 version
+ classpath("com.android.tools:r8:8.2.16-dev")
+ }
+ }
+}
+dependencyResolutionManagement {
+ repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
+ repositories {
+ google()
+ mavenCentral()
+ }
+}
+rootProject.name = "Meter Reader"
+include ':app'