commit 5acb95f191bcba86a5b0835fab4b64889a1809f7 Author: hmalik144 Date: Sun Feb 23 18:30:30 2020 +0000 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..603b140 --- /dev/null +++ b/.gitignore @@ -0,0 +1,14 @@ +*.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 diff --git a/.idea/.name b/.idea/.name new file mode 100644 index 0000000..1112e5d --- /dev/null +++ b/.idea/.name @@ -0,0 +1 @@ +Transport Coding Challenge \ No newline at end of file diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml new file mode 100644 index 0000000..45b5654 --- /dev/null +++ b/.idea/codeStyles/Project.xml @@ -0,0 +1,125 @@ + + + + + + + + + + + +
+ + + + xmlns:android + + ^$ + + + +
+
+ + + + xmlns:.* + + ^$ + + + BY_NAME + +
+
+ + + + .*:id + + http://schemas.android.com/apk/res/android + + + +
+
+ + + + .*:name + + http://schemas.android.com/apk/res/android + + + +
+
+ + + + name + + ^$ + + + +
+
+ + + + style + + ^$ + + + +
+
+ + + + .* + + ^$ + + + BY_NAME + +
+
+ + + + .* + + http://schemas.android.com/apk/res/android + + + ANDROID_ATTRIBUTE_ORDER + +
+
+ + + + .* + + .* + + + BY_NAME + +
+
+
+
+ + +
+
\ No newline at end of file diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml new file mode 100644 index 0000000..79ee123 --- /dev/null +++ b/.idea/codeStyles/codeStyleConfig.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/.idea/dictionaries/h_mal.xml b/.idea/dictionaries/h_mal.xml new file mode 100644 index 0000000..f32ad56 --- /dev/null +++ b/.idea/dictionaries/h_mal.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/.idea/gradle.xml b/.idea/gradle.xml new file mode 100644 index 0000000..169fd0d --- /dev/null +++ b/.idea/gradle.xml @@ -0,0 +1,19 @@ + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..d5727af --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/runConfigurations.xml b/.idea/runConfigurations.xml new file mode 100644 index 0000000..7f68460 --- /dev/null +++ b/.idea/runConfigurations.xml @@ -0,0 +1,12 @@ + + + + + + \ No newline at end of file diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..796b96d --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 0000000..3ff6d77 --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,71 @@ +apply plugin: 'com.android.application' + +apply plugin: 'kotlin-android' + +apply plugin: 'kotlin-android-extensions' + +//kotlin kapt +apply plugin: 'kotlin-kapt' + +android { + compileSdkVersion 29 + defaultConfig { + applicationId "com.example.h_mal.transportcodingchallenge" + minSdkVersion 24 + targetSdkVersion 29 + versionCode 1 + versionName "1.0" + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + } + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + dataBinding { + enabled = true + } + testOptions { + unitTests.returnDefaultValues = true + } + +} + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar']) + implementation"org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" + implementation 'androidx.appcompat:appcompat:1.0.2' + implementation 'androidx.core:core-ktx:1.0.2' + implementation 'androidx.constraintlayout:constraintlayout:1.1.3' + implementation 'androidx.lifecycle:lifecycle-extensions:2.0.0' + implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.0.0' + testImplementation 'junit:junit:4.12' + androidTestImplementation 'androidx.test.ext:junit:1.1.1' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1' + + //Retrofit and GSON + implementation 'com.squareup.retrofit2:retrofit:2.6.0' + implementation 'com.squareup.retrofit2:converter-gson:2.6.0' + + //Kotlin Coroutines + implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.0" + implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.0" + + // ViewModel and LiveData + implementation "androidx.lifecycle:lifecycle-extensions:2.1.0" + + //New Material Design + implementation 'com.google.android.material:material:1.1.0-alpha10' + + //Kodein Dependency Injection + implementation "org.kodein.di:kodein-di-generic-jvm:6.2.1" + implementation "org.kodein.di:kodein-di-framework-android-x:6.2.1" + + //Android Navigation Architecture + implementation "androidx.navigation:navigation-fragment-ktx:2.2.0-alpha02" + implementation "androidx.navigation:navigation-ui-ktx:2.2.0-alpha02" + + androidTestImplementation 'androidx.test:rules:1.3.0-alpha04' + +} diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..f1b4245 --- /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 diff --git a/app/src/androidTest/java/com/example/h_mal/transportcodingchallenge/ui/main/MainActivityTest.kt b/app/src/androidTest/java/com/example/h_mal/transportcodingchallenge/ui/main/MainActivityTest.kt new file mode 100644 index 0000000..2975776 --- /dev/null +++ b/app/src/androidTest/java/com/example/h_mal/transportcodingchallenge/ui/main/MainActivityTest.kt @@ -0,0 +1,109 @@ +package com.example.h_mal.transportcodingchallenge.ui.main + + +import android.net.wifi.WifiManager +import android.view.View +import androidx.test.espresso.Espresso.onView +import androidx.test.espresso.UiController +import androidx.test.espresso.ViewAction +import androidx.test.espresso.action.ViewActions.* +import androidx.test.espresso.matcher.ViewMatchers.* +import androidx.test.espresso.matcher.RootMatchers.withDecorView +import androidx.test.espresso.assertion.ViewAssertions.matches +import androidx.test.filters.LargeTest +import androidx.test.internal.runner.junit4.AndroidJUnit4ClassRunner +import androidx.test.rule.ActivityTestRule +import com.example.h_mal.transportcodingchallenge.R +import org.hamcrest.Matcher +import org.hamcrest.Matchers.* +import org.hamcrest.CoreMatchers.`is` +import org.hamcrest.CoreMatchers.not +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + + +@LargeTest +@RunWith(AndroidJUnit4ClassRunner::class) +class MainActivityTest { + + @Rule + @JvmField + var mActivityTestRule = ActivityTestRule(MainActivity::class.java) + + val submitButton = onView(allOf(withId(R.id.submit), withText("Submit"), isDisplayed())) + val searchEditText = onView(allOf(withId(R.id.search_box), isDisplayed())) + + @Test + fun testSubmit_emptySearch() { + submitButton.perform(click()) + + testToast("No Road ID inserted") + } + + @Test + fun testSubmit_SearchInvalidEntry() { + + searchEditText.perform(replaceText("A31222"), closeSoftKeyboard()) + submitButton.perform(click()) + waitFor(1000) + testToast( + "The following road id is not recognised: A31222" + ) + } + + @Test + fun testSubmit_SearchValidEntry() { + + searchEditText.perform(replaceText("A40"), closeSoftKeyboard()) + submitButton.perform(click()) + waitFor(1000) + + onView(allOf( + withId(R.id.road_id), + withText("A40"), + isDisplayed() + ) + ) + + onView( + allOf( + withText("Road Status"), + isDisplayed() + ) + ) + + onView( + allOf( + withText("Road Description"), + isDisplayed() + ) + ) + + } + + private fun testToast(toastText:String){ + onView(withText(toastText)) + .inRoot(withDecorView(not(`is`(mActivityTestRule.activity.window.decorView)))) + .check(matches(isDisplayed())) + + waitFor(2000) + } + + fun waitFor(delay: Long): ViewAction? { + return object : ViewAction { + override fun getConstraints(): Matcher { + return isRoot() + } + + override fun getDescription(): String { + return "wait for " + delay + "milliseconds" + } + + override fun perform(uiController: UiController, view: View?) { + uiController.loopMainThreadForAtLeast(delay) + } + } + } +} diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..2747732 --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/com/example/h_mal/transportcodingchallenge/AppClass.kt b/app/src/main/java/com/example/h_mal/transportcodingchallenge/AppClass.kt new file mode 100644 index 0000000..e5619bc --- /dev/null +++ b/app/src/main/java/com/example/h_mal/transportcodingchallenge/AppClass.kt @@ -0,0 +1,32 @@ +package com.example.h_mal.transportcodingchallenge + +import android.app.Application +import com.example.h_mal.transportcodingchallenge.data.network.ApiData +import com.example.h_mal.transportcodingchallenge.data.network.NetworkConnectionInterceptor +import com.example.h_mal.transportcodingchallenge.data.repositories.Repository +import com.example.h_mal.transportcodingchallenge.ui.main.MainViewModelFactory +import org.kodein.di.Kodein +import org.kodein.di.KodeinAware +import org.kodein.di.android.x.androidXModule +import org.kodein.di.generic.bind +import org.kodein.di.generic.instance +import org.kodein.di.generic.provider +import org.kodein.di.generic.singleton + +class AppClass :Application(), KodeinAware{ + + //kodeinaware for dependency injection + override val kodein = Kodein.lazy { + //import relevent module + import(androidXModule(this@AppClass)) + + //create instance of network interceptor + bind() from singleton { NetworkConnectionInterceptor(instance()) } + //create an instance of Api data class for retrofit calls + bind() from singleton { ApiData(instance()) } + //create an instance of the repository with above api class inserted + bind() from singleton { Repository(instance(), instance()) } + //create an instance of viewmodel factory with above repository inserted + bind() from provider{ MainViewModelFactory(instance())} + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/h_mal/transportcodingchallenge/data/model/RoadData.kt b/app/src/main/java/com/example/h_mal/transportcodingchallenge/data/model/RoadData.kt new file mode 100644 index 0000000..7b6e476 --- /dev/null +++ b/app/src/main/java/com/example/h_mal/transportcodingchallenge/data/model/RoadData.kt @@ -0,0 +1,8 @@ +package com.example.h_mal.transportcodingchallenge.data.model + +data class RoadData( + val displayName: String, + val statusSeverity: String, + val statusSeverityDescription: String +) + diff --git a/app/src/main/java/com/example/h_mal/transportcodingchallenge/data/network/ApiData.kt b/app/src/main/java/com/example/h_mal/transportcodingchallenge/data/network/ApiData.kt new file mode 100644 index 0000000..efdf087 --- /dev/null +++ b/app/src/main/java/com/example/h_mal/transportcodingchallenge/data/network/ApiData.kt @@ -0,0 +1,43 @@ +package com.example.h_mal.transportcodingchallenge.data.network + +import com.example.h_mal.transportcodingchallenge.data.model.RoadData + +import okhttp3.OkHttpClient +import retrofit2.Response +import retrofit2.Retrofit +import retrofit2.converter.gson.GsonConverterFactory +import retrofit2.http.GET +import retrofit2.http.Path +import retrofit2.http.Query + +interface ApiData { + + + //Retrofit api call with path being the road id we input + @GET("{roadId}") + suspend fun getRoadData(@Path("roadId") roadId: String, + @Query("app_key") app_key: String, + @Query("app_id") app_id: String) : Response> + + companion object{ + //class invokation with the network interceptor passed + operator fun invoke( + networkConnectionInterceptor: NetworkConnectionInterceptor + ) : ApiData{ + + //build okhttpclient + val okkHttpclient = OkHttpClient.Builder() + .addInterceptor(networkConnectionInterceptor) + .build() + + //return our API data class + return Retrofit.Builder() + .client(okkHttpclient) + .baseUrl("https://api.tfl.gov.uk/Road/") + .addConverterFactory(GsonConverterFactory.create()) + .build() + .create(ApiData::class.java) + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/example/h_mal/transportcodingchallenge/data/network/NetworkConnectionInterceptor.kt b/app/src/main/java/com/example/h_mal/transportcodingchallenge/data/network/NetworkConnectionInterceptor.kt new file mode 100644 index 0000000..7b097d3 --- /dev/null +++ b/app/src/main/java/com/example/h_mal/transportcodingchallenge/data/network/NetworkConnectionInterceptor.kt @@ -0,0 +1,39 @@ +package com.example.h_mal.transportcodingchallenge.data.network + +import android.content.Context +import android.net.ConnectivityManager +import android.net.NetworkCapabilities +import okhttp3.Interceptor +import okhttp3.Response +import java.io.IOException + +class NetworkConnectionInterceptor( + context: Context +) : Interceptor { + + private val applicationContext = context.applicationContext + + //check if there is an active data connection or throw error + override fun intercept(chain: Interceptor.Chain): Response { + if (!isInternetAvailable()) + throw IOException("Make sure you have an active data connection") + return chain.proceed(chain.request()) + } + + private fun isInternetAvailable(): Boolean { + var result = false + val connectivityManager = + applicationContext.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager? + connectivityManager?.let { + it.getNetworkCapabilities(connectivityManager.activeNetwork)?.apply { + result = when { + hasTransport(NetworkCapabilities.TRANSPORT_WIFI) -> true + hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) -> true + else -> false + } + } + } + return result + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/example/h_mal/transportcodingchallenge/data/network/SafeApiRequest.kt b/app/src/main/java/com/example/h_mal/transportcodingchallenge/data/network/SafeApiRequest.kt new file mode 100644 index 0000000..209414d --- /dev/null +++ b/app/src/main/java/com/example/h_mal/transportcodingchallenge/data/network/SafeApiRequest.kt @@ -0,0 +1,33 @@ +package com.example.h_mal.transportcodingchallenge.data.network + +import android.util.Log +import org.json.JSONException +import org.json.JSONObject +import retrofit2.Response +import java.io.IOException + +abstract class SafeApiRequest { + + //safe api call to safely return our response from the our object + // in the form of {@RoadObject} or throw an error based off the error message in the JSON + suspend fun apiRequest(call: suspend () -> Response) : T{ + val response = call.invoke() + if(response.isSuccessful){ + return response.body()!! + }else{ + val error = response.errorBody()?.string() + + val message = StringBuilder() + error?.let{ + try{ + message.append(JSONObject(it).getString("message")) + }catch(e: JSONException){ } + + } + Log.e("Network Error","Error Code: ${response.code()}") + + throw IOException(message.toString()) + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/example/h_mal/transportcodingchallenge/data/repositories/Repository.kt b/app/src/main/java/com/example/h_mal/transportcodingchallenge/data/repositories/Repository.kt new file mode 100644 index 0000000..1479001 --- /dev/null +++ b/app/src/main/java/com/example/h_mal/transportcodingchallenge/data/repositories/Repository.kt @@ -0,0 +1,25 @@ +package com.example.h_mal.transportcodingchallenge.data.repositories + +import android.content.Context +import com.example.h_mal.transportcodingchallenge.R +import com.example.h_mal.transportcodingchallenge.data.model.RoadData +import com.example.h_mal.transportcodingchallenge.data.network.ApiData +import com.example.h_mal.transportcodingchallenge.data.network.SafeApiRequest + +class Repository( + private val api: ApiData, + context: Context +): SafeApiRequest(){ + + //retrieve the values from the resourse class + private val apiKey: String = context.getString(R.string.Api_key) + private val appId: String = context.getString(R.string.Application_ID) + + //api function to retrieve data from the api via retrofit + //return the data in the for of list as that is how the JSON is + suspend fun getRoadData(roadId: String): List?{ + return apiRequest { + api.getRoadData(roadId, apiKey, appId) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/h_mal/transportcodingchallenge/ui/main/CompletionListener.kt b/app/src/main/java/com/example/h_mal/transportcodingchallenge/ui/main/CompletionListener.kt new file mode 100644 index 0000000..2228da4 --- /dev/null +++ b/app/src/main/java/com/example/h_mal/transportcodingchallenge/ui/main/CompletionListener.kt @@ -0,0 +1,7 @@ +package com.example.h_mal.transportcodingchallenge.ui.main + +interface CompletionListener { + fun onStarted() + fun onSuccess() + fun onFailure(message: String) +} \ No newline at end of file diff --git a/app/src/main/java/com/example/h_mal/transportcodingchallenge/ui/main/MainActivity.kt b/app/src/main/java/com/example/h_mal/transportcodingchallenge/ui/main/MainActivity.kt new file mode 100644 index 0000000..f728dd3 --- /dev/null +++ b/app/src/main/java/com/example/h_mal/transportcodingchallenge/ui/main/MainActivity.kt @@ -0,0 +1,75 @@ +package com.example.h_mal.transportcodingchallenge.ui.main + +import androidx.appcompat.app.AppCompatActivity +import android.os.Bundle +import android.widget.Toast +import androidx.databinding.DataBindingUtil +import androidx.lifecycle.Observer +import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.ViewModelProviders +import com.example.h_mal.transportcodingchallenge.R +import com.example.h_mal.transportcodingchallenge.databinding.MainActivityBinding +import com.example.h_mal.transportcodingchallenge.utils.* +import kotlinx.android.synthetic.main.main_activity.* +import org.kodein.di.KodeinAware +import org.kodein.di.android.kodein +import org.kodein.di.generic.instance + +class MainActivity : AppCompatActivity(), KodeinAware, CompletionListener { + + //obtain viewmodel factory from the application instantiation(s) + override val kodein by kodein() + private val factory : MainViewModelFactory by instance() + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + //setup view for databinding for data to be held in view model + //viewmodels outlive UI so its the best place to keep data + val binding: MainActivityBinding = DataBindingUtil.setContentView(this, R.layout.main_activity) + //create viewmodel class + val viewModel = ViewModelProviders.of(this,factory).get(MainViewModel::class.java) + //binding viewmodel to databing + binding.viewmodel = viewModel + + //setup completion listener + viewModel.completionListener = this + + //observe live data from the view model to change views accordingly + viewModel.roadLiveDate.observe(this, Observer { + result_box.show() + road_id.text = it.displayName + road_status.text = it.statusSeverity + road_description.text = it.statusSeverityDescription + }) + } + + //disable views while loading + private fun invalidateViews(){ + search_box.disable() + submit.disable() + } + + //enable views after loadning + private fun revalidateViews(){ + search_box.enable() + submit.enable() + } + + override fun onStarted() { + invalidateViews() + progress_circular.show() + } + + override fun onSuccess() { + revalidateViews() + progress_circular.hide() + } + + override fun onFailure(message: String) { + Toast.makeText(this,message, Toast.LENGTH_SHORT).show() + progress_circular.hide() + revalidateViews() + } + +} diff --git a/app/src/main/java/com/example/h_mal/transportcodingchallenge/ui/main/MainViewModel.kt b/app/src/main/java/com/example/h_mal/transportcodingchallenge/ui/main/MainViewModel.kt new file mode 100644 index 0000000..e5f94bf --- /dev/null +++ b/app/src/main/java/com/example/h_mal/transportcodingchallenge/ui/main/MainViewModel.kt @@ -0,0 +1,59 @@ +package com.example.h_mal.transportcodingchallenge.ui.main + +import android.view.View +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import com.example.h_mal.transportcodingchallenge.data.model.RoadData +import com.example.h_mal.transportcodingchallenge.data.repositories.Repository +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import java.io.IOException + +class MainViewModel( + private val repository: Repository +) : ViewModel() { + + //completion listener + var completionListener: CompletionListener? = null + + //livedata to populate views in the UI + var roadLiveDate: MutableLiveData = MutableLiveData() + + //databinding the text in the edittext in the layout + var roadId: String? = null + + //databinding the onclick of the submit button in the view + fun submit(view: View?){ + //callback to the view that the operation has started + completionListener?.onStarted() + + //null check the input + if (roadId.isNullOrEmpty()){ + completionListener?.onFailure("No Road ID inserted") + return + } + + //launch a coroutine on main thread to fetch data + CoroutineScope(Dispatchers.Main).launch { + try { + //fetch data from the repository + val response = repository.getRoadData(roadId!!.trim()) + + //unwrap if response is not null + response?.get(0)?.let { + //update the live data + roadLiveDate.value = it + //callback to ciew that operation was successful + completionListener?.onSuccess() + return@launch + } + //callback operation was failed + completionListener?.onFailure("Failed to retrieve data") + }catch (e: IOException){ + //callback operation was failed + completionListener?.onFailure(e.message!!) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/h_mal/transportcodingchallenge/ui/main/MainViewModelFactory.kt b/app/src/main/java/com/example/h_mal/transportcodingchallenge/ui/main/MainViewModelFactory.kt new file mode 100644 index 0000000..8c4c02a --- /dev/null +++ b/app/src/main/java/com/example/h_mal/transportcodingchallenge/ui/main/MainViewModelFactory.kt @@ -0,0 +1,15 @@ +package com.example.h_mal.transportcodingchallenge.ui.main + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProvider +import com.example.h_mal.transportcodingchallenge.data.repositories.Repository + +@Suppress("UNCHECKED_CAST") +class MainViewModelFactory ( + private val repository: Repository +): ViewModelProvider.NewInstanceFactory(){ + + override fun create(modelClass: Class): T { + return MainViewModel(repository) as T + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/h_mal/transportcodingchallenge/utils/ToggleViewUtils.kt b/app/src/main/java/com/example/h_mal/transportcodingchallenge/utils/ToggleViewUtils.kt new file mode 100644 index 0000000..add700a --- /dev/null +++ b/app/src/main/java/com/example/h_mal/transportcodingchallenge/utils/ToggleViewUtils.kt @@ -0,0 +1,19 @@ +package com.example.h_mal.transportcodingchallenge.utils + +import android.view.View + +fun View.show(){ + visibility = View.VISIBLE +} + +fun View.hide(){ + visibility = View.GONE +} + +fun View.disable(){ + isEnabled = false +} + +fun View.enable(){ + isEnabled = true +} \ No newline at end of file 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..1f6bb29 --- /dev/null +++ b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + 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..0d025f9 --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/main_activity.xml b/app/src/main/res/layout/main_activity.xml new file mode 100644 index 0000000..48a5a4c --- /dev/null +++ b/app/src/main/res/layout/main_activity.xml @@ -0,0 +1,151 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +