diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..99d1893 --- /dev/null +++ b/.gitignore @@ -0,0 +1,101 @@ +# Created by https://www.gitignore.io/api/androidstudio +### AndroidStudio ### +# Covers files to be ignored for android development using Android Studio. +# Built application files +*.apk +*.ap_ +# Files for the ART/Dalvik VM +*.dex +# Java class files +*.class +# Generated files +bin/ +gen/ +out/ +# Gradle files +.gradle +.gradle/ +build/ +# Signing files +.signing/ +# Local configuration file (sdk path, etc) +local.properties +# Proguard folder generated by Eclipse +proguard/ +# Log Files +*.log +# Android Studio +/*/build/ +/*/local.properties +/*/out +/*/*/build +/*/*/production +captures/ +.navigation/ +*.ipr +*~ +*.swp +# Android Patch +gen-external-apklibs +# External native build folder generated in Android Studio 2.2 and later +.externalNativeBuild +# NDK +obj/ +# IntelliJ IDEA +*.iml +*.iws +/out/ +# User-specific configurations +.idea/caches/ +.idea/libraries/ +.idea/shelf/ +.idea/workspace.xml +.idea/tasks.xml +.idea/.name +.idea/compiler.xml +.idea/copyright/profiles_settings.xml +.idea/encodings.xml +.idea/misc.xml +.idea/modules.xml +.idea/scopes/scope_settings.xml +.idea/dictionaries +.idea/vcs.xml +.idea/jsLibraryMappings.xml +.idea/datasources.xml +.idea/dataSources.ids +.idea/sqlDataSources.xml +.idea/dynamic.xml +.idea/uiDesigner.xml +# OS-specific files +.DS_Store +.DS_Store? +._* +.Spotlight-V100 +.Trashes +ehthumbs.db +Thumbs.db +# Legacy Eclipse project files +.classpath +.project +.cproject +.settings/ +# Mobile Tools for Java (J2ME) +.mtj.tmp/ +# Package Files # +*.war +*.ear +# virtual machine crash logs (Reference: http://www.java.com/en/download/help/error_hotspot.xml) +hs_err_pid* +## Plugin-specific files: +# mpeltonen/sbt-idea plugin +.idea_modules/ +# JIRA plugin +atlassian-ide-plugin.xml +# Mongo Explorer plugin +.idea/mongoSettings.xml +### AndroidStudio Patch ### +!/gradle/wrapper/gradle-wrapper.jar +# End of https://www.gitignore.io/api/androidstudio +/activationapp/gradleBuild +*.aab +/.idea/workspace.xml___jb_tmp___ \ No newline at end of file diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml new file mode 100644 index 0000000..1bec35e --- /dev/null +++ b/.idea/codeStyles/Project.xml @@ -0,0 +1,10 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml new file mode 100644 index 0000000..79ee123 --- /dev/null +++ b/.idea/codeStyles/codeStyleConfig.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/.idea/gradle.xml b/.idea/gradle.xml new file mode 100644 index 0000000..7ac24c7 --- /dev/null +++ b/.idea/gradle.xml @@ -0,0 +1,18 @@ + + + + + + \ No newline at end of file diff --git a/.idea/runConfigurations.xml b/.idea/runConfigurations.xml new file mode 100644 index 0000000..7f68460 --- /dev/null +++ b/.idea/runConfigurations.xml @@ -0,0 +1,12 @@ + + + + + + \ No newline at end of file diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..796b96d --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 0000000..184c511 --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,42 @@ +apply plugin: 'com.android.application' + +apply plugin: 'kotlin-android' + +apply plugin: 'kotlin-android-extensions' + +android { + compileSdkVersion 28 + defaultConfig { + applicationId "com.example.mobilecodingchallenge" + minSdkVersion 27 + targetSdkVersion 28 + versionCode 1 + versionName "1.0" + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + } + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } +} + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar']) + implementation"org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" + implementation 'androidx.appcompat:appcompat:1.0.2' + implementation 'androidx.core:core-ktx:1.0.2' + implementation 'androidx.constraintlayout:constraintlayout:1.1.3' + + testImplementation 'junit:junit:4.12' + androidTestImplementation 'androidx.test:runner:1.2.0' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' + implementation 'com.android.support:recyclerview-v7:28.0.0' + implementation 'com.squareup.moshi:moshi-kotlin:1.8.0' + implementation 'com.android.volley:volley:1.1.0' + implementation 'com.github.bumptech.glide:glide:4.9.0' + annotationProcessor 'com.github.bumptech.glide:compiler:4.9.0' + + +} diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..f1b4245 --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile diff --git a/app/src/androidTest/java/com/example/mobilecodingchallenge/ExampleInstrumentedTest.kt b/app/src/androidTest/java/com/example/mobilecodingchallenge/ExampleInstrumentedTest.kt new file mode 100644 index 0000000..871ff36 --- /dev/null +++ b/app/src/androidTest/java/com/example/mobilecodingchallenge/ExampleInstrumentedTest.kt @@ -0,0 +1,24 @@ +package com.example.mobilecodingchallenge + +import androidx.test.InstrumentationRegistry +import androidx.test.runner.AndroidJUnit4 + +import org.junit.Test +import org.junit.runner.RunWith + +import org.junit.Assert.* + +/** + * Instrumented test, which will execute on an Android device. + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +@RunWith(AndroidJUnit4::class) +class ExampleInstrumentedTest { + @Test + fun useAppContext() { + // Context of the app under test. + val appContext = InstrumentationRegistry.getTargetContext() + assertEquals("com.example.mobilecodingchallenge", appContext.packageName) + } +} diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..bb202e3 --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/com/example/mobilecodingchallenge/Adapter/GitHubRepoRecycleAdapter.kt b/app/src/main/java/com/example/mobilecodingchallenge/Adapter/GitHubRepoRecycleAdapter.kt new file mode 100644 index 0000000..e39c0c8 --- /dev/null +++ b/app/src/main/java/com/example/mobilecodingchallenge/Adapter/GitHubRepoRecycleAdapter.kt @@ -0,0 +1,70 @@ +package com.example.mobilecodingchallenge + +import android.content.Context +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.ImageView +import android.widget.TextView +import androidx.recyclerview.widget.RecyclerView +import com.bumptech.glide.Glide + +class GitHubRepoRecycleAdapter(val context: Context, var gitHubRepo: MutableList) : RecyclerView.Adapter() { + + override fun onBindViewHolder(holder: Holder, position: Int) { + holder.bindItems(gitHubRepo[position],context) + } + + override fun getItemCount(): Int { + return gitHubRepo.count() + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): Holder { + val view = LayoutInflater.from(parent.context).inflate(R.layout.githubrepo_list_item, parent, false) + return Holder(view) + } + + inner class Holder(itemView: View) : RecyclerView.ViewHolder(itemView) { + val repoName = itemView.findViewById(R.id.repoName) + val repoDescription = itemView.findViewById(R.id.repoDescription) + val avatarImage = itemView.findViewById(R.id.avatarImage) + val ownerName = itemView.findViewById(R.id.ownerName) + val starCount = itemView.findViewById(R.id.starCounter) + + fun bindItems(gitHubItem: Items, context: Context) { + repoName.text = gitHubItem.repoName + repoDescription.text = gitHubItem.repoDescription + + Glide.with(context).load(gitHubItem.repoOwner?.avatar).into(avatarImage) + ownerName.text = gitHubItem.repoOwner?.login + starCount.text = gitHubItem.repoStars?.toDouble()?.let { formatPoints(it) } + } + } + + + fun setData(items: List) { + this.gitHubRepo = items.toMutableList() + notifyDataSetChanged() + } + + fun addMoreData(items: List) { + this.gitHubRepo.addAll(items) + notifyDataSetChanged() + } + + // Helper Methods + fun formatPoints(number: Double) : String { + var numberString = "" + if (Math.abs(number / 1000000) > 1) { + numberString = String.format("%.1fm",(number / 1000000)) + + } else if (Math.abs(number / 1000) > 1) { + numberString = String.format("%.1fk",(number / 1000)) + + } else { + numberString = number.toString() + } + return numberString + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/example/mobilecodingchallenge/Controller/MainActivity.kt b/app/src/main/java/com/example/mobilecodingchallenge/Controller/MainActivity.kt new file mode 100644 index 0000000..de1da2d --- /dev/null +++ b/app/src/main/java/com/example/mobilecodingchallenge/Controller/MainActivity.kt @@ -0,0 +1,78 @@ +package com.example.mobilecodingchallenge + +import android.icu.text.SimpleDateFormat +import android.icu.util.Calendar +import androidx.appcompat.app.AppCompatActivity +import android.os.Bundle +import android.view.View +import android.widget.Toast +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import kotlinx.android.synthetic.main.activity_main.* + +class MainActivity : AppCompatActivity() { + + lateinit var adapter: GitHubRepoRecycleAdapter + var gitHubRepo: List = listOf() + private var isServiceRunning = false + private var page = 1 + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_main) + + adapter = GitHubRepoRecycleAdapter(this,gitHubRepo.toMutableList()) + gitHubRepoListView.adapter = adapter + gitHubRepoListView.layoutManager = LinearLayoutManager(this) + gitHubRepoListView.setHasFixedSize(true) + + loadData(getQueryString()) + + gitHubRepoListView.addOnScrollListener(object: RecyclerView.OnScrollListener() { + override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) { + super.onScrolled(recyclerView, dx, dy) + val linearLayoutManager = recyclerView + .layoutManager as LinearLayoutManager? + + if (!isServiceRunning && linearLayoutManager!!.itemCount <= linearLayoutManager.findLastVisibleItemPosition() + 2) { + page = page + 1 + loadData(getQueryString(page)) + } + } + + }) + } + + fun loadData(queryString: String) { + progressBar.visibility = View.VISIBLE + isServiceRunning = true + WebService.callService(this,queryString,{response -> + isServiceRunning = false + progressBar.visibility = View.INVISIBLE + if (gitHubRepo.count() == 0) { + gitHubRepo = response.gitHubRepo + adapter.setData(response.gitHubRepo) + } else { + adapter.addMoreData(response.gitHubRepo) + } + },{ + Toast.makeText(this,it,Toast.LENGTH_LONG).show() + isServiceRunning = false + progressBar.visibility = View.INVISIBLE + }) + } + + fun getQueryString(page: Int = 1) : String { + val date = Calendar.getInstance() + date.add(Calendar.DATE, -30) + + val formatter = SimpleDateFormat("yyyy-MM-dd") + val thirtyDaysBeforeTodayInString = formatter.format(date) + + var queryString = "q=created:%3E$thirtyDaysBeforeTodayInString&sort=stars&order=desc" + if (page > 1) { + queryString = "$queryString&page=$page" + } + return queryString + } +} diff --git a/app/src/main/java/com/example/mobilecodingchallenge/Lib/ServiceSupport/Endpoint.kt b/app/src/main/java/com/example/mobilecodingchallenge/Lib/ServiceSupport/Endpoint.kt new file mode 100644 index 0000000..c70d50b --- /dev/null +++ b/app/src/main/java/com/example/mobilecodingchallenge/Lib/ServiceSupport/Endpoint.kt @@ -0,0 +1,16 @@ +package com.example.mobilecodingchallenge + +import com.squareup.moshi.JsonAdapter +import com.squareup.moshi.Moshi + +interface Endpoint { + + fun method(): Int //.raw RequestMethod + fun url(): String + fun classType(): Class + fun adapter(moshi: Moshi): JsonAdapter? = null + fun headers(): Map = emptyMap() + fun body(): String = "" + fun bodyContentType(): String? = null + +} \ No newline at end of file diff --git a/app/src/main/java/com/example/mobilecodingchallenge/Lib/ServiceSupport/MoshiRequest.kt b/app/src/main/java/com/example/mobilecodingchallenge/Lib/ServiceSupport/MoshiRequest.kt new file mode 100644 index 0000000..fae1e80 --- /dev/null +++ b/app/src/main/java/com/example/mobilecodingchallenge/Lib/ServiceSupport/MoshiRequest.kt @@ -0,0 +1,128 @@ +package com.example.mobilecodingchallenge + +import com.android.volley.* +import com.android.volley.toolbox.HttpHeaderParser +import com.android.volley.toolbox.JsonRequest +import com.example.mobilecodingchallenge.ErrorParser.errorFromResponse +import com.squareup.moshi.Json +import com.squareup.moshi.JsonAdapter +import com.squareup.moshi.JsonClass +import com.squareup.moshi.Moshi +import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory +import java.io.UnsupportedEncodingException +import java.lang.Error +import java.nio.charset.Charset + +open class MoshiJsonRequest(endpoint: Endpoint, listener: Response.Listener, + errorListener: Response.ErrorListener) : JsonRequest(endpoint.method(), endpoint.url(), endpoint.body(), listener, errorListener) { + private val mListener: Response.Listener + private var mMoshi: Moshi = Moshi.Builder() + .add(KotlinJsonAdapterFactory()) + .build() + private val tAdapter: JsonAdapter + private val mBody: String + private val mHeaders: Map + private val mContentType: String? + private val defaultContentType = "application/json; charset=UTF-8" + + init { + if (endpoint.adapter(mMoshi) == null) { + tAdapter = mMoshi.adapter(endpoint.classType()) + } else { + val adapter = endpoint.adapter(mMoshi) as JsonAdapter + tAdapter = adapter + } + mListener = listener + mBody = endpoint.body() + mContentType = endpoint.bodyContentType() + mHeaders = endpoint.headers() + } + + override fun getBodyContentType(): String { + if (mContentType != null) { + return mContentType + } + return defaultContentType + } + + @Throws(AuthFailureError::class) + override fun getHeaders(): Map { + return mHeaders + } + + override fun parseNetworkResponse(response: NetworkResponse): Response { + try { + val jsonString = String( + response.data ?: ByteArray(0), + Charset.forName(HttpHeaderParser.parseCharset(response.headers))) + val parsedObject = tAdapter.fromJson(jsonString) + if (parsedObject == null) { + val error = errorFromResponse(jsonString) + if (error != null) { + return Response.error(error) + } else { + return Response.error(VolleyError("Unrecognized Response")) + } + } + return Response.success(parsedObject, + HttpHeaderParser.parseCacheHeaders(response)) + } catch (e: UnsupportedEncodingException) { + return Response.error(ParseError(e)) + } catch (je: Error) { + return Response.error(ParseError(je)) + } + } + + override fun deliverResponse(response: T) = mListener.onResponse(response) +} + + @JsonClass(generateAdapter = true) + data class APIErrorResponse(@Json(name = "SUCCESS") val error: GitHubError?) + + @JsonClass(generateAdapter = true) + data class GitHubError(@Json(name = "error_code") val errorCode: String?, @Json(name = "error_message") val errorMessage: String?, @Json(name = "error_summary") val errorSummary: String?, @Json(name = "status") val status: Int?) { + + fun volleyError(): VolleyError? { + if (errorMessage != null) else return null + return VolleyError(errorMessage) + } + } + + object ErrorParser { + private var mMoshi: Moshi = Moshi.Builder() + .add(KotlinJsonAdapterFactory()) + .build() + + fun errorFromResponse(jsonString: String): VolleyError? { + if (jsonString.contains("SUCCESS", true)) { + val errorResponseAdapter = mMoshi.adapter(APIErrorResponse(null).javaClass) + val errorResponse = errorResponseAdapter.fromJson(jsonString) + return errorResponse?.error?.volleyError() + } else { + val errorAdapter = mMoshi.adapter(GitHubError(null, null, null, null).javaClass) + val error = errorAdapter.fromJson(jsonString) + return error?.volleyError() + } + } + + fun errorMessageFrom(volleyError: VolleyError): String? { + try { + val jsonString = String( + volleyError.networkResponse?.data ?: ByteArray(0), + Charset.forName(HttpHeaderParser.parseCharset(volleyError.networkResponse?.headers))) + println(jsonString) + if (jsonString.contains("SUCCESS", true)){ + val errorResponseAdapter = mMoshi.adapter(APIErrorResponse(null).javaClass) + val errorResponse = errorResponseAdapter.fromJson(jsonString) + return errorResponse?.error?.errorMessage + } else { + val errorAdapter = mMoshi.adapter(GitHubError(null, null, null, null).javaClass) + val error = errorAdapter.fromJson(jsonString) + return error?.errorMessage + } + } + catch (e: Exception) { + return e.localizedMessage + } + } + } \ No newline at end of file diff --git a/app/src/main/java/com/example/mobilecodingchallenge/Model/GitHubRepo.kt b/app/src/main/java/com/example/mobilecodingchallenge/Model/GitHubRepo.kt new file mode 100644 index 0000000..51bc7e5 --- /dev/null +++ b/app/src/main/java/com/example/mobilecodingchallenge/Model/GitHubRepo.kt @@ -0,0 +1,22 @@ +package com.example.mobilecodingchallenge + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +@JsonClass(generateAdapter = true) +data class GitHubRepo(@Json(name = "total_count") var totalCounts: Int = 0, @Json(name = "items") var gitHubRepo: List = listOf()) { +} + +@JsonClass(generateAdapter = true) +data class Items(@Json(name = "id") var repoId: String? = null, + @Json(name = "name") var repoName: String? = null, + @Json(name = "description") var repoDescription: String? = null, + @Json(name = "owner") var repoOwner: RepoOwner? = RepoOwner("",""), + @Json(name = "stargazers_count") var repoStars: Int? = 0) { + +} + +@JsonClass(generateAdapter = true) +data class RepoOwner(@Json(name = "login") var login: String, + @Json(name = "avatar_url") var avatar: String) { +} \ No newline at end of file diff --git a/app/src/main/java/com/example/mobilecodingchallenge/Services/WebSericeEndpoint.kt b/app/src/main/java/com/example/mobilecodingchallenge/Services/WebSericeEndpoint.kt new file mode 100644 index 0000000..825c08d --- /dev/null +++ b/app/src/main/java/com/example/mobilecodingchallenge/Services/WebSericeEndpoint.kt @@ -0,0 +1,23 @@ +package com.example.mobilecodingchallenge + +import com.android.volley.Request + +class WebSericeEndpoint(val queryString: String): Endpoint { + + override fun method(): Int { + return Request.Method.GET + } + + override fun url(): String { + return "https://api.github.com/search/repositories?" + queryString + } + + override fun classType(): Class { + return GitHubRepo().javaClass + } + + override fun headers(): Map { + return super.headers().plus(mapOf("Content-Type" to "application/json; charset=UTF-8")) + } + +} diff --git a/app/src/main/java/com/example/mobilecodingchallenge/Services/WebService.kt b/app/src/main/java/com/example/mobilecodingchallenge/Services/WebService.kt new file mode 100644 index 0000000..2a6d096 --- /dev/null +++ b/app/src/main/java/com/example/mobilecodingchallenge/Services/WebService.kt @@ -0,0 +1,41 @@ +package com.example.mobilecodingchallenge + +import android.content.Context +import com.android.volley.Response +import com.android.volley.toolbox.Volley + +object WebService { + + fun callService(context: Context, queryString: String, completion: (GitHubRepo) -> Unit, failure: (String) -> Unit) { + + val endpoint = WebSericeEndpoint(queryString) + + val gitHubRepoRequest: MoshiJsonRequest = MoshiJsonRequest( + endpoint = endpoint, + listener = Response.Listener { response -> + val repo = response + if (repo.totalCounts == null || repo.totalCounts == 0) { + failure("No Records Available") + } + completion(repo) + }, + errorListener = Response.ErrorListener { error -> + if (error != null) else { + failure("Received unknown error") + } + val message = error.message + + val errorMsg = ErrorParser.errorMessageFrom(error) + if (message == null) { + if (errorMsg != null){ + failure(errorMsg) + } else { + failure("Received unknown error") + } + } else { + failure(message) + } + }) + Volley.newRequestQueue(context).add(gitHubRepoRequest) + } +} \ No newline at end of file diff --git a/app/src/main/res/drawable-hdpi/screen.png b/app/src/main/res/drawable-hdpi/screen.png new file mode 100755 index 0000000..a91f19c Binary files /dev/null and b/app/src/main/res/drawable-hdpi/screen.png differ diff --git a/app/src/main/res/drawable-ldpi/screen.png b/app/src/main/res/drawable-ldpi/screen.png new file mode 100755 index 0000000..276d361 Binary files /dev/null and b/app/src/main/res/drawable-ldpi/screen.png differ diff --git a/app/src/main/res/drawable-mdpi/screen.png b/app/src/main/res/drawable-mdpi/screen.png new file mode 100755 index 0000000..45c2ec1 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/screen.png differ diff --git a/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml new file mode 100644 index 0000000..6348baa --- /dev/null +++ b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + diff --git a/app/src/main/res/drawable-xhdpi/screen.png b/app/src/main/res/drawable-xhdpi/screen.png new file mode 100755 index 0000000..0d6f1da Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/screen.png differ diff --git a/app/src/main/res/drawable-xxhdpi/screen.png b/app/src/main/res/drawable-xxhdpi/screen.png new file mode 100755 index 0000000..b81e8d5 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/screen.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/screen.png b/app/src/main/res/drawable-xxxhdpi/screen.png new file mode 100755 index 0000000..47c5d64 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/screen.png differ diff --git a/app/src/main/res/drawable/border.xml b/app/src/main/res/drawable/border.xml new file mode 100644 index 0000000..bd02eee --- /dev/null +++ b/app/src/main/res/drawable/border.xml @@ -0,0 +1,9 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 0000000..a0ad202 --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,74 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/screen.png b/app/src/main/res/drawable/screen.png new file mode 100755 index 0000000..a91f19c Binary files /dev/null and b/app/src/main/res/drawable/screen.png differ diff --git a/app/src/main/res/drawable/splash.xml b/app/src/main/res/drawable/splash.xml new file mode 100644 index 0000000..9234dc8 --- /dev/null +++ b/app/src/main/res/drawable/splash.xml @@ -0,0 +1,10 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/star.png b/app/src/main/res/drawable/star.png new file mode 100755 index 0000000..70e374b Binary files /dev/null and b/app/src/main/res/drawable/star.png differ diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml new file mode 100644 index 0000000..f5e5dea --- /dev/null +++ b/app/src/main/res/layout/activity_main.xml @@ -0,0 +1,39 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/githubrepo_list_item.xml b/app/src/main/res/layout/githubrepo_list_item.xml new file mode 100644 index 0000000..9517a08 --- /dev/null +++ b/app/src/main/res/layout/githubrepo_list_item.xml @@ -0,0 +1,57 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 0000000..bbd3e02 --- /dev/null +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml new file mode 100644 index 0000000..bbd3e02 --- /dev/null +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.png b/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000..898f3ed Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/app/src/main/res/mipmap-hdpi/ic_launcher_round.png new file mode 100644 index 0000000..dffca36 Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.png b/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000..64ba76f Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/app/src/main/res/mipmap-mdpi/ic_launcher_round.png new file mode 100644 index 0000000..dae5e08 Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000..e5ed465 Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png new file mode 100644 index 0000000..14ed0af Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000..b0907ca Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png new file mode 100644 index 0000000..d8ae031 Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000..2c18de9 Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png new file mode 100644 index 0000000..beed3cd Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml new file mode 100644 index 0000000..73a5a67 --- /dev/null +++ b/app/src/main/res/values/colors.xml @@ -0,0 +1,6 @@ + + + #f7f6f6 + #eeeeee + #000000 + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml new file mode 100644 index 0000000..76ebbf4 --- /dev/null +++ b/app/src/main/res/values/strings.xml @@ -0,0 +1,10 @@ + + Mobile Coding Challenge + Trending Repos + Repo Name + Repo Description + Owner Name + 0K + Star Image + Avatar + diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml new file mode 100644 index 0000000..d06fd45 --- /dev/null +++ b/app/src/main/res/values/styles.xml @@ -0,0 +1,12 @@ + + + + + + diff --git a/app/src/test/java/com/example/mobilecodingchallenge/ExampleUnitTest.kt b/app/src/test/java/com/example/mobilecodingchallenge/ExampleUnitTest.kt new file mode 100644 index 0000000..731a30c --- /dev/null +++ b/app/src/test/java/com/example/mobilecodingchallenge/ExampleUnitTest.kt @@ -0,0 +1,17 @@ +package com.example.mobilecodingchallenge + +import org.junit.Test + +import org.junit.Assert.* + +/** + * Example local unit test, which will execute on the development machine (host). + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +class ExampleUnitTest { + @Test + fun addition_isCorrect() { + assertEquals(4, 2 + 2) + } +} diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..438d280 --- /dev/null +++ b/build.gradle @@ -0,0 +1,28 @@ +// Top-level build file where you can add configuration options common to all sub-projects/modules. + +buildscript { + ext.kotlin_version = '1.3.41' + repositories { + google() + jcenter() + + } + dependencies { + classpath 'com.android.tools.build:gradle:3.4.2' + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + // NOTE: Do not place your application dependencies here; they belong + // in the individual module build.gradle files + } +} + +allprojects { + repositories { + google() + jcenter() + + } +} + +task clean(type: Delete) { + delete rootProject.buildDir +} diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..23339e0 --- /dev/null +++ b/gradle.properties @@ -0,0 +1,21 @@ +# Project-wide Gradle settings. +# IDE (e.g. Android Studio) users: +# Gradle settings configured through the IDE *will override* +# any settings specified in this file. +# For more details on how to configure your build environment visit +# http://www.gradle.org/docs/current/userguide/build_environment.html +# Specifies the JVM arguments used for the daemon process. +# The setting is particularly useful for tweaking memory settings. +org.gradle.jvmargs=-Xmx1536m +# When configured, Gradle will run in incubating parallel mode. +# This option should only be used with decoupled projects. More details, visit +# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects +# org.gradle.parallel=true +# AndroidX package structure to make it clearer which packages are bundled with the +# Android operating system, and which are packaged with your app's APK +# https://developer.android.com/topic/libraries/support-library/androidx-rn +android.useAndroidX=true +# Automatically convert third-party libraries to use AndroidX +android.enableJetifier=true +# Kotlin code style for this project: "official" or "obsolete": +kotlin.code.style=official diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..f6b961f Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..b7a2a6f --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Tue Aug 06 18:10:25 PKT 2019 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-5.1.1-all.zip diff --git a/gradlew b/gradlew new file mode 100755 index 0000000..cccdd3d --- /dev/null +++ b/gradlew @@ -0,0 +1,172 @@ +#!/usr/bin/env sh + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=$(save "$@") + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong +if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then + cd "$(dirname "$0")" +fi + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..e95643d --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,84 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 0000000..e7b4def --- /dev/null +++ b/settings.gradle @@ -0,0 +1 @@ +include ':app'