├── WaveExample ├── settings.gradle ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── app │ ├── src │ │ ├── main │ │ │ ├── res │ │ │ │ ├── mipmap-hdpi │ │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-mdpi │ │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xhdpi │ │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xxhdpi │ │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xxxhdpi │ │ │ │ │ └── ic_launcher.png │ │ │ │ ├── values │ │ │ │ │ ├── strings.xml │ │ │ │ │ ├── colors.xml │ │ │ │ │ ├── dimens.xml │ │ │ │ │ └── styles.xml │ │ │ │ ├── drawable-v21 │ │ │ │ │ └── selector_accent.xml │ │ │ │ ├── values-w820dp │ │ │ │ │ └── dimens.xml │ │ │ │ ├── drawable │ │ │ │ │ └── selector_accent.xml │ │ │ │ └── layout │ │ │ │ │ ├── act_project_list.xml │ │ │ │ │ └── item_product.xml │ │ │ ├── kotlin │ │ │ │ └── com │ │ │ │ │ └── derekcsm │ │ │ │ │ └── wave_example │ │ │ │ │ ├── _base │ │ │ │ │ └── Constants.java │ │ │ │ │ ├── _api │ │ │ │ │ ├── WaveApi.kt │ │ │ │ │ ├── ApiBuilder.kt │ │ │ │ │ └── OkhttpHeadersInterceptor.kt │ │ │ │ │ ├── projectlist │ │ │ │ │ ├── adapter │ │ │ │ │ │ ├── ProductsAdapterItem.kt │ │ │ │ │ │ ├── ProductViewHolder.kt │ │ │ │ │ │ └── ProductsAdapter.kt │ │ │ │ │ ├── ProductListContract.kt │ │ │ │ │ ├── ProductListPresenter.kt │ │ │ │ │ └── ProductListActivity.kt │ │ │ │ │ └── _model │ │ │ │ │ └── Product.kt │ │ │ └── AndroidManifest.xml │ │ ├── test │ │ │ └── java │ │ │ │ └── com │ │ │ │ └── derekcsm │ │ │ │ └── wave_example │ │ │ │ └── ExampleUnitTest.java │ │ └── androidTest │ │ │ └── java │ │ │ └── com │ │ │ └── derekcsm │ │ │ └── wave_example │ │ │ └── ExampleInstrumentedTest.java │ ├── proguard-rules.pro │ └── build.gradle ├── build.gradle ├── gradle.properties ├── gradlew.bat ├── .gitignore └── gradlew └── README.md /WaveExample/settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | -------------------------------------------------------------------------------- /WaveExample/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/derekcsm/mobile-challenge/master/WaveExample/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /WaveExample/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/derekcsm/mobile-challenge/master/WaveExample/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /WaveExample/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/derekcsm/mobile-challenge/master/WaveExample/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /WaveExample/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/derekcsm/mobile-challenge/master/WaveExample/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /WaveExample/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/derekcsm/mobile-challenge/master/WaveExample/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /WaveExample/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/derekcsm/mobile-challenge/master/WaveExample/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /WaveExample/app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Wave Example 3 | 4 | $%1$s 5 | 6 | -------------------------------------------------------------------------------- /WaveExample/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Mon Dec 28 10:00:20 PST 2015 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip 7 | -------------------------------------------------------------------------------- /WaveExample/app/src/main/res/drawable-v21/selector_accent.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 7 | -------------------------------------------------------------------------------- /WaveExample/app/src/main/kotlin/com/derekcsm/wave_example/_base/Constants.java: -------------------------------------------------------------------------------- 1 | package com.derekcsm.wave_example._base; 2 | 3 | public interface Constants { 4 | 5 | String endpoint = "api.waveapps.com"; 6 | String ACCESS_TOKEN = "6W9hcvwRvyyZgPu9Odq7ko8DSY8Nfm"; 7 | String BUSINESS_ID = "89746d57-c25f-4cec-9c63-34d7780b044b"; 8 | } 9 | -------------------------------------------------------------------------------- /WaveExample/app/src/main/res/values-w820dp/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 64dp 6 | 7 | -------------------------------------------------------------------------------- /WaveExample/app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #37dcd6 4 | #008a94 5 | #eb9e34 6 | 7 | #56eb9e34 8 | 9 | #de000000 10 | #de000000 11 | 12 | -------------------------------------------------------------------------------- /WaveExample/app/src/main/kotlin/com/derekcsm/wave_example/_api/WaveApi.kt: -------------------------------------------------------------------------------- 1 | package com.derekcsm.wave_example._api 2 | 3 | import com.derekcsm.wave_example._model.Product 4 | import retrofit2.http.GET 5 | import retrofit2.http.Path 6 | import rx.Observable 7 | 8 | interface WaveApi { 9 | 10 | @GET("businesses/{business_id}/products") 11 | fun getProducts(@Path("business_id") businessId: String): Observable> 12 | } -------------------------------------------------------------------------------- /WaveExample/app/src/main/kotlin/com/derekcsm/wave_example/projectlist/adapter/ProductsAdapterItem.kt: -------------------------------------------------------------------------------- 1 | package com.derekcsm.wave_example.projectlist.adapter 2 | 3 | import android.support.annotation.IntDef 4 | 5 | class ProductsAdapterItem(var `object`: T?, @ProductsAdapterItem.ViewType var viewType: Int) { 6 | 7 | companion object { 8 | const val PRODUCT = 0.toLong() 9 | } 10 | 11 | @IntDef(PRODUCT) 12 | annotation class ViewType 13 | } 14 | 15 | -------------------------------------------------------------------------------- /WaveExample/app/src/main/kotlin/com/derekcsm/wave_example/_model/Product.kt: -------------------------------------------------------------------------------- 1 | package com.derekcsm.wave_example._model 2 | 3 | import com.squareup.moshi.Json 4 | 5 | data class Product( 6 | var id: Int = 0, 7 | var url: String, 8 | var name: String, 9 | var price: Float = 0.toFloat(), 10 | var description: String? = null, 11 | @Json(name = "is_active") var isActive: Boolean = false, 12 | @Json(name = "date_created") var dateCreated: String, 13 | @Json(name = "date_modified") var dateModified: String 14 | ) -------------------------------------------------------------------------------- /WaveExample/app/src/test/java/com/derekcsm/wave_example/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package com.derekcsm.wave_example; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.*; 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * @see Testing documentation 11 | */ 12 | public class ExampleUnitTest { 13 | @Test 14 | public void addition_isCorrect() throws Exception { 15 | assertEquals(4, 2 + 2); 16 | } 17 | } -------------------------------------------------------------------------------- /WaveExample/app/src/main/res/drawable/selector_accent.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /WaveExample/app/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16dp 4 | 16dp 5 | 6 | 7 | 112sp 8 | 56sp 9 | 45sp 10 | 34sp 11 | 24sp 12 | 20sp 13 | 16sp 14 | 14sp 15 | 12sp 16 | 17 | -------------------------------------------------------------------------------- /WaveExample/app/src/main/kotlin/com/derekcsm/wave_example/projectlist/ProductListContract.kt: -------------------------------------------------------------------------------- 1 | package com.derekcsm.wave_example.projectlist 2 | 3 | import android.content.Context 4 | import android.os.Bundle 5 | import com.derekcsm.wave_example.projectlist.adapter.ProductsAdapter 6 | 7 | interface ProductListContract { 8 | 9 | interface View { 10 | fun getProductsAdapter(): ProductsAdapter 11 | 12 | fun showLoading() 13 | 14 | fun hideLoading() 15 | 16 | fun getContext(): Context 17 | } 18 | 19 | interface UserActionsListener { 20 | fun KEY_SAVED_PRODUCTS() : String 21 | 22 | fun fetchProductsFromApi() 23 | 24 | fun restoreInstanceState(savedState: Bundle) 25 | } 26 | 27 | } -------------------------------------------------------------------------------- /WaveExample/app/src/main/res/layout/act_project_list.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 11 | 12 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /WaveExample/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | 13 | 14 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /WaveExample/app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /Users/derekcs/Library/Android/sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /WaveExample/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext.kotlin_version = '1.0.5-2' 3 | repositories { 4 | jcenter() 5 | } 6 | dependencies { 7 | classpath 'com.android.tools.build:gradle:2.2.3' 8 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 9 | 10 | // NOTE: Do not place your application dependencies here; they belong 11 | // in the individual module build.gradle files 12 | } 13 | } 14 | 15 | allprojects { 16 | repositories { 17 | jcenter() 18 | maven { url 'https://oss.sonatype.org/content/repositories/snapshots/' } 19 | } 20 | } 21 | 22 | task clean(type: Delete) { 23 | delete rootProject.buildDir 24 | } 25 | 26 | ext { 27 | androidSupportVersion = '24.2.1' 28 | } 29 | -------------------------------------------------------------------------------- /WaveExample/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /WaveExample/gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | 3 | # IDE (e.g. Android Studio) users: 4 | # Gradle settings configured through the IDE *will override* 5 | # any settings specified in this file. 6 | 7 | # For more details on how to configure your build environment visit 8 | # http://www.gradle.org/docs/current/userguide/build_environment.html 9 | 10 | # Specifies the JVM arguments used for the daemon process. 11 | # The setting is particularly useful for tweaking memory settings. 12 | org.gradle.jvmargs=-Xmx1536m 13 | 14 | # When configured, Gradle will run in incubating parallel mode. 15 | # This option should only be used with decoupled projects. More details, visit 16 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 17 | # org.gradle.parallel=true 18 | -------------------------------------------------------------------------------- /WaveExample/app/src/main/res/layout/item_product.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 17 | 18 | 24 | 25 | -------------------------------------------------------------------------------- /WaveExample/app/src/androidTest/java/com/derekcsm/wave_example/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.derekcsm.wave_example; 2 | 3 | import android.content.Context; 4 | import android.support.test.InstrumentationRegistry; 5 | import android.support.test.runner.AndroidJUnit4; 6 | 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | 10 | import static org.junit.Assert.*; 11 | 12 | /** 13 | * Instrumentation test, which will execute on an Android device. 14 | * 15 | * @see Testing documentation 16 | */ 17 | @RunWith(AndroidJUnit4.class) 18 | public class ExampleInstrumentedTest { 19 | @Test 20 | public void useAppContext() throws Exception { 21 | // Context of the app under test. 22 | Context appContext = InstrumentationRegistry.getTargetContext(); 23 | 24 | assertEquals("com.derekcsm.wave_example", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /WaveExample/app/src/main/kotlin/com/derekcsm/wave_example/_api/ApiBuilder.kt: -------------------------------------------------------------------------------- 1 | package com.derekcsm.wave_example._api 2 | 3 | import com.derekcsm.wave_example._base.Constants 4 | import okhttp3.HttpUrl 5 | import okhttp3.OkHttpClient 6 | import retrofit2.Retrofit 7 | import retrofit2.adapter.rxjava.RxJavaCallAdapterFactory 8 | import retrofit2.converter.moshi.MoshiConverterFactory 9 | 10 | class ApiBuilder { 11 | 12 | fun create(): WaveApi { 13 | 14 | var okHttpClientBuilder: OkHttpClient.Builder = OkHttpClient.Builder() 15 | okHttpClientBuilder.addInterceptor(OkHttpHeadersInterceptor()) 16 | var okHttpClient = okHttpClientBuilder.build() 17 | 18 | val retrofit = Retrofit.Builder() 19 | .baseUrl(getBaseUrl()) 20 | .client(okHttpClient) 21 | .addCallAdapterFactory(RxJavaCallAdapterFactory.create()) 22 | .addConverterFactory(MoshiConverterFactory.create()) 23 | .build() 24 | 25 | return retrofit.create(WaveApi::class.java) 26 | } 27 | 28 | private fun getBaseUrl(): HttpUrl { 29 | return HttpUrl.Builder() 30 | .scheme("https") 31 | .host(Constants.endpoint) 32 | .build() 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /WaveExample/app/src/main/kotlin/com/derekcsm/wave_example/projectlist/adapter/ProductViewHolder.kt: -------------------------------------------------------------------------------- 1 | package com.derekcsm.wave_example.projectlist.adapter 2 | 3 | import android.content.Context 4 | import android.support.v7.widget.RecyclerView 5 | import android.view.LayoutInflater 6 | import android.view.View 7 | import android.view.ViewGroup 8 | import android.widget.TextView 9 | import butterknife.bindView 10 | import com.derekcsm.wave_example.R 11 | import com.derekcsm.wave_example._model.Product 12 | import java.text.NumberFormat 13 | 14 | class ProductViewHolder private constructor(itemView: View) : RecyclerView.ViewHolder(itemView) { 15 | 16 | val tvTitle: TextView by bindView(R.id.tv_product_title) 17 | val tvPrice: TextView by bindView(R.id.tv_price) 18 | 19 | init { 20 | } 21 | 22 | fun onBind(item: ProductsAdapterItem, listener: ProductsAdapter.ClickListener) { 23 | val product = item.`object` as Product 24 | 25 | tvTitle.text = product.name 26 | 27 | val formatter = NumberFormat.getNumberInstance() 28 | formatter.minimumFractionDigits = 2 29 | formatter.maximumFractionDigits = 2 30 | val formattedPrice = formatter.format(product.price) 31 | tvPrice.text = itemView.context.getString(R.string.price, formattedPrice) 32 | 33 | itemView.setOnClickListener { listener.onProductClicked(item) } 34 | } 35 | 36 | companion object { 37 | fun create(context: Context, viewGroup: ViewGroup): ProductViewHolder { 38 | return ProductViewHolder(LayoutInflater.from(context).inflate(R.layout.item_product, viewGroup, false)) 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /WaveExample/app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | apply plugin: 'kotlin-android' 3 | 4 | android { 5 | compileSdkVersion 24 6 | buildToolsVersion '23.0.3' 7 | defaultConfig { 8 | applicationId 'com.derekcsm.wave_example' 9 | minSdkVersion 19 10 | targetSdkVersion 24 11 | versionCode 1 12 | versionName '1.0' 13 | testInstrumentationRunner 'android.support.test.runner.AndroidJUnitRunner' 14 | } 15 | buildTypes { 16 | release { 17 | minifyEnabled false 18 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 19 | } 20 | } 21 | sourceSets { 22 | main.java.srcDirs += 'src/main/kotlin' 23 | } 24 | } 25 | 26 | dependencies { 27 | compile fileTree(dir: 'libs', include: ['*.jar']) 28 | androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { 29 | exclude group: 'com.android.support', module: 'support-annotations' 30 | }) 31 | 32 | // Kotlin 33 | compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" 34 | 35 | // RX 36 | compile 'io.reactivex:rxandroid:1.2.1' 37 | compile 'io.reactivex:rxjava:1.1.5' 38 | 39 | // Google Android / Support / Niceties 40 | compile "com.android.support:appcompat-v7:$androidSupportVersion" 41 | compile "com.android.support:support-v4:$androidSupportVersion" 42 | compile "com.android.support:recyclerview-v7:$androidSupportVersion" 43 | compile 'com.jakewharton:butterknife:7.0.1' 44 | compile 'com.jakewharton:kotterknife:0.1.0-SNAPSHOT' 45 | 46 | // Networking 47 | compile 'com.squareup.okhttp3:okhttp:3.5.0' 48 | compile 'com.squareup.retrofit2:retrofit:2.1.0' 49 | compile 'com.squareup.retrofit2:converter-moshi:2.1.0' 50 | compile "com.squareup.retrofit2:adapter-rxjava:2.1.0" 51 | 52 | // JSON 53 | compile 'com.squareup.moshi:moshi:1.2.0' 54 | 55 | // Unit testing 56 | testCompile 'junit:junit:4.12' 57 | } 58 | repositories { 59 | mavenCentral() 60 | } 61 | -------------------------------------------------------------------------------- /WaveExample/app/src/main/kotlin/com/derekcsm/wave_example/_api/OkhttpHeadersInterceptor.kt: -------------------------------------------------------------------------------- 1 | package com.derekcsm.wave_example._api 2 | 3 | import android.util.Log 4 | import com.derekcsm.wave_example._base.Constants 5 | import okhttp3.Interceptor 6 | import okhttp3.Response 7 | import java.io.IOException 8 | 9 | class OkHttpHeadersInterceptor constructor() : Interceptor { 10 | private val TAG = "OkHttpHeadersIntercept" 11 | 12 | @Throws(IOException::class) 13 | override fun intercept(chain: Interceptor.Chain): Response { 14 | val builder = chain.request().newBuilder() 15 | builder.addHeader("Connection", "Keep-Alive") 16 | builder.addHeader("Accept-Charset", "UTF-8") 17 | if (chain.request().header("Accept") == null) 18 | builder.addHeader("Accept", "application/json") 19 | builder.addHeader("Content-Type", "application/json") 20 | 21 | // add authorization header to request and proceed 22 | builder.addHeader("Authorization", "Bearer " + Constants.ACCESS_TOKEN) 23 | var response = chain.proceed(builder.build()) 24 | 25 | // -- TODO 26 | // -- everything below this point is not doing anything 27 | // -- if I were to implement Oauth2 correctly we'd have refresh/proceed setup 28 | 29 | Log.d(TAG, "intercept: response=" + response.code() + " url=" + response.request().url().toString()) 30 | if ((response.code() == 401) and !response.request().url().toString().contains("/oauth/token")) { 31 | //response = chain.proceed(retryOAuthAuthentication(builder)) 32 | } 33 | 34 | // Hypothetically speaking if after retrying auth, still 401 then notify unauthorized 35 | if ((response.code() == 401) and response.request().url().toString().contains("oauth/token")) { 36 | Log.d(TAG, "intercept NOTIFY UNAUTHORIZED: response=" + response.code() 37 | + " url=" + response.request().url().toString()) 38 | } 39 | return response 40 | } 41 | 42 | private fun retryOAuthAuthentication() { 43 | // in a perfect world this should refresh the auth token but I just don't have time in this example 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /WaveExample/app/src/main/kotlin/com/derekcsm/wave_example/projectlist/adapter/ProductsAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.derekcsm.wave_example.projectlist.adapter 2 | 3 | import android.support.v7.widget.RecyclerView 4 | import android.view.ViewGroup 5 | import com.derekcsm.wave_example._model.Product 6 | import java.util.* 7 | 8 | class ProductsAdapter(private val listener: ProductsAdapter.ClickListener) : 9 | RecyclerView.Adapter() { 10 | 11 | private var productsAdapterItems = ArrayList>() 12 | 13 | init { 14 | } 15 | 16 | fun addItems(ProductsAdapterItems: ArrayList>) { 17 | this.productsAdapterItems = ProductsAdapterItems 18 | } 19 | 20 | fun getItems(): ArrayList> { 21 | return productsAdapterItems 22 | } 23 | 24 | override fun getItemCount(): Int { 25 | return productsAdapterItems.size 26 | } 27 | 28 | override fun getItemViewType(position: Int): Int { 29 | return productsAdapterItems[position].viewType 30 | } 31 | 32 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { 33 | when (viewType.toLong()) { 34 | ProductsAdapterItem.PRODUCT -> return ProductViewHolder.create(parent.context, parent) 35 | else -> throw IllegalStateException("Incorrect view type: " + viewType) 36 | } 37 | } 38 | 39 | @Suppress("UNCHECKED_CAST") 40 | override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { 41 | val currentItem = productsAdapterItems[position] 42 | 43 | when (holder.itemViewType.toLong()) { 44 | ProductsAdapterItem.PRODUCT -> (holder as ProductViewHolder).onBind( 45 | currentItem as ProductsAdapterItem, listener) 46 | else -> throw IllegalStateException("Incorrect view type: " + holder.itemViewType) 47 | } 48 | } 49 | 50 | override fun onAttachedToRecyclerView(recyclerView: RecyclerView?) { 51 | super.onAttachedToRecyclerView(recyclerView) 52 | } 53 | 54 | interface ClickListener { 55 | fun onProductClicked(item: ProductsAdapterItem) 56 | } 57 | } -------------------------------------------------------------------------------- /WaveExample/gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /WaveExample/.gitignore: -------------------------------------------------------------------------------- 1 | # Created by https://www.gitignore.io 2 | 3 | ### Windows ### 4 | # Windows image file caches 5 | Thumbs.db 6 | ehthumbs.db 7 | 8 | # Folder config file 9 | Desktop.ini 10 | 11 | # Recycle Bin used on file shares 12 | $RECYCLE.BIN/ 13 | 14 | # Windows Installer files 15 | *.cab 16 | *.msi 17 | *.msm 18 | *.msp 19 | 20 | # Windows shortcuts 21 | *.lnk 22 | 23 | 24 | ### OSX ### 25 | .DS_Store 26 | .AppleDouble 27 | .LSOverride 28 | 29 | # Icon must end with two \r 30 | Icon 31 | 32 | 33 | # Thumbnails 34 | ._* 35 | 36 | # Files that might appear in the root of a volume 37 | .DocumentRevisions-V100 38 | .fseventsd 39 | .Spotlight-V100 40 | .TemporaryItems 41 | .Trashes 42 | .VolumeIcon.icns 43 | 44 | # Directories potentially created on remote AFP share 45 | .AppleDB 46 | .AppleDesktop 47 | Network Trash Folder 48 | Temporary Items 49 | .apdisk 50 | 51 | 52 | ### Linux ### 53 | *~ 54 | 55 | # KDE directory preferences 56 | .directory 57 | 58 | # Linux trash folder which might appear on any partition or disk 59 | .Trash-* 60 | 61 | 62 | ### Intellij ### 63 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm 64 | 65 | *.iml 66 | 67 | ## Directory-based project format: 68 | .idea/ 69 | # if you remove the above rule, at least ignore the following: 70 | 71 | # User-specific stuff: 72 | # .idea/workspace.xml 73 | # .idea/tasks.xml 74 | # .idea/dictionaries 75 | 76 | # Sensitive or high-churn files: 77 | # .idea/dataSources.ids 78 | # .idea/dataSources.xml 79 | # .idea/sqlDataSources.xml 80 | # .idea/dynamic.xml 81 | # .idea/uiDesigner.xml 82 | 83 | # Gradle: 84 | # .idea/gradle.xml 85 | # .idea/libraries 86 | 87 | # Mongo Explorer plugin: 88 | # .idea/mongoSettings.xml 89 | 90 | ## File-based project format: 91 | *.ipr 92 | *.iws 93 | 94 | ## Plugin-specific files: 95 | 96 | # IntelliJ 97 | /out/ 98 | 99 | # mpeltonen/sbt-idea plugin 100 | .idea_modules/ 101 | 102 | # JIRA plugin 103 | atlassian-ide-plugin.xml 104 | 105 | # Crashlytics plugin (for Android Studio and IntelliJ) 106 | com_crashlytics_export_strings.xml 107 | crashlytics.properties 108 | crashlytics-build.properties 109 | 110 | 111 | ### Android ### 112 | # Built application files 113 | *.apk 114 | *.ap_ 115 | 116 | # Files for the Dalvik VM 117 | *.dex 118 | 119 | # Java class files 120 | *.class 121 | 122 | # Generated files 123 | bin/ 124 | gen/ 125 | 126 | # Gradle files 127 | .gradle/ 128 | build/ 129 | /*/build/ 130 | 131 | # Local configuration file (sdk path, etc) 132 | local.properties 133 | 134 | # Proguard folder generated by Eclipse 135 | proguard/ 136 | 137 | # Log Files 138 | *.log 139 | 140 | ### Android Patch ### 141 | gen-external-apklibs -------------------------------------------------------------------------------- /WaveExample/app/src/main/kotlin/com/derekcsm/wave_example/projectlist/ProductListPresenter.kt: -------------------------------------------------------------------------------- 1 | package com.derekcsm.wave_example.projectlist 2 | 3 | import android.os.Bundle 4 | import android.util.Log 5 | import android.widget.Toast 6 | import com.derekcsm.wave_example._api.ApiBuilder 7 | import com.derekcsm.wave_example._api.WaveApi 8 | import com.derekcsm.wave_example._base.Constants 9 | import com.derekcsm.wave_example._model.Product 10 | import com.derekcsm.wave_example.projectlist.adapter.ProductsAdapterItem 11 | import com.squareup.moshi.Moshi 12 | import rx.Subscriber 13 | import rx.android.schedulers.AndroidSchedulers 14 | import rx.schedulers.Schedulers 15 | import java.util.* 16 | 17 | class ProductListPresenter constructor(mView: ProductListContract.View) : 18 | ProductListContract.UserActionsListener { 19 | 20 | private val TAG = "ProductListPresenter" 21 | private val KEY_SAVED_PRODUCTS = "saved_products" 22 | override fun KEY_SAVED_PRODUCTS(): String { 23 | return KEY_SAVED_PRODUCTS 24 | } 25 | private val mView: ProductListContract.View 26 | private val waveApi: WaveApi 27 | 28 | init { 29 | this.mView = mView 30 | waveApi = ApiBuilder().create() 31 | } 32 | 33 | override fun restoreInstanceState(savedState: Bundle) { 34 | var productsAdapterItems = ArrayList>() 35 | val stringProducts = savedState.getStringArrayList(KEY_SAVED_PRODUCTS) 36 | 37 | val moshi: Moshi = Moshi.Builder().build() 38 | val jsonAdapter = moshi.adapter(Product::class.java) 39 | 40 | var i = 0 41 | while (i < stringProducts.size) { 42 | 43 | val nProduct = jsonAdapter.fromJson(stringProducts.get(i)) 44 | 45 | val productAdapterItem = ProductsAdapterItem(nProduct, 46 | ProductsAdapterItem.PRODUCT.toInt()) 47 | productsAdapterItems.add(productAdapterItem) 48 | i++ 49 | } 50 | 51 | mView.getProductsAdapter().addItems(productsAdapterItems) 52 | mView.getProductsAdapter().notifyDataSetChanged() 53 | } 54 | 55 | override fun fetchProductsFromApi() { 56 | waveApi.getProducts(Constants.BUSINESS_ID) 57 | .subscribeOn(Schedulers.io()) 58 | .observeOn(AndroidSchedulers.mainThread()) 59 | .subscribe(object : Subscriber>() { 60 | override fun onCompleted() { 61 | Log.d(TAG, "onCompleted ") 62 | mView.hideLoading() 63 | } 64 | 65 | override fun onError(e: Throwable) { 66 | Log.d(TAG, "onError " + e.message) 67 | Toast.makeText(mView.getContext(), "Error loading: " + e.message, Toast.LENGTH_LONG).show() 68 | mView.hideLoading() 69 | } 70 | 71 | override fun onNext(products: List?) { 72 | Log.d(TAG, "onNext " + products) 73 | if (products != null) { 74 | populateAdapter(products) 75 | } 76 | } 77 | }) 78 | } 79 | 80 | private fun populateAdapter(products: List) { 81 | 82 | val productsAdapterItems = ArrayList>() 83 | 84 | var i = 0 85 | while (i < products.size) { 86 | val productAdapterItem = ProductsAdapterItem(products.get(i), 87 | ProductsAdapterItem.PRODUCT.toInt()) 88 | productsAdapterItems.add(productAdapterItem) 89 | i++ 90 | } 91 | 92 | mView.getProductsAdapter().addItems(productsAdapterItems) 93 | mView.getProductsAdapter().notifyDataSetChanged() 94 | } 95 | 96 | } -------------------------------------------------------------------------------- /WaveExample/app/src/main/kotlin/com/derekcsm/wave_example/projectlist/ProductListActivity.kt: -------------------------------------------------------------------------------- 1 | package com.derekcsm.wave_example.projectlist 2 | 3 | import android.content.Context 4 | import android.os.Bundle 5 | import android.support.v4.widget.SwipeRefreshLayout 6 | import android.support.v7.app.AppCompatActivity 7 | import android.support.v7.widget.LinearLayoutManager 8 | import android.support.v7.widget.RecyclerView 9 | import android.widget.Toast 10 | import butterknife.bindView 11 | import com.derekcsm.wave_example.R 12 | import com.derekcsm.wave_example._model.Product 13 | import com.derekcsm.wave_example.projectlist.adapter.ProductsAdapter 14 | import com.derekcsm.wave_example.projectlist.adapter.ProductsAdapterItem 15 | import com.squareup.moshi.Moshi 16 | import java.util.* 17 | 18 | 19 | class ProductListActivity : AppCompatActivity(), ProductListContract.View, 20 | ProductsAdapter.ClickListener, SwipeRefreshLayout.OnRefreshListener { 21 | 22 | private val TAG = "ProductListActivity" 23 | private lateinit var mActionsListener: ProductListContract.UserActionsListener 24 | 25 | val rvProductList: RecyclerView by bindView(R.id.rv_product_list) 26 | val swipeRefreshLayout: SwipeRefreshLayout by bindView(R.id.swiperefresh_product) 27 | 28 | private lateinit var mProductsAdapter: ProductsAdapter 29 | override fun getProductsAdapter(): ProductsAdapter { 30 | return mProductsAdapter 31 | } 32 | 33 | override fun onCreate(savedState: Bundle?) { 34 | super.onCreate(savedState) 35 | setContentView(R.layout.act_project_list) 36 | 37 | mActionsListener = ProductListPresenter(this) 38 | 39 | setupProductAdapter() 40 | swipeRefreshLayout.setOnRefreshListener(this) 41 | swipeRefreshLayout.setColorSchemeResources(R.color.colorAccent, R.color.colorPrimary) 42 | 43 | if (savedState == null) { 44 | showLoading() 45 | mActionsListener.fetchProductsFromApi() 46 | } else if (savedState.containsKey(mActionsListener.KEY_SAVED_PRODUCTS())) { 47 | // restore state 48 | mActionsListener.restoreInstanceState(savedState) 49 | 50 | } else { 51 | showLoading() 52 | mActionsListener.fetchProductsFromApi() 53 | } 54 | } 55 | 56 | override fun onSaveInstanceState(outState: Bundle?) { 57 | val stringProducts = ArrayList() 58 | 59 | val moshi: Moshi = Moshi.Builder().build() 60 | val jsonAdapter = moshi.adapter(Product::class.java) 61 | 62 | var i = 0 63 | while (i < mProductsAdapter.getItems().size) { 64 | stringProducts.add(jsonAdapter.toJson(mProductsAdapter.getItems().get(i).`object` as Product)) 65 | i++ 66 | } 67 | 68 | outState!!.putStringArrayList(mActionsListener.KEY_SAVED_PRODUCTS(), stringProducts) 69 | super.onSaveInstanceState(outState) 70 | } 71 | 72 | private fun setupProductAdapter() { 73 | mProductsAdapter = ProductsAdapter(this) 74 | rvProductList.layoutManager = LinearLayoutManager(this) 75 | rvProductList.adapter = mProductsAdapter 76 | } 77 | 78 | override fun onRefresh() { 79 | showLoading() 80 | mActionsListener.fetchProductsFromApi() 81 | } 82 | 83 | override fun showLoading() { 84 | swipeRefreshLayout.isRefreshing = true 85 | } 86 | 87 | override fun hideLoading() { 88 | swipeRefreshLayout.isRefreshing = false 89 | } 90 | 91 | override fun onProductClicked(item: ProductsAdapterItem) { 92 | val product = item.`object` as Product 93 | Toast.makeText(this, product.name, Toast.LENGTH_SHORT).show() 94 | } 95 | 96 | override fun getContext(): Context { 97 | return this 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Built by @derekcsm 2 | 3 | Might have got a bit carried away, but hey - this was actually pretty fun and it took around 4ish hours in total. 4 | 5 | The idea here was to build out a really clean app that lives within Android gracefully and feels polished to a decent degree. I built the app using Kotlin with a bunch of nice helper libraries like RXJava to keep things clear and composable. The network request is made through an extensible Retrofit API interface that uses and OkHttp Interceptor to add auth headers to all requests. The Activity uses an MVP pattern, and saves instance state using Moshi Json serializations through a String ArrayList stored in the outState bundle. Errors will display as toast messages, and you can even pull to referesh! 6 | 7 | > Should be noted that proper Oauth2 would implement token refreshing, I've added some notes to the OkHttpHeadersInterceptor class 8 | 9 | --- 10 | # Wave Software Development Challenge 11 | Applicants for the [Mobile engineer](https://wave.bamboohr.co.uk/jobs/view.php?id=6) role at Wave must complete the following challenge, and submit a solution prior to the onsite interview. 12 | 13 | The purpose of this exercise is to create something that we can work on together during the onsite. We do this so that you get a chance to collaborate with Wavers during the interview in a situation where you know something better than us (it's your code, after all!) 14 | 15 | There isn't a hard deadline for this exercise; take as long as you need to complete it. However, in terms of total time spent actively working on the challenge, we ask that you not spend more than a few hours, as we value your time and are happy to leave things open to discussion in the onsite interview. 16 | 17 | You can write your app using your favorite language, tools, platform, etc. Whether that means something native or something hybrid is completely up to you. 18 | 19 | Send your submission to [dev.careers@waveapps.com](dev.careers@waveapps.com). Feel free to email [dev.careers@waveapps.com](dev.careers@waveapps.com) if you have any questions. 20 | 21 | ## Submission Instructions 22 | 1. Fork this project on github. You will need to create an account if you don't already have one. 23 | 1. Complete the project as described below within your fork. 24 | 1. Push all of your changes to your fork on github and submit a pull request. 25 | 1. You should also email [dev.careers@waveapps.com](dev.careers@waveapps.com) and your recruiter to let them know you have submitted a solution. Make sure to include your github username in your email (so we can match applicants with pull requests.) 26 | 27 | ## Alternate Submission Instructions (if you don't want to publicize completing the challenge) 28 | 1. Clone the repository. 29 | 1. Complete your project as described below within your local repository. 30 | 1. Email a patch file to [dev.careers@waveapps.com](dev.careers@waveapps.com). 31 | 32 | ## Project Description 33 | In this project, we're going to be creating a simple app that shows a Wave user the products that they can charge for on their invoices. 34 | 35 | You'll be using the public Wave API in this challenge. You can find the documentation [here](http://docs.waveapps.io/). You will specifically be interested in [the products endpoint](http://docs.waveapps.io/endpoints/products.html#get--businesses-business_id-products-), and [using an access token with the API](http://docs.waveapps.io/oauth/index.html#use-the-access-token-to-access-the-api). 36 | 37 | Your Wave contact will supply you with a business ID and a Wave API token before you begin. 38 | 39 | ### What your application must do: 40 | 41 | 1. Your app must retrieve the list of products for the specific business ID sent to you by your Wave contact 42 | 1. The list of products should be fetched and shown to the user in a list view when the app is launched. 43 | 1. Each item in the list view should show the product name and price (formatted as a dollar amount.) 44 | 45 | You are not required to add any interactivity to the app -- i.e. you do not need to send the user to a detail view when they touch one of the list items. 46 | 47 | Your app is allowed to render nothing if there is no internet connection when it loads. 48 | 49 | Once you're done, please submit a paragraph or two in your `README` about what you are particularly proud of in your implementation, and why. 50 | 51 | ## Evaluation 52 | Evaluation of your submission will be based on the following criteria. 53 | 54 | 1. Did your application fulfill the basic requirements? 55 | 1. Did you document the method for setting up and running your application? 56 | 1. Did you follow the instructions for submission? 57 | -------------------------------------------------------------------------------- /WaveExample/gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # Attempt to set APP_HOME 46 | # Resolve links: $0 may be a link 47 | PRG="$0" 48 | # Need this for relative symlinks. 49 | while [ -h "$PRG" ] ; do 50 | ls=`ls -ld "$PRG"` 51 | link=`expr "$ls" : '.*-> \(.*\)$'` 52 | if expr "$link" : '/.*' > /dev/null; then 53 | PRG="$link" 54 | else 55 | PRG=`dirname "$PRG"`"/$link" 56 | fi 57 | done 58 | SAVED="`pwd`" 59 | cd "`dirname \"$PRG\"`/" >/dev/null 60 | APP_HOME="`pwd -P`" 61 | cd "$SAVED" >/dev/null 62 | 63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 64 | 65 | # Determine the Java command to use to start the JVM. 66 | if [ -n "$JAVA_HOME" ] ; then 67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 68 | # IBM's JDK on AIX uses strange locations for the executables 69 | JAVACMD="$JAVA_HOME/jre/sh/java" 70 | else 71 | JAVACMD="$JAVA_HOME/bin/java" 72 | fi 73 | if [ ! -x "$JAVACMD" ] ; then 74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 75 | 76 | Please set the JAVA_HOME variable in your environment to match the 77 | location of your Java installation." 78 | fi 79 | else 80 | JAVACMD="java" 81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 82 | 83 | Please set the JAVA_HOME variable in your environment to match the 84 | location of your Java installation." 85 | fi 86 | 87 | # Increase the maximum file descriptors if we can. 88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 89 | MAX_FD_LIMIT=`ulimit -H -n` 90 | if [ $? -eq 0 ] ; then 91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 92 | MAX_FD="$MAX_FD_LIMIT" 93 | fi 94 | ulimit -n $MAX_FD 95 | if [ $? -ne 0 ] ; then 96 | warn "Could not set maximum file descriptor limit: $MAX_FD" 97 | fi 98 | else 99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 100 | fi 101 | fi 102 | 103 | # For Darwin, add options to specify how the application appears in the dock 104 | if $darwin; then 105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 106 | fi 107 | 108 | # For Cygwin, switch paths to Windows format before running java 109 | if $cygwin ; then 110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 112 | JAVACMD=`cygpath --unix "$JAVACMD"` 113 | 114 | # We build the pattern for arguments to be converted via cygpath 115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 116 | SEP="" 117 | for dir in $ROOTDIRSRAW ; do 118 | ROOTDIRS="$ROOTDIRS$SEP$dir" 119 | SEP="|" 120 | done 121 | OURCYGPATTERN="(^($ROOTDIRS))" 122 | # Add a user-defined pattern to the cygpath arguments 123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 125 | fi 126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 127 | i=0 128 | for arg in "$@" ; do 129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 131 | 132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 134 | else 135 | eval `echo args$i`="\"$arg\"" 136 | fi 137 | i=$((i+1)) 138 | done 139 | case $i in 140 | (0) set -- ;; 141 | (1) set -- "$args0" ;; 142 | (2) set -- "$args0" "$args1" ;; 143 | (3) set -- "$args0" "$args1" "$args2" ;; 144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 150 | esac 151 | fi 152 | 153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 154 | function splitJvmOpts() { 155 | JVM_OPTS=("$@") 156 | } 157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 159 | 160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 161 | --------------------------------------------------------------------------------