diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 00000000..796b96d1 --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 00000000..d3a43746 --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,98 @@ +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' +apply plugin: 'kotlin-android-extensions' +apply plugin: 'kotlin-kapt' + +android { + compileSdkVersion 29 + buildToolsVersion "29.0.3" + + defaultConfig { + applicationId "br.com.desafio" + minSdkVersion 21 + targetSdkVersion 29 + versionCode 1 + versionName "1.0" + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + multiDexEnabled true + vectorDrawables.useSupportLibrary = true + } + + flavorDimensions "version" + productFlavors { + dev { + dimension "version" + sourceSets{ + main { java.srcDirs = ['src/main/java'] } + androidTest { java.srcDirs = ['src/androidTest/java'] } + } + } + prod { + dimension "version" + } + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + dataBinding { + enabled = true + } +} + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar']) + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" + implementation 'androidx.appcompat:appcompat:1.1.0' + implementation 'androidx.core:core-ktx:1.3.0' + implementation 'androidx.constraintlayout:constraintlayout:1.1.3' + implementation 'androidx.recyclerview:recyclerview:1.1.0' + implementation 'android.arch.lifecycle:extensions:1.1.1' + implementation 'org.koin:koin-android:1.0.1' + implementation "org.koin:koin-android-viewmodel:$koin_version" + implementation 'com.nostra13.universalimageloader:universal-image-loader:1.9.5' + implementation 'com.google.android.material:material:1.3.0-alpha01' + implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0' + + // Http + implementation 'com.squareup.retrofit2:retrofit:2.9.0' + implementation 'com.squareup.retrofit2:converter-moshi:2.9.0' + implementation 'com.squareup.retrofit2:converter-gson:2.9.0' + implementation 'com.squareup.moshi:moshi:1.9.3' + kapt('com.squareup.moshi:moshi-kotlin-codegen:1.9.3') + + def nav_version = "2.3.0" + implementation "androidx.navigation:navigation-fragment-ktx:$nav_version" + implementation "androidx.navigation:navigation-ui-ktx:$nav_version" + + // Teste + testImplementation 'junit:junit:4.13' + devImplementation 'com.squareup.okhttp:mockwebserver:2.5.0' + devImplementation 'androidx.test.ext:junit:1.1.1' + devImplementation 'androidx.test.espresso:espresso-core:3.2.0' + devImplementation 'androidx.test:runner:1.2.0' + devImplementation 'androidx.test:rules:1.2.0' + devImplementation 'com.android.support.test.espresso:espresso-intents:3.0.2' + devImplementation('com.android.support.test.espresso:espresso-core:3.0.2') { + exclude group: 'com.android.support', module: 'support-annotations' + } + + def lifecycle_version = "1.1.1" + implementation "android.arch.lifecycle:extensions:$lifecycle_version" + def room_version = "1.1.1" + implementation "android.arch.persistence.room:runtime:$room_version" + kapt "android.arch.persistence.room:compiler:$room_version" + + kapt 'com.android.databinding:compiler:3.1.4' + + implementation 'androidx.multidex:multidex:2.0.1' +} diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 00000000..f1b42451 --- /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/sampledata/data.json b/app/sampledata/data.json new file mode 100644 index 00000000..489238c3 --- /dev/null +++ b/app/sampledata/data.json @@ -0,0 +1,42 @@ +[ + { + "name": "Pencil", + "quantity": 1, + "stock": 5, + "image_url": "https://github.com/charleston10/test-android-nexaas/blob/master/assets/pencil.png?raw=true", + "price": 150, + "tax": 162, + "shipping": 50, + "description": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam nunc magna, gravida ut orci non, egestas venenatis libero. Sed luctus, turpis at porta commodo, ipsum orci volutpat sapien, ut scelerisque diam massa lobortis odioc." + }, + { + "name": "Rubberbands", + "quantity": 1, + "stock": 8, + "image_url": "https://github.com/charleston10/test-android-nexaas/blob/master/assets/rubberbands.png?raw=true", + "price": 450, + "tax": 81, + "shipping": 0, + "description": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam nunc magna, gravida ut orci non, egestas venenatis libero. Sed luctus, turpis at porta commodo, ipsum orci volutpat sapien, ut scelerisque diam massa lobortis odio. Nulla ut tincidunt erat, a mollis tortor. Phasellus vel ligula leo. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Nam id semper quam, id efficitur mi. Etiam volutpat eleifend commodo. Duis sed consectetur diam. Morbi mattis justo eget vehicula placerat. Sed commodo, neque a accumsan vehicula, magna libero lacinia dolor, a consectetur turpis odio nec risus. Nullam id dui lacus." + }, + { + "name": "Rulers", + "quantity": 1, + "stock": 1, + "image_url": "https://github.com/charleston10/test-android-nexaas/blob/master/assets/rulers.png?raw=true", + "price": 800, + "tax": 0, + "shipping": 100, + "description": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam nunc magna, gravida ut orci non, egestas venenatis libero. Sed luctus, turpis at porta commodo, ipsum orci volutpat sapien, ut scelerisque diam massa lobortis odioc." + }, + { + "name": "Clock", + "quantity": 1, + "stock": 10, + "image_url": "https://github.com/charleston10/test-android-nexaas/blob/master/assets/clock.png?raw=true", + "price": 2200, + "tax": 81, + "shipping": 50, + "description": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam nunc magna, gravida ut orci non, egestas venenatis libero. Sed luctus, turpis at porta commodo, ipsum orci volutpat sapien, ut scelerisque diam massa lobortis odio. Nulla ut tincidunt erat, a mollis tortor. Phasellus vel ligula leo. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Nam id semper quam, id efficitur mi. Etiam volutpat eleifend commodo. Duis sed consectetur diam. Morbi mattis justo eget vehicula placerat. Sed commodo, neque a accumsan vehicula, magna libero lacinia dolor, a consectetur turpis odio nec risus. Nullam id dui lacus." + } +] \ No newline at end of file diff --git a/app/src/androidTest/java/br/com/desafio/LoginTest.kt b/app/src/androidTest/java/br/com/desafio/LoginTest.kt new file mode 100644 index 00000000..2739a2c6 --- /dev/null +++ b/app/src/androidTest/java/br/com/desafio/LoginTest.kt @@ -0,0 +1,54 @@ +package br.com.desafio + +import android.util.Log +import androidx.test.espresso.Espresso +import androidx.test.espresso.action.ViewActions +import androidx.test.espresso.intent.Intents +import androidx.test.espresso.intent.matcher.IntentMatchers.hasComponent +import androidx.test.espresso.matcher.ViewMatchers +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.LargeTest +import androidx.test.rule.ActivityTestRule +import br.com.desafio.mock.MockServer +import br.com.desafio.response.LoginResponse.loginResnponseSucess +import br.com.desafio.ui.activity.LoginActivity +import br.com.desafio.ui.activity.MainActivity +import org.junit.After +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +@LargeTest +class LoginTest { // Exemplo de teste integrado com Espresso + + @get:Rule + val activityRule = ActivityTestRule(LoginActivity::class.java) + + var mockServer: MockServer? = null + + @Before + fun setUp() { + if (mockServer == null) { + mockServer = MockServer() + } + } + + @After + fun cleanUp() { + mockServer?.stop() + } + + @Test + fun logar() { + Log.i("*** ", "Inicio teste de login") + Intents.init() + mockServer!!.changeResponse(loginResnponseSucess) + Espresso.onView(ViewMatchers.withId(R.id.edtLogin)).perform(ViewActions.typeText("123456"), ViewActions.closeSoftKeyboard()) + Espresso.onView(ViewMatchers.withText("Login")).perform(ViewActions.click()) + Intents.intended(hasComponent(MainActivity::class.java.name)) + Log.i("*** ", "Fim teste de login") + Intents.release() + } +} \ No newline at end of file diff --git a/app/src/dev/java/br/com/desafio/DesafioApplication.kt b/app/src/dev/java/br/com/desafio/DesafioApplication.kt new file mode 100644 index 00000000..61ec7496 --- /dev/null +++ b/app/src/dev/java/br/com/desafio/DesafioApplication.kt @@ -0,0 +1,15 @@ +package br.com.desafio + +import android.app.Application +import br.com.desafio.di.myModule +import br.com.desafio.util.Util +import org.koin.android.ext.android.startKoin + +class DesafioApplication : Application() { + + override fun onCreate() { + super.onCreate() + startKoin(this, myModule) + Util.initImageLoader(this) + } +} \ No newline at end of file diff --git a/app/src/dev/java/br/com/desafio/mock/DefaultConfigurationMockResponse.kt b/app/src/dev/java/br/com/desafio/mock/DefaultConfigurationMockResponse.kt new file mode 100644 index 00000000..99e4e1bc --- /dev/null +++ b/app/src/dev/java/br/com/desafio/mock/DefaultConfigurationMockResponse.kt @@ -0,0 +1,21 @@ +package br.com.desafio.mock + +import br.com.desafio.response.LoginResponse.loginResnponseSucess +import java.util.* + +class DefaultConfigurationMockResponse { + + private val mapResponse: HashMap = HashMap() + + fun response(URI: String?): Any? { + return mapResponse[URI] + } + + private fun createResponse() { + mapResponse["https://raw.githubusercontent.com/"] = loginResnponseSucess + } + + init { + createResponse() + } +} \ No newline at end of file diff --git a/app/src/dev/java/br/com/desafio/mock/MockServer.kt b/app/src/dev/java/br/com/desafio/mock/MockServer.kt new file mode 100644 index 00000000..65ffb6e4 --- /dev/null +++ b/app/src/dev/java/br/com/desafio/mock/MockServer.kt @@ -0,0 +1,79 @@ +package br.com.desafio.mock + +import com.google.gson.Gson +import com.squareup.okhttp.mockwebserver.Dispatcher +import com.squareup.okhttp.mockwebserver.MockResponse +import com.squareup.okhttp.mockwebserver.MockWebServer +import com.squareup.okhttp.mockwebserver.RecordedRequest +import java.io.IOException +import java.net.InetAddress +import java.util.logging.Logger + +class MockServer { + + private var mockWebServer: MockWebServer? = null + + fun changeResponse(responseObject: Any?) { + mockWebServer!!.setDispatcher(AlterDispatcher(responseObject)) + } + + private inner class DefaultConfigurationDispatcher : Dispatcher() { + @Throws(InterruptedException::class) + override fun dispatch(request: RecordedRequest): MockResponse { + val mockResponse = MockResponse() + val defaultConfigurationMockResponse = + DefaultConfigurationMockResponse() + val response = defaultConfigurationMockResponse.response(request.path) + val responseJson = Gson().toJson(response) + logger.info("------------------------------ --------------- --------------- --------------- --------------- --------------- --------------- --------------- ") + logger.info("REQUEST PATH: " + request.path) + logger.info("RESPONSE JSON: $responseJson") + mockResponse + .addHeader("Content-Type", "application/json; charset=utf-8") + .addHeader("Cache-Control", "no-cache") + .setBody(responseJson) + return mockResponse + } + } + + private inner class AlterDispatcher(private val responseObject: Any?) : + Dispatcher() { + @Throws(InterruptedException::class) + override fun dispatch(request: RecordedRequest): MockResponse { + val gson = Gson() + val mockResponse = MockResponse() + mockResponse.setResponseCode(200) + val responseJson = gson.toJson(responseObject) + logger.info("RESPONSE ON DISPATCHER: $responseJson") + mockResponse.setBody(gson.toJson(responseObject)) + return mockResponse + } + + } + + fun stop() { + try { + mockWebServer!!.shutdown() + } catch (e: IOException) { + e.printStackTrace() + } + } + + companion object { + private val logger = + Logger.getLogger(MockServer::class.java.name) + } + + init { + try { + mockWebServer = MockWebServer() + mockWebServer!!.setDispatcher(DefaultConfigurationDispatcher()) + logger.info(InetAddress.getLocalHost().hostAddress) + val address = InetAddress.getLocalHost().hostAddress + val inetAddress = InetAddress.getByName(address) + mockWebServer!!.start(inetAddress, 8080) + } catch (e: IOException) { + logger.info("Error when try start mock server ..." + e.message + e.cause.toString()) + } + } +} \ No newline at end of file diff --git a/app/src/dev/java/br/com/desafio/response/LoginResponse.kt b/app/src/dev/java/br/com/desafio/response/LoginResponse.kt new file mode 100644 index 00000000..03ce66d1 --- /dev/null +++ b/app/src/dev/java/br/com/desafio/response/LoginResponse.kt @@ -0,0 +1,7 @@ +package br.com.desafio.response + +object LoginResponse { + + @JvmStatic + val loginResnponseSucess: String = "ok" +} \ 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 00000000..015820ca --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/br/com/desafio/di/Module.kt b/app/src/main/java/br/com/desafio/di/Module.kt new file mode 100644 index 00000000..8acb4449 --- /dev/null +++ b/app/src/main/java/br/com/desafio/di/Module.kt @@ -0,0 +1,29 @@ +package br.com.desafio.di + +import br.com.desafio.vm.MainViewModel +import br.com.desafio.http.ItemDataSource +import br.com.desafio.http.ItemRepository +import br.com.desafio.http.remote.FreeApi +import br.com.desafio.http.remote.FreeApiDataSource +import br.com.desafio.vm.DetailViewModel +import org.koin.android.ext.koin.androidApplication +import org.koin.dsl.module.module +import retrofit2.Retrofit +import retrofit2.converter.gson.GsonConverterFactory +import org.koin.android.viewmodel.ext.koin.viewModel + +val Module = module { + single{Retrofit.Builder().baseUrl("https://raw.githubusercontent.com/").addConverterFactory(GsonConverterFactory.create()).build()} + single{(get() as Retrofit).create(FreeApi::class.java)} + single("api"){FreeApiDataSource(get()) as ItemDataSource} + single("repository") { ItemRepository(get("api")) as ItemDataSource} + viewModel { + MainViewModel( + get("repository"), + androidApplication() + ) + } + viewModel { DetailViewModel(androidApplication()) } +} + +val myModule = listOf(Module) \ No newline at end of file diff --git a/app/src/main/java/br/com/desafio/http/ItemDataSource.kt b/app/src/main/java/br/com/desafio/http/ItemDataSource.kt new file mode 100644 index 00000000..3bc1d817 --- /dev/null +++ b/app/src/main/java/br/com/desafio/http/ItemDataSource.kt @@ -0,0 +1,8 @@ +package br.com.desafio.http + +import br.com.desafio.ui.model.Item + +interface ItemDataSource { + + fun listAllObjects(success : (List) -> Unit, failure: () -> Unit) +} \ No newline at end of file diff --git a/app/src/main/java/br/com/desafio/http/ItemRepository.kt b/app/src/main/java/br/com/desafio/http/ItemRepository.kt new file mode 100644 index 00000000..bea33a2e --- /dev/null +++ b/app/src/main/java/br/com/desafio/http/ItemRepository.kt @@ -0,0 +1,10 @@ +package br.com.desafio.http + +import br.com.desafio.ui.model.Item + +class ItemRepository(private val freeApiDataSource: ItemDataSource): ItemDataSource { + + override fun listAllObjects(success: (List) -> Unit, failure: () -> Unit) { + freeApiDataSource.listAllObjects(success, failure) + } +} \ No newline at end of file diff --git a/app/src/main/java/br/com/desafio/http/remote/FreeApi.kt b/app/src/main/java/br/com/desafio/http/remote/FreeApi.kt new file mode 100644 index 00000000..18792a6e --- /dev/null +++ b/app/src/main/java/br/com/desafio/http/remote/FreeApi.kt @@ -0,0 +1,11 @@ +package br.com.desafio.http.remote + +import br.com.desafio.ui.model.Item +import retrofit2.Call +import retrofit2.http.GET + +interface FreeApi { + + @GET("/myfreecomm/desafio-mobile-android/master/api/data.json") + fun listObjects(): Call> +} \ No newline at end of file diff --git a/app/src/main/java/br/com/desafio/http/remote/FreeApiDataSource.kt b/app/src/main/java/br/com/desafio/http/remote/FreeApiDataSource.kt new file mode 100644 index 00000000..f99a6036 --- /dev/null +++ b/app/src/main/java/br/com/desafio/http/remote/FreeApiDataSource.kt @@ -0,0 +1,30 @@ +package br.com.desafio.http.remote + +import br.com.desafio.ui.model.Item +import br.com.desafio.http.ItemDataSource +import retrofit2.Call +import retrofit2.Callback +import retrofit2.Response + +class FreeApiDataSource(val freeApi: FreeApi): ItemDataSource { + + override fun listAllObjects(success: (List) -> Unit, failure: () -> Unit) { + val call = freeApi.listObjects() + call.enqueue(object : Callback>{ + override fun onResponse(call: Call>, response: Response>) { + if (response.isSuccessful) { + val itens = mutableListOf() + response.body()?.forEach { + itens.add(it) + } + success(itens) + } else { + failure() + } + } + override fun onFailure(call: Call>, t: Throwable) { + failure() + } + }) + } +} \ No newline at end of file diff --git a/app/src/main/java/br/com/desafio/ui/activity/LoginActivity.kt b/app/src/main/java/br/com/desafio/ui/activity/LoginActivity.kt new file mode 100644 index 00000000..a27c71ee --- /dev/null +++ b/app/src/main/java/br/com/desafio/ui/activity/LoginActivity.kt @@ -0,0 +1,41 @@ +package br.com.desafio.ui.activity + +import android.annotation.SuppressLint +import android.content.Intent +import android.content.pm.ActivityInfo +import androidx.appcompat.app.AppCompatActivity +import android.os.Bundle +import android.text.TextUtils +import androidx.databinding.DataBindingUtil +import androidx.lifecycle.Observer +import androidx.lifecycle.ViewModelProviders +import br.com.desafio.R +import br.com.desafio.databinding.ActivityLoginBinding +import br.com.desafio.vm.LoginViewModel +import java.util.* + +class LoginActivity : AppCompatActivity() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + requestedOrientation = (ActivityInfo.SCREEN_ORIENTATION_PORTRAIT) + var loginViewModel = ViewModelProviders.of(this).get(LoginViewModel::class.java) + val binding: ActivityLoginBinding = DataBindingUtil.setContentView(this@LoginActivity, R.layout.activity_login) + binding.lifecycleOwner = this + binding.loginViewModel = loginViewModel + + loginViewModel.user.observe(this, Observer { it -> + it?.let { + if (TextUtils.isEmpty(Objects.requireNonNull(it).strLogin)) { + binding.edtLogin.error = "Inserir login" + binding.edtLogin.requestFocus() + } else if (!it!!.isLoginLengthGreaterThan5) { + binding.edtLogin.error = "6 dígitos" + binding.edtLogin.requestFocus() + } else { + startActivity(Intent(this, MainActivity::class.java)) + } + } + }) + } +} \ No newline at end of file diff --git a/app/src/main/java/br/com/desafio/ui/activity/MainActivity.kt b/app/src/main/java/br/com/desafio/ui/activity/MainActivity.kt new file mode 100644 index 00000000..2d54d42b --- /dev/null +++ b/app/src/main/java/br/com/desafio/ui/activity/MainActivity.kt @@ -0,0 +1,90 @@ +package br.com.desafio.ui.activity + +import android.content.pm.ActivityInfo +import android.os.Build +import android.os.Bundle +import android.transition.TransitionInflater +import android.util.Log +import android.view.View +import android.widget.Toast +import androidx.appcompat.app.AppCompatActivity +import androidx.fragment.app.Fragment +import br.com.desafio.R +import br.com.desafio.ui.fragment.MainFragment +import br.com.desafio.util.Util.Companion.addFragmentTo + +class MainActivity : AppCompatActivity() { + + companion object { + private var back_pressed: Long = 0 + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_main) + requestedOrientation = (ActivityInfo.SCREEN_ORIENTATION_PORTRAIT) +// showFragment( +// MainFragment(), +// MainFragment.FRAGMENT_TAG +// ) + addFragmentTo(R.id.content_frame, createFragment()) + } + + private fun createFragment(): MainFragment { + return MainFragment.newInstance() + } + + // Teste + private fun showFragment(name: Fragment, tag: String) { + val fragmentManager = supportFragmentManager + val fragmentPopped = fragmentManager.popBackStackImmediate(tag, 0) + if (fragmentPopped) { + Log.d("Back",fragmentPopped.toString()) + } else { + val fragmentTransaction = fragmentManager.beginTransaction() + fragmentTransaction.replace(R.id.content_frame, name, tag) + fragmentTransaction.addToBackStack(tag) + fragmentTransaction.commit() + } + } + + fun showFragmentWithTransition(current: Fragment, newFragment: Fragment, tag: String?, sharedView: View, sharedElementName: String) { + val fragmentManager = supportFragmentManager + val fragmentPopped = fragmentManager.popBackStackImmediate(tag, 0) + if (fragmentPopped) { + Log.d("Bask stack ",fragmentPopped.toString()) + } else { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + current.sharedElementReturnTransition = TransitionInflater.from(this).inflateTransition(R.transition.default_transition) + current.exitTransition = TransitionInflater.from(this).inflateTransition(android.R.transition.no_transition) + newFragment.sharedElementEnterTransition = TransitionInflater.from(this).inflateTransition(R.transition.default_transition) + newFragment.enterTransition = TransitionInflater.from(this).inflateTransition(android.R.transition.no_transition) + } + val fragmentTransaction = fragmentManager.beginTransaction() + fragmentTransaction.replace(R.id.content_frame, newFragment, tag) + fragmentTransaction.addToBackStack(tag) + fragmentTransaction.addSharedElement(sharedView, sharedElementName) + fragmentTransaction.commit() + } + } + + override fun onBackPressed() { + oneStepBack() + } + + private fun oneStepBack() { + val fts = supportFragmentManager.beginTransaction() + val fragmentManager = supportFragmentManager + if (fragmentManager.backStackEntryCount >= 2) { + fragmentManager.popBackStackImmediate() + fts.commit() + } else { + doubleClickToExit() + } + } + + private fun doubleClickToExit() { + if (back_pressed + 2000 > System.currentTimeMillis()) finish() else Toast.makeText(this@MainActivity, "Click again to exit", Toast.LENGTH_SHORT).show() + back_pressed = System.currentTimeMillis() + } +} \ No newline at end of file diff --git a/app/src/main/java/br/com/desafio/ui/fragment/DetailFragment.kt b/app/src/main/java/br/com/desafio/ui/fragment/DetailFragment.kt new file mode 100644 index 00000000..86ba03b7 --- /dev/null +++ b/app/src/main/java/br/com/desafio/ui/fragment/DetailFragment.kt @@ -0,0 +1,42 @@ +package br.com.desafio.ui.fragment + +import android.os.Bundle +import android.os.Handler +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.Toast +import androidx.databinding.DataBindingUtil +import androidx.fragment.app.Fragment +import androidx.fragment.app.FragmentActivity +import androidx.lifecycle.Observer +import br.com.desafio.R +import br.com.desafio.databinding.DetailFragmentBinding +import br.com.desafio.ui.model.Item +import br.com.desafio.vm.DetailViewModel +import org.koin.android.viewmodel.ext.android.viewModel + +class DetailFragment(var item: Item?) : Fragment() { + + private val viewModel: DetailViewModel by viewModel() + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { + val binding: DetailFragmentBinding = DataBindingUtil.inflate(inflater, R.layout.detail_fragment, container,false) + binding.root.transitionName = binding.root.transitionName + binding.viewModel = viewModel + binding.lifecycleOwner = this + lifecycle.addObserver(viewModel) + + viewModel.resultImageUrl.set(item?.image_url) + viewModel.name.set(item?.name) + viewModel.price.set("R$ " + item?.price) + viewModel.description.set(item?.description) + + viewModel.voltar.observe(viewLifecycleOwner, Observer { + if (it){ + fragmentManager?.popBackStack() + } + }) + return binding.root + } +} \ No newline at end of file diff --git a/app/src/main/java/br/com/desafio/ui/fragment/MainFragment.kt b/app/src/main/java/br/com/desafio/ui/fragment/MainFragment.kt new file mode 100644 index 00000000..7c5e15d4 --- /dev/null +++ b/app/src/main/java/br/com/desafio/ui/fragment/MainFragment.kt @@ -0,0 +1,184 @@ +package br.com.desafio.ui.fragment + +import android.os.Build +import android.os.Bundle +import android.transition.Fade +import android.transition.TransitionInflater +import android.transition.TransitionSet +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.Toast +import androidx.appcompat.app.ActionBarDrawerToggle +import androidx.core.view.GravityCompat +import androidx.databinding.DataBindingUtil +import androidx.fragment.app.Fragment +import androidx.lifecycle.Observer +import androidx.recyclerview.widget.DividerItemDecoration +import androidx.recyclerview.widget.LinearLayoutManager +import br.com.desafio.R +import br.com.desafio.databinding.MainFragmentBinding +import br.com.desafio.ui.model.Item +import br.com.desafio.util.MainAdapter +import br.com.desafio.vm.MainViewModel +import kotlinx.android.synthetic.main.app_bar_main.* +import kotlinx.android.synthetic.main.main_fragment.* +import kotlinx.android.synthetic.main.main_item.* +import kotlinx.android.synthetic.main.navigation_view.* +import org.koin.android.viewmodel.ext.android.viewModel + +class MainFragment: Fragment() { + + private val MOVE_DEFAULT_TIME: Long = 1000 + private val FADE_DEFAULT_TIME: Long = 300 + + private val viewModel: MainViewModel by viewModel() + + companion object { + fun newInstance(): MainFragment { + return MainFragment() + } + const val FRAGMENT_TAG = "main" + } + + override fun onActivityCreated(savedInstanceState: Bundle?) { + super.onActivityCreated(savedInstanceState) + initItems() + } + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + val binding: MainFragmentBinding = + DataBindingUtil.inflate(inflater, R.layout.main_fragment, container, false) + binding.viewModel = viewModel + lifecycle.addObserver(viewModel) + binding.lifecycleOwner = this + binding.recyclerView.layoutManager = LinearLayoutManager(activity) + viewModel.itens.observe(viewLifecycleOwner, Observer { it -> + it?.let { + with(recycler_view) { + this.layoutManager = LinearLayoutManager(activity) + this.addItemDecoration( + DividerItemDecoration( + this.context, + DividerItemDecoration.VERTICAL + ) + ) + this.adapter = MainAdapter(it) { item -> + activity?.let { + performTransition(item) + } + } + } + } + }) + return binding.root + } + + private fun initItems() { + val toggle = ActionBarDrawerToggle( + activity, + drawer_layout, + material_toolbar, + R.string.navigation_drawer_open, + R.string.navigation_drawer_close + ) + drawer_layout.addDrawerListener(toggle) + toggle.syncState() + + material_toolbar.title = context?.getString(R.string.app_name) + material_toolbar.setOnMenuItemClickListener { + when (it.itemId) { + R.id.search -> { + Toast.makeText(context, it.title.toString(), Toast.LENGTH_SHORT).show() + } + R.id._3d -> { + Toast.makeText(context, it.title.toString(), Toast.LENGTH_SHORT).show() + } + R.id.accelerator -> { + Toast.makeText(context, it.title.toString(), Toast.LENGTH_SHORT).show() + } + else -> { + Toast.makeText(context, it.title.toString(), Toast.LENGTH_SHORT).show() + } + } + true + } + + nav_view.setNavigationItemSelectedListener { + when (it.itemId) { + R.id.viagem -> { + Toast.makeText(context, it.title.toString(), Toast.LENGTH_SHORT).show() + } + R.id.som -> { + Toast.makeText(context, it.title.toString(), Toast.LENGTH_SHORT).show() + } + else -> { + Toast.makeText(context, it.title.toString(), Toast.LENGTH_SHORT).show() + } + } + drawer_layout.closeDrawer(GravityCompat.START) + false + } + } + + fun showFragmentWithTransition(newFragment: Fragment, tag: String?, sharedView: View, sharedElementName: String) { + val previousFragment = activity?.let { + it.supportFragmentManager.findFragmentById(R.id.content_frame) + } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + previousFragment!!.sharedElementReturnTransition = TransitionInflater.from(context).inflateTransition(R.transition.default_transition) + previousFragment!!.exitTransition = TransitionInflater.from(context).inflateTransition(android.R.transition.no_transition) + newFragment.sharedElementEnterTransition = TransitionInflater.from(context).inflateTransition(R.transition.default_transition) + newFragment.enterTransition = TransitionInflater.from(context).inflateTransition(android.R.transition.no_transition) + } + val fragmentTransaction = activity?.let { it.supportFragmentManager.beginTransaction() } + //val fragmentTransaction = fragmentManager.beginTransaction() + fragmentTransaction!!.replace(R.id.content_frame, newFragment, tag) + fragmentTransaction!!.addToBackStack(tag) + fragmentTransaction!!.addSharedElement(sharedView, sharedElementName) + fragmentTransaction!!.commit() + } + + private fun performTransition(item: Item) { + val previousFragment = activity?.let { + it.supportFragmentManager.findFragmentById(R.id.content_frame) + } + val nextFragment = DetailFragment(item) + val fragmentTransaction = activity?.let { it.supportFragmentManager.beginTransaction() } + + val exitFade = Fade() + exitFade.duration = FADE_DEFAULT_TIME + previousFragment!!.exitTransition = exitFade + + val enterTransitionSet = TransitionSet() + enterTransitionSet.addTransition( + TransitionInflater.from(activity).inflateTransition(android.R.transition.move) + ) + enterTransitionSet.duration = MOVE_DEFAULT_TIME + enterTransitionSet.startDelay = FADE_DEFAULT_TIME + nextFragment.sharedElementEnterTransition = enterTransitionSet + + val enterFade = Fade() + enterFade.startDelay = MOVE_DEFAULT_TIME + FADE_DEFAULT_TIME + enterFade.duration = FADE_DEFAULT_TIME + nextFragment.enterTransition = enterFade + + val logo: View = image_item + val name = name + val price = price + val desc = desc + fragmentTransaction?.let { + it.addSharedElement(logo, logo.transitionName) + //it.addSharedElement(name, name.transitionName) + //it.addSharedElement(price, price.transitionName) + //it.addSharedElement(desc, desc.transitionName) + it.replace(R.id.content_frame, nextFragment) + it.addToBackStack(MainFragment.toString()) + it.commitAllowingStateLoss() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/br/com/desafio/ui/model/Item.kt b/app/src/main/java/br/com/desafio/ui/model/Item.kt new file mode 100644 index 00000000..6b3795ea --- /dev/null +++ b/app/src/main/java/br/com/desafio/ui/model/Item.kt @@ -0,0 +1,22 @@ +package br.com.desafio.ui.model + +import android.widget.ImageView +import androidx.databinding.BindingAdapter +import br.com.desafio.util.Util +import com.nostra13.universalimageloader.core.ImageLoader +import java.io.Serializable + +@BindingAdapter("bind:imageUrl") +fun loadImage(view: ImageView, url: String?) { + ImageLoader.getInstance().displayImage(url, view, Util.getOptions()) +} + +data class Item( + var name: String, + var quantity: Int, + var stock: Int, + var image_url: String, + var price: Double, + var tax: Double, + var shipping: Int, + var description: String) : Serializable \ No newline at end of file diff --git a/app/src/main/java/br/com/desafio/ui/model/LoginUser.kt b/app/src/main/java/br/com/desafio/ui/model/LoginUser.kt new file mode 100644 index 00000000..69336e8f --- /dev/null +++ b/app/src/main/java/br/com/desafio/ui/model/LoginUser.kt @@ -0,0 +1,7 @@ +package br.com.desafio.ui.model + +class LoginUser(val strLogin: String?) { + + val isLoginLengthGreaterThan5: Boolean + get() = strLogin!!.length > 5 +} \ No newline at end of file diff --git a/app/src/main/java/br/com/desafio/util/AdapterItemsContract.kt b/app/src/main/java/br/com/desafio/util/AdapterItemsContract.kt new file mode 100644 index 00000000..2917171f --- /dev/null +++ b/app/src/main/java/br/com/desafio/util/AdapterItemsContract.kt @@ -0,0 +1,6 @@ +package br.com.desafio.util + +interface AdapterItemsContract { + + fun replaceItems(list: List<*>) +} \ No newline at end of file diff --git a/app/src/main/java/br/com/desafio/util/MainAdapter.kt b/app/src/main/java/br/com/desafio/util/MainAdapter.kt new file mode 100644 index 00000000..56641017 --- /dev/null +++ b/app/src/main/java/br/com/desafio/util/MainAdapter.kt @@ -0,0 +1,50 @@ +package br.com.desafio.util + +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView +import br.com.desafio.R +import br.com.desafio.ui.model.Item +import com.nostra13.universalimageloader.core.ImageLoader +import kotlinx.android.synthetic.main.main_item.view.* + +class MainAdapter(var list: List, private val onClickListener: (item: Item) -> Unit) : RecyclerView.Adapter(), + AdapterItemsContract { + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MainViewHolder { + return MainViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.main_item, parent, false), onClickListener) + } + + override fun onBindViewHolder(holder: MainViewHolder, position: Int) { + holder.setIsRecyclable(false) + holder.bind(list[position]) + } + + override fun replaceItems(items: List<*>) { + this.list = items as List + notifyDataSetChanged() + } + + override fun getItemCount() = list.size + + inner class MainViewHolder(view: View, private val onClickListener: (item: Item) -> Unit) : RecyclerView.ViewHolder(view) { + + private val image = itemView.image_item + private val name = itemView.name + private val stock = itemView.stock + private val price = itemView.price + private val desc = itemView.desc + + fun bind(item: Item){ + ImageLoader.getInstance().displayImage(item.image_url, image, Util.getOptions()) + name.text = item.name + stock.text = item.stock.toString() + " em estoque" + price.text = "R$ " + item.price + desc.text = item.description + itemView.setOnClickListener { + onClickListener.invoke(item) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/br/com/desafio/util/Util.kt b/app/src/main/java/br/com/desafio/util/Util.kt new file mode 100644 index 00000000..bcb15c83 --- /dev/null +++ b/app/src/main/java/br/com/desafio/util/Util.kt @@ -0,0 +1,46 @@ +package br.com.desafio.util + +import android.content.Context +import android.graphics.Bitmap +import android.widget.ImageView +import androidx.appcompat.app.AppCompatActivity +import androidx.fragment.app.Fragment +import br.com.desafio.R +import com.nostra13.universalimageloader.cache.disc.naming.Md5FileNameGenerator +import com.nostra13.universalimageloader.core.DisplayImageOptions +import com.nostra13.universalimageloader.core.ImageLoader +import com.nostra13.universalimageloader.core.ImageLoaderConfiguration +import com.nostra13.universalimageloader.core.assist.QueueProcessingType + +class Util { + + companion object{ + + fun initImageLoader(context: Context){ + val config = ImageLoaderConfiguration.Builder(context) + config.threadPriority(Thread.NORM_PRIORITY - 2) + config.denyCacheImageMultipleSizesInMemory() + config.diskCacheFileNameGenerator(Md5FileNameGenerator()) + config.diskCacheSize(50 * 1024 * 1024) // 50 MiB + config.tasksProcessingOrder(QueueProcessingType.LIFO) + config.writeDebugLogs() + ImageLoader.getInstance().init(config.build()) + } + + fun getOptions(): DisplayImageOptions? { + return DisplayImageOptions.Builder() + .showImageOnLoading(R.drawable.ic_search_24px) + .showImageForEmptyUri(R.drawable.ic_search_24px) + .showImageOnFail(R.drawable.ic_search_24px) + .cacheInMemory(true) + .cacheOnDisk(true) + .considerExifParams(true) + .bitmapConfig(Bitmap.Config.RGB_565) + .build() + } + + fun AppCompatActivity.addFragmentTo(containerId: Int, fragment: Fragment, tag: String = "") { + supportFragmentManager.beginTransaction().add(containerId, fragment, tag).commit() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/br/com/desafio/vm/DetailViewModel.kt b/app/src/main/java/br/com/desafio/vm/DetailViewModel.kt new file mode 100644 index 00000000..f73b2930 --- /dev/null +++ b/app/src/main/java/br/com/desafio/vm/DetailViewModel.kt @@ -0,0 +1,57 @@ +package br.com.desafio.vm + +import android.app.Application +import android.util.Log +import android.view.View +import android.widget.Toast +import androidx.databinding.ObservableField +import androidx.lifecycle.* +import br.com.desafio.R + +class DetailViewModel(private val application: Application) : ViewModel(), LifecycleObserver, View.OnClickListener { + + var resultImageUrl = ObservableField() + var name = ObservableField() + var price = ObservableField() + var description = ObservableField() + var voltar = MutableLiveData() + + override fun onClick(v: View) { + when(v.id){ + R.id.remove -> {Toast.makeText(application.applicationContext, "Remove item", Toast.LENGTH_SHORT).show()} + R.id.clean -> {voltar.value = true} + R.id.search -> {Toast.makeText(application.applicationContext, "Search", Toast.LENGTH_SHORT).show()} + else -> {Toast.makeText(application.applicationContext, "More", Toast.LENGTH_SHORT).show()} + } + } + + @OnLifecycleEvent(Lifecycle.Event.ON_RESUME) + private fun onResume() { + Log.d("onResume", "onResume") + } + + @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE) + fun onPause() { + Log.d("onPause", "onPause") + } + + @OnLifecycleEvent(Lifecycle.Event.ON_START) + fun onStart() { + Log.d("onStart", "onStart") + } + + @OnLifecycleEvent(Lifecycle.Event.ON_CREATE) + fun onCreate() { + Log.d("onCreate", "onCreate") + } + + @OnLifecycleEvent(Lifecycle.Event.ON_STOP) + fun onStop() { + Log.d("onStop", "onStop") + } + + @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY) + fun onDestroy() { + Log.d("onDestroy", "onDestroy") + } +} \ No newline at end of file diff --git a/app/src/main/java/br/com/desafio/vm/LoginViewModel.kt b/app/src/main/java/br/com/desafio/vm/LoginViewModel.kt new file mode 100644 index 00000000..71bdad33 --- /dev/null +++ b/app/src/main/java/br/com/desafio/vm/LoginViewModel.kt @@ -0,0 +1,25 @@ +package br.com.desafio.vm + +import androidx.databinding.ObservableField +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import br.com.desafio.ui.model.LoginUser + +class LoginViewModel : ViewModel() { + + var login = ObservableField("") + var userMutableLiveData: MutableLiveData? = null + + val user: MutableLiveData + get() { + if (userMutableLiveData == null) { + userMutableLiveData = MutableLiveData() + } + return userMutableLiveData!! + } + + fun onClick() { + val loginUser = LoginUser(login.get()) + userMutableLiveData!!.value = loginUser + } +} \ No newline at end of file diff --git a/app/src/main/java/br/com/desafio/vm/MainViewModel.kt b/app/src/main/java/br/com/desafio/vm/MainViewModel.kt new file mode 100644 index 00000000..988d22c2 --- /dev/null +++ b/app/src/main/java/br/com/desafio/vm/MainViewModel.kt @@ -0,0 +1,90 @@ +package br.com.desafio.vm + +import android.app.Application +import android.util.Log +import android.view.View +import android.widget.Toast +import androidx.databinding.ObservableBoolean +import androidx.databinding.ObservableField +import androidx.lifecycle.* +import br.com.desafio.R +import br.com.desafio.ui.model.Item +import br.com.desafio.http.ItemDataSource + +class MainViewModel(private val repository: ItemDataSource, private val application: Application) : ViewModel(), LifecycleObserver, View.OnClickListener { + + var itens = MutableLiveData>() + val showLoading = ObservableBoolean(false) + val msg = ObservableField() + var contador = ObservableField() + var valorTotal = MutableLiveData() + var valorSubtotal = MutableLiveData() + var valorShipping = MutableLiveData() + var valorTax = MutableLiveData() + + fun toLoad() { + showLoading.set(true) + msg.set("") + repository.listAllObjects({ items -> + itens.postValue(items) + if (items.isEmpty()) { + msg.set(application.getString(R.string.empty)) + } else { + contador.set(items.size.toString() + " itens no carrinho!") + somar(items) + showLoading.set(false) + } + }, { + msg.set(application.getString(R.string.failed)) + showLoading.set(false) + }) + } + + private fun somar(itens: List) { + var totalPrice = itens.sumByDouble { it.price } + var totalTax = itens.sumByDouble { it.tax } + var totalShip = itens.sumBy { it.shipping } + valorTotal.value = "R$ ${totalPrice.plus(totalTax)}" + valorSubtotal.value = "R$ $totalPrice" + valorShipping.value = totalShip.toString() + valorTax.value = "R$ $totalTax" + } + + override fun onClick(v: View) { + when(v.id) { + R.id.btcheckout -> {Toast.makeText(application.applicationContext, "Check Out", Toast.LENGTH_SHORT).show()} + else -> {Toast.makeText(application.applicationContext, "XXXX", Toast.LENGTH_SHORT).show()} + } + } + + @OnLifecycleEvent(Lifecycle.Event.ON_RESUME) + private fun onResume() { + Log.d("onResume", "onResume") + } + + @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE) + fun onPause() { + Log.d("onPause", "onPause") + } + + @OnLifecycleEvent(Lifecycle.Event.ON_START) + fun onStart() { + Log.d("onStart", "onStart") + toLoad() + } + + @OnLifecycleEvent(Lifecycle.Event.ON_CREATE) + fun onCreate() { + Log.d("onCreate", "onCreate") + } + + @OnLifecycleEvent(Lifecycle.Event.ON_STOP) + fun onStop() { + Log.d("onStop", "onStop") + } + + @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY) + fun onDestroy() { + Log.d("onDestroy", "onDestroy") + } +} \ No newline at end of file diff --git a/app/src/main/res/anim/slide_in_left.xml b/app/src/main/res/anim/slide_in_left.xml new file mode 100644 index 00000000..69c92cf9 --- /dev/null +++ b/app/src/main/res/anim/slide_in_left.xml @@ -0,0 +1,9 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/anim/slide_in_right.xml b/app/src/main/res/anim/slide_in_right.xml new file mode 100644 index 00000000..a8aa6511 --- /dev/null +++ b/app/src/main/res/anim/slide_in_right.xml @@ -0,0 +1,24 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/anim/slide_out_left.xml b/app/src/main/res/anim/slide_out_left.xml new file mode 100644 index 00000000..a71b458d --- /dev/null +++ b/app/src/main/res/anim/slide_out_left.xml @@ -0,0 +1,24 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/anim/slide_out_right.xml b/app/src/main/res/anim/slide_out_right.xml new file mode 100644 index 00000000..4dab5ae8 --- /dev/null +++ b/app/src/main/res/anim/slide_out_right.xml @@ -0,0 +1,24 @@ + + + + + + \ 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 00000000..2b068d11 --- /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/border.xml b/app/src/main/res/drawable/border.xml new file mode 100644 index 00000000..871d8d2f --- /dev/null +++ b/app/src/main/res/drawable/border.xml @@ -0,0 +1,15 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_3d_rotation_24px.xml b/app/src/main/res/drawable/ic_3d_rotation_24px.xml new file mode 100644 index 00000000..a830dfc9 --- /dev/null +++ b/app/src/main/res/drawable/ic_3d_rotation_24px.xml @@ -0,0 +1,13 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_accelerator_24px.xml b/app/src/main/res/drawable/ic_accelerator_24px.xml new file mode 100644 index 00000000..cc2b877b --- /dev/null +++ b/app/src/main/res/drawable/ic_accelerator_24px.xml @@ -0,0 +1,12 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_airplanemode_active_black_24dp.xml b/app/src/main/res/drawable/ic_airplanemode_active_black_24dp.xml new file mode 100644 index 00000000..e2469678 --- /dev/null +++ b/app/src/main/res/drawable/ic_airplanemode_active_black_24dp.xml @@ -0,0 +1,16 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_audiotrack_black_24dp.xml b/app/src/main/res/drawable/ic_audiotrack_black_24dp.xml new file mode 100644 index 00000000..9ed284b3 --- /dev/null +++ b/app/src/main/res/drawable/ic_audiotrack_black_24dp.xml @@ -0,0 +1,12 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_beach_access_black_24dp.xml b/app/src/main/res/drawable/ic_beach_access_black_24dp.xml new file mode 100644 index 00000000..eee535e0 --- /dev/null +++ b/app/src/main/res/drawable/ic_beach_access_black_24dp.xml @@ -0,0 +1,12 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_clear_black_24dp.xml b/app/src/main/res/drawable/ic_clear_black_24dp.xml new file mode 100644 index 00000000..ede4b710 --- /dev/null +++ b/app/src/main/res/drawable/ic_clear_black_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_dashboard_24px.xml b/app/src/main/res/drawable/ic_dashboard_24px.xml new file mode 100644 index 00000000..ed85cff7 --- /dev/null +++ b/app/src/main/res/drawable/ic_dashboard_24px.xml @@ -0,0 +1,12 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_drawer_menu_24px.xml b/app/src/main/res/drawable/ic_drawer_menu_24px.xml new file mode 100644 index 00000000..d6ff6c08 --- /dev/null +++ b/app/src/main/res/drawable/ic_drawer_menu_24px.xml @@ -0,0 +1,13 @@ + + + + + \ 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 00000000..07d5da9c --- /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_more_vert_black_24dp.xml b/app/src/main/res/drawable/ic_more_vert_black_24dp.xml new file mode 100644 index 00000000..5176d8a4 --- /dev/null +++ b/app/src/main/res/drawable/ic_more_vert_black_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_search_24px.xml b/app/src/main/res/drawable/ic_search_24px.xml new file mode 100644 index 00000000..1480dd1b --- /dev/null +++ b/app/src/main/res/drawable/ic_search_24px.xml @@ -0,0 +1,12 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_login.xml b/app/src/main/res/layout/activity_login.xml new file mode 100644 index 00000000..b0f34229 --- /dev/null +++ b/app/src/main/res/layout/activity_login.xml @@ -0,0 +1,53 @@ + + + + + + + + + + + + + +