diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b8966d3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +# Ignore ./idea folder +.idea/* diff --git a/.gradle/5.1.1/executionHistory/executionHistory.bin b/.gradle/5.1.1/executionHistory/executionHistory.bin new file mode 100644 index 0000000..d2afa85 Binary files /dev/null and b/.gradle/5.1.1/executionHistory/executionHistory.bin differ diff --git a/.gradle/5.1.1/executionHistory/executionHistory.lock b/.gradle/5.1.1/executionHistory/executionHistory.lock new file mode 100644 index 0000000..e9268c1 Binary files /dev/null and b/.gradle/5.1.1/executionHistory/executionHistory.lock differ diff --git a/.gradle/5.1.1/fileChanges/last-build.bin b/.gradle/5.1.1/fileChanges/last-build.bin new file mode 100644 index 0000000..f76dd23 Binary files /dev/null and b/.gradle/5.1.1/fileChanges/last-build.bin differ diff --git a/.gradle/5.1.1/fileContent/fileContent.lock b/.gradle/5.1.1/fileContent/fileContent.lock new file mode 100644 index 0000000..86d63f1 Binary files /dev/null and b/.gradle/5.1.1/fileContent/fileContent.lock differ diff --git a/.gradle/5.1.1/fileHashes/fileHashes.bin b/.gradle/5.1.1/fileHashes/fileHashes.bin new file mode 100644 index 0000000..4407d26 Binary files /dev/null and b/.gradle/5.1.1/fileHashes/fileHashes.bin differ diff --git a/.gradle/5.1.1/fileHashes/fileHashes.lock b/.gradle/5.1.1/fileHashes/fileHashes.lock new file mode 100644 index 0000000..0937f84 Binary files /dev/null and b/.gradle/5.1.1/fileHashes/fileHashes.lock differ diff --git a/.gradle/5.1.1/fileHashes/resourceHashesCache.bin b/.gradle/5.1.1/fileHashes/resourceHashesCache.bin new file mode 100644 index 0000000..e28c521 Binary files /dev/null and b/.gradle/5.1.1/fileHashes/resourceHashesCache.bin differ diff --git a/.gradle/5.1.1/gc.properties b/.gradle/5.1.1/gc.properties new file mode 100644 index 0000000..e69de29 diff --git a/.gradle/5.1.1/javaCompile/classAnalysis.bin b/.gradle/5.1.1/javaCompile/classAnalysis.bin new file mode 100644 index 0000000..e095d6c Binary files /dev/null and b/.gradle/5.1.1/javaCompile/classAnalysis.bin differ diff --git a/.gradle/5.1.1/javaCompile/javaCompile.lock b/.gradle/5.1.1/javaCompile/javaCompile.lock new file mode 100644 index 0000000..a26aff0 Binary files /dev/null and b/.gradle/5.1.1/javaCompile/javaCompile.lock differ diff --git a/.gradle/5.1.1/javaCompile/taskHistory.bin b/.gradle/5.1.1/javaCompile/taskHistory.bin new file mode 100644 index 0000000..dc43ee0 Binary files /dev/null and b/.gradle/5.1.1/javaCompile/taskHistory.bin differ diff --git a/.gradle/buildOutputCleanup/buildOutputCleanup.lock b/.gradle/buildOutputCleanup/buildOutputCleanup.lock new file mode 100644 index 0000000..8e321ae Binary files /dev/null and b/.gradle/buildOutputCleanup/buildOutputCleanup.lock differ diff --git a/.gradle/buildOutputCleanup/cache.properties b/.gradle/buildOutputCleanup/cache.properties new file mode 100644 index 0000000..5e3f930 --- /dev/null +++ b/.gradle/buildOutputCleanup/cache.properties @@ -0,0 +1,2 @@ +#Wed May 15 11:39:32 WET 2019 +gradle.version=5.1.1 diff --git a/.gradle/buildOutputCleanup/outputFiles.bin b/.gradle/buildOutputCleanup/outputFiles.bin new file mode 100644 index 0000000..3fdbdd6 Binary files /dev/null and b/.gradle/buildOutputCleanup/outputFiles.bin differ diff --git a/.gradle/vcs-1/gc.properties b/.gradle/vcs-1/gc.properties new file mode 100644 index 0000000..e69de29 diff --git a/CodeChallenge.iml b/CodeChallenge.iml new file mode 100644 index 0000000..51883cb --- /dev/null +++ b/CodeChallenge.iml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/README.md b/README.md index f83520b..35a6230 100644 --- a/README.md +++ b/README.md @@ -44,10 +44,16 @@ Here's what each element represents : ![alt text](https://raw.githubusercontent.com/hiddenfounders/mobile-coding-challenge/master/row-explained.png) -## Technologies to use -Choose whatever mobile platform you're most familiar with. +## Technologies +For this project, I still stuck to the Android Jetpack libraries: -* For iOS, feel free to use Swift or Objective-C. -* For Android, feel free to use Kotlin or Java. +* Butter Knife --> Viewbinding library for android. +* Retrofit --> REST client library for android, it makes it easy to retrieve JSON data via API. +* Paging library --> Facilitates loading data from network data source on-demand, and also allows the app to work with large data sets. +* ViewModel library --> Stores and manages UI-related data in a lifecycle conscious way. +* Glide --> Used for loading images efficiently. +## How to run project +Follow the indications on the Google Developpers Doc [here](https://developer.android.com/training/basics/firstapp/running-app). + 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/app.iml b/app/app.iml new file mode 100644 index 0000000..09b6829 --- /dev/null +++ b/app/app.iml @@ -0,0 +1,196 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 0000000..0ab33c8 --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,61 @@ +apply plugin: 'com.android.application' + +android { + compileSdkVersion 28 + defaultConfig { + applicationId "io.psisoft.codechallenge" + minSdkVersion 15 + targetSdkVersion 28 + versionCode 1 + versionName "1.0" + testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" + } + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + compileOptions { + sourceCompatibility = '1.8' + targetCompatibility = '1.8' + } +} + +dependencies { + + def butter_knife_version = "8.8.1" + def retrofit_version = "2.4.0" + def paging_version = "1.0.0" + def view_model_version = "1.1.0" + def glide_version = "4.9.0" + + // Adding Butter Knife + implementation "com.jakewharton:butterknife:$butter_knife_version" + annotationProcessor "com.jakewharton:butterknife-compiler:$butter_knife_version" + + //Adding Retrofit + implementation "com.squareup.retrofit2:retrofit:$retrofit_version" + implementation "com.squareup.retrofit2:converter-gson:$retrofit_version" + + // Adding paging + implementation "android.arch.paging:runtime:$paging_version" + + // Adding view model + implementation "android.arch.lifecycle:extensions:$view_model_version" + implementation "android.arch.lifecycle:viewmodel:$view_model_version" + + // Adding Glide + implementation "com.github.bumptech.glide:glide:$glide_version" + annotationProcessor "com.github.bumptech.glide:compiler:$glide_version" + + + implementation fileTree(dir: 'libs', include: ['*.jar']) + implementation 'com.android.support:appcompat-v7:28.0.0' + implementation 'com.android.support.constraint:constraint-layout:1.1.3' + implementation 'com.android.support:design:28.0.0' + implementation 'com.android.support:support-v4:28.0.0' + testImplementation 'junit:junit:4.12' + androidTestImplementation 'com.android.support.test:runner:1.0.2' + androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' +} 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/io/psisoft/codechallenge/ExampleInstrumentedTest.java b/app/src/androidTest/java/io/psisoft/codechallenge/ExampleInstrumentedTest.java new file mode 100644 index 0000000..3f696d6 --- /dev/null +++ b/app/src/androidTest/java/io/psisoft/codechallenge/ExampleInstrumentedTest.java @@ -0,0 +1,26 @@ +package io.psisoft.codechallenge; + +import android.content.Context; +import android.support.test.InstrumentationRegistry; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.junit.Assert.*; + +/** + * Instrumented test, which will execute on an Android device. + * + * @see Testing documentation + */ +@RunWith(AndroidJUnit4.class) +public class ExampleInstrumentedTest { + @Test + public void useAppContext() { + // Context of the app under test. + Context appContext = InstrumentationRegistry.getTargetContext(); + + assertEquals("io.psisoft.codechallenge", appContext.getPackageName()); + } +} diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..0a7605e --- /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/io/psisoft/codechallenge/adapter/RepositoryAdapter.java b/app/src/main/java/io/psisoft/codechallenge/adapter/RepositoryAdapter.java new file mode 100644 index 0000000..6ab5f10 --- /dev/null +++ b/app/src/main/java/io/psisoft/codechallenge/adapter/RepositoryAdapter.java @@ -0,0 +1,55 @@ +package io.psisoft.codechallenge.adapter; + +import android.arch.paging.PagedListAdapter; +import android.support.annotation.NonNull; +import android.support.v7.util.DiffUtil; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import io.psisoft.codechallenge.R; +import io.psisoft.codechallenge.model.GithubRepository; + +public class RepositoryAdapter extends PagedListAdapter { + + public RepositoryAdapter() { + super(DIFF_CALLBACK); + } + + private static DiffUtil.ItemCallback DIFF_CALLBACK = + new DiffUtil.ItemCallback() { + + @Override + public boolean areItemsTheSame(GithubRepository oldGithubRepository, + GithubRepository newGithubRepository) { + return oldGithubRepository.getId() == newGithubRepository.getId(); + } + + @Override + public boolean areContentsTheSame(GithubRepository oldGithubRepository, + GithubRepository newGithubRepository) { + return oldGithubRepository.equals(newGithubRepository); + } + }; + + + @NonNull + @Override + public RepositoryViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int i) { + // Inflate the item repository view + View view = LayoutInflater.from(viewGroup.getContext()) + .inflate(R.layout.repository_item_view, viewGroup, false); + return new RepositoryViewHolder(view); + } + + @Override + public void onBindViewHolder(@NonNull RepositoryViewHolder repositoryViewHolder, int i) { + + GithubRepository item = getItem(i); + + if (item != null) { + // Bind data into the view holder + repositoryViewHolder.bindData(item); + } + } +} diff --git a/app/src/main/java/io/psisoft/codechallenge/adapter/RepositoryViewHolder.java b/app/src/main/java/io/psisoft/codechallenge/adapter/RepositoryViewHolder.java new file mode 100644 index 0000000..c67c1ab --- /dev/null +++ b/app/src/main/java/io/psisoft/codechallenge/adapter/RepositoryViewHolder.java @@ -0,0 +1,84 @@ +package io.psisoft.codechallenge.adapter; + +import android.content.Context; +import android.support.annotation.NonNull; +import android.support.v7.widget.RecyclerView; +import android.view.View; +import android.widget.ImageView; +import android.widget.TextView; + +import com.bumptech.glide.Glide; + +import java.util.Map; +import java.util.NavigableMap; +import java.util.TreeMap; + +import butterknife.BindView; +import butterknife.ButterKnife; +import io.psisoft.codechallenge.R; +import io.psisoft.codechallenge.model.GithubRepository; + +public class RepositoryViewHolder extends RecyclerView.ViewHolder { + + @BindView(R.id.repository_name) + TextView repositoryName; + @BindView(R.id.repository_description) + TextView repositoryDescription; + @BindView(R.id.owner_image) + ImageView ownerImage; + @BindView(R.id.owner_name) + TextView ownerName; + @BindView(R.id.repository_stars) + TextView repositoryStars; + + private Context context; + private static final NavigableMap suffixes = new TreeMap<>(); + + static { + suffixes.put(1_000L, "k"); + suffixes.put(1_000_000L, "M"); + suffixes.put(1_000_000_000L, "G"); + suffixes.put(1_000_000_000_000L, "T"); + suffixes.put(1_000_000_000_000_000L, "P"); + suffixes.put(1_000_000_000_000_000_000L, "E"); + } + + + public RepositoryViewHolder(@NonNull View itemView) { + super(itemView); + ButterKnife.bind(this, itemView); + + context = itemView.getContext(); + } + + // Bind data to the ViewHolder + public void bindData(GithubRepository githubRepository) { + + repositoryName.setText(githubRepository.getName()); + repositoryDescription.setText(githubRepository.getDescription()); + repositoryStars.setText(format(githubRepository.getStargazersCount())); + + ownerName.setText(githubRepository.getOwner().getLogin()); + + Glide.with(context) + .load(githubRepository.getOwner().getAvatar_url()) + .centerCrop() + .placeholder(R.drawable.ic_image_placeholder) + .into(ownerImage); + } + + // Custom number formatting + private String format(long value) { + if (value == Long.MIN_VALUE) return format(Long.MIN_VALUE + 1); + if (value < 0) return "-" + format(-value); + if (value < 1000) return Long.toString(value); + + Map.Entry e = suffixes.floorEntry(value); + Long divideBy = e.getKey(); + String suffix = e.getValue(); + + long truncated = value / (divideBy / 10); + boolean hasDecimal = truncated < 100 && (truncated / 10d) != (truncated / 10); + return hasDecimal ? (truncated / 10d) + suffix : (truncated / 10) + suffix; + } +} diff --git a/app/src/main/java/io/psisoft/codechallenge/api/ApiHelper.java b/app/src/main/java/io/psisoft/codechallenge/api/ApiHelper.java new file mode 100644 index 0000000..675523b --- /dev/null +++ b/app/src/main/java/io/psisoft/codechallenge/api/ApiHelper.java @@ -0,0 +1,27 @@ +package io.psisoft.codechallenge.api; + +import io.psisoft.codechallenge.model.GithubResponse; +import retrofit2.Call; +import retrofit2.http.GET; +import retrofit2.http.Query; + +public interface ApiHelper { + + /** + * Get github repositories from API. + * + * @param order desc or asc order + * @param sort sorting attribute + * @param createdDate page size + * @param page page number + * + * @return GithubRepositories + */ + @GET("search/repositories") + Call getGithubRepositories( + @Query("q") String createdDate, + @Query("sort") String sort, + @Query("order") String order, + @Query("page") int page + ); +} diff --git a/app/src/main/java/io/psisoft/codechallenge/api/ApiService.java b/app/src/main/java/io/psisoft/codechallenge/api/ApiService.java new file mode 100644 index 0000000..8202b3b --- /dev/null +++ b/app/src/main/java/io/psisoft/codechallenge/api/ApiService.java @@ -0,0 +1,34 @@ +package io.psisoft.codechallenge.api; + +import retrofit2.Retrofit; +import retrofit2.converter.gson.GsonConverterFactory; + +/* Singleton class + * for Retrofit + */ +public class ApiService { + + private static final String BASE_URL = "https://api.github.com/"; + private static ApiService mInstance; + private Retrofit retrofit; + + // private constructor + private ApiService(){ + retrofit = new Retrofit.Builder() + .baseUrl(BASE_URL) + .addConverterFactory(GsonConverterFactory.create()) + .build(); + } + + // Get Retrofit instance + public static synchronized ApiService getInstance(){ + if(mInstance == null){ + mInstance = new ApiService(); + } + return mInstance; + } + + public ApiHelper getApiHelper(){ + return retrofit.create(ApiHelper.class); + } +} diff --git a/app/src/main/java/io/psisoft/codechallenge/api/NetworkState.java b/app/src/main/java/io/psisoft/codechallenge/api/NetworkState.java new file mode 100644 index 0000000..ec58ada --- /dev/null +++ b/app/src/main/java/io/psisoft/codechallenge/api/NetworkState.java @@ -0,0 +1,34 @@ +package io.psisoft.codechallenge.api; + +public class NetworkState { + + private final Status status; + private final String msg; + + public static final NetworkState INITIALIZED; + public static final NetworkState NOT_INITIALIZED; + public static final NetworkState LOADING; + public static final NetworkState LOADED; + public static final NetworkState FAILED; + + NetworkState(Status status, String msg) { + this.status = status; + this.msg = msg; + } + + static { + INITIALIZED = new NetworkState(Status.INITIALIZED, "Initialized"); + NOT_INITIALIZED = new NetworkState(Status.NOT_INITIALIZED, "Not initialized properly"); + LOADED = new NetworkState(Status.SUCCESS, "Success"); + LOADING = new NetworkState(Status.RUNNING, "Running"); + FAILED = new NetworkState(Status.FAILED, "Problem was accrued"); + } + + public Status getStatus() { + return status; + } + + public String getMsg() { + return msg; + } +} diff --git a/app/src/main/java/io/psisoft/codechallenge/api/Status.java b/app/src/main/java/io/psisoft/codechallenge/api/Status.java new file mode 100644 index 0000000..b90a79f --- /dev/null +++ b/app/src/main/java/io/psisoft/codechallenge/api/Status.java @@ -0,0 +1,9 @@ +package io.psisoft.codechallenge.api; + +public enum Status { + INITIALIZED, + NOT_INITIALIZED, + RUNNING, + SUCCESS, + FAILED +} \ No newline at end of file diff --git a/app/src/main/java/io/psisoft/codechallenge/model/GithubRepository.java b/app/src/main/java/io/psisoft/codechallenge/model/GithubRepository.java new file mode 100644 index 0000000..f2d16d9 --- /dev/null +++ b/app/src/main/java/io/psisoft/codechallenge/model/GithubRepository.java @@ -0,0 +1,78 @@ +package io.psisoft.codechallenge.model; + +import com.google.gson.annotations.Expose; +import com.google.gson.annotations.SerializedName; + +public class GithubRepository { + + private int id; + private String name; + private String description; + private GithubRepositoryOwner owner; + + @SerializedName("stargazers_count") + @Expose + private int stargazersCount; + + + // Constructor + public GithubRepository(int id, String name, String description, GithubRepositoryOwner owner, int stargazersCount) { + this.id = id; + this.name = name; + this.description = description; + this.owner = owner; + this.stargazersCount = stargazersCount; + } + + // Getters and Setters + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public GithubRepositoryOwner getOwner() { + return owner; + } + + public void setOwner(GithubRepositoryOwner owner) { + this.owner = owner; + } + + public int getStargazersCount() { + return stargazersCount; + } + + public void setStargazersCount(int stargazersCount) { + this.stargazersCount = stargazersCount; + } + + + // Methods + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + GithubRepository that = (GithubRepository) o; + return id == that.id; + } + +} diff --git a/app/src/main/java/io/psisoft/codechallenge/model/GithubRepositoryOwner.java b/app/src/main/java/io/psisoft/codechallenge/model/GithubRepositoryOwner.java new file mode 100644 index 0000000..64c7f73 --- /dev/null +++ b/app/src/main/java/io/psisoft/codechallenge/model/GithubRepositoryOwner.java @@ -0,0 +1,32 @@ +package io.psisoft.codechallenge.model; + +public class GithubRepositoryOwner { + + private String login; + private String avatar_url; + + + // Constructor + public GithubRepositoryOwner(String login, String avatar_url) { + this.login = login; + this.avatar_url = avatar_url; + } + + + // Getters and Setters + public String getLogin() { + return login; + } + + public void setLogin(String login) { + this.login = login; + } + + public String getAvatar_url() { + return avatar_url; + } + + public void setAvatar_url(String avatar_url) { + this.avatar_url = avatar_url; + } +} diff --git a/app/src/main/java/io/psisoft/codechallenge/model/GithubResponse.java b/app/src/main/java/io/psisoft/codechallenge/model/GithubResponse.java new file mode 100644 index 0000000..d05c5af --- /dev/null +++ b/app/src/main/java/io/psisoft/codechallenge/model/GithubResponse.java @@ -0,0 +1,52 @@ +package io.psisoft.codechallenge.model; + +import com.google.gson.annotations.Expose; +import com.google.gson.annotations.SerializedName; + +import java.util.List; + +public class GithubResponse { + + @SerializedName("total_count") + @Expose + private int totalCount; + + @SerializedName("incomplete_results") + @Expose + private boolean incompleteResults; + + private List items; + + + // Constructor + public GithubResponse(int totalCount, boolean incompleteResults, List items) { + this.totalCount = totalCount; + this.incompleteResults = incompleteResults; + this.items = items; + } + + // Getters and Setters + public int getTotalCount() { + return totalCount; + } + + public void setTotalCount(int totalCount) { + this.totalCount = totalCount; + } + + public boolean isIncompleteResults() { + return incompleteResults; + } + + public void setIncompleteResults(boolean incompleteResults) { + this.incompleteResults = incompleteResults; + } + + public List getItems() { + return items; + } + + public void setItems(List items) { + this.items = items; + } +} diff --git a/app/src/main/java/io/psisoft/codechallenge/ui/MainActivity.java b/app/src/main/java/io/psisoft/codechallenge/ui/MainActivity.java new file mode 100644 index 0000000..1c3de4f --- /dev/null +++ b/app/src/main/java/io/psisoft/codechallenge/ui/MainActivity.java @@ -0,0 +1,63 @@ +package io.psisoft.codechallenge.ui; + +import android.support.design.widget.BottomNavigationView; +import android.support.v4.app.Fragment; +import android.support.v4.app.FragmentManager; +import android.support.v4.app.FragmentTransaction; +import android.support.v7.app.AppCompatActivity; +import android.os.Bundle; +import android.support.v7.widget.Toolbar; +import android.widget.FrameLayout; +import android.widget.TextView; + +import butterknife.BindView; +import butterknife.ButterKnife; +import io.psisoft.codechallenge.R; + +public class MainActivity extends AppCompatActivity{ + + @BindView(R.id.navigation_view) + BottomNavigationView bottomNavigationView; + @BindView(R.id.my_toolbar) + Toolbar customToolbar; + @BindView(R.id.my_toolbar_title) + TextView toolbarTitle; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_main); + ButterKnife.bind(this); + + // Set Action Bar with a custom Toolbar + setSupportActionBar(customToolbar); + + // Setup the trending fragment + FragmentManager fragmentManager = getSupportFragmentManager(); + fragmentManager.beginTransaction().replace(R.id.container, new Trending(), "feed").commit(); + bottomNavigationView.setSelectedItemId(R.id.trending); + + // Listen to the selected navigation item + bottomNavigationView.setOnNavigationItemSelectedListener + (item -> { + Fragment selectedFragment = null; + switch (item.getItemId()) { + case R.id.trending: + selectedFragment = new Trending(); + toolbarTitle.setText("Trending Repo"); + break; + case R.id.setting: + selectedFragment = new Settings(); + toolbarTitle.setText("Settings"); + break; + } + + // Replace container with the selected fragment + FragmentTransaction transaction = getSupportFragmentManager().beginTransaction(); + transaction.replace(R.id.container, selectedFragment); + transaction.commit(); + return true; + }); + } + +} diff --git a/app/src/main/java/io/psisoft/codechallenge/ui/RepositoryViewModel.java b/app/src/main/java/io/psisoft/codechallenge/ui/RepositoryViewModel.java new file mode 100644 index 0000000..d5083e8 --- /dev/null +++ b/app/src/main/java/io/psisoft/codechallenge/ui/RepositoryViewModel.java @@ -0,0 +1,53 @@ +package io.psisoft.codechallenge.ui; + +import android.arch.core.util.Function; +import android.arch.lifecycle.LiveData; +import android.arch.lifecycle.Transformations; +import android.arch.lifecycle.ViewModel; +import android.arch.paging.LivePagedListBuilder; +import android.arch.paging.PagedList; + +import io.psisoft.codechallenge.api.NetworkState; +import io.psisoft.codechallenge.model.GithubRepository; +import io.psisoft.codechallenge.utils.RepositoryDataSource; +import io.psisoft.codechallenge.utils.RepositoryDataSourceFactory; + +public class RepositoryViewModel extends ViewModel { + + private LiveData> itemPagedList; + private LiveData myDataSource; + private LiveData networkState; + + private static final int PAGE_SIZE = 30; + + public RepositoryViewModel() { + init(); + } + + public void init() { + + RepositoryDataSourceFactory itemDataSourceFactory = new RepositoryDataSourceFactory(); + myDataSource = itemDataSourceFactory.getItemLiveDataSource(); + + networkState = Transformations.switchMap(myDataSource, + (Function>) RepositoryDataSource::getNetworkState); + + PagedList.Config config = + (new PagedList.Config.Builder()) + .setEnablePlaceholders(false) + .setPageSize(PAGE_SIZE) + .build(); + + itemPagedList = (new LivePagedListBuilder(itemDataSourceFactory, config)).build(); + + + } + + public LiveData> getItemPagedList() { + return itemPagedList; + } + + public LiveData getNetworkState() { + return networkState; + } +} diff --git a/app/src/main/java/io/psisoft/codechallenge/ui/Settings.java b/app/src/main/java/io/psisoft/codechallenge/ui/Settings.java new file mode 100644 index 0000000..9150f7c --- /dev/null +++ b/app/src/main/java/io/psisoft/codechallenge/ui/Settings.java @@ -0,0 +1,31 @@ +package io.psisoft.codechallenge.ui; + +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.v4.app.Fragment; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import io.psisoft.codechallenge.R; + + +public class Settings extends Fragment { + + public Settings() { + // Required empty public constructor + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + } + + @Override + public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + // Inflate the layout for this fragment + return inflater.inflate(R.layout.fragment_settings, container, false); + } +} diff --git a/app/src/main/java/io/psisoft/codechallenge/ui/Trending.java b/app/src/main/java/io/psisoft/codechallenge/ui/Trending.java new file mode 100644 index 0000000..6946926 --- /dev/null +++ b/app/src/main/java/io/psisoft/codechallenge/ui/Trending.java @@ -0,0 +1,86 @@ +package io.psisoft.codechallenge.ui; + +import android.arch.lifecycle.ViewModelProviders; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.v4.app.Fragment; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ProgressBar; +import android.widget.Toast; + +import butterknife.BindView; +import butterknife.ButterKnife; +import io.psisoft.codechallenge.R; +import io.psisoft.codechallenge.adapter.RepositoryAdapter; +import io.psisoft.codechallenge.api.Status; + +public class Trending extends Fragment { + + @BindView(R.id.trending_recyclerView) + RecyclerView trendingRecyclerView; + @BindView(R.id.progress_circular) + ProgressBar progressBar; + + public Trending() { + // Required empty public constructor + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + } + + @Override + public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + // Inflate the layout for this fragment + View view = inflater.inflate(R.layout.fragment_trending, container, false); + ButterKnife.bind(this, view); + + // Setup trending recycler view + trendingRecyclerView.setLayoutManager(new LinearLayoutManager(getContext())); + trendingRecyclerView.setHasFixedSize(true); + + RepositoryViewModel itemViewModel = ViewModelProviders.of(this).get(RepositoryViewModel.class); + final RepositoryAdapter adapter = new RepositoryAdapter(); + + // Observe itemPagedList + itemViewModel.getItemPagedList().observe(this, adapter::submitList); + + // Observe the Network state + itemViewModel.getNetworkState().observe(this, networkState -> { + + //TODO: In this task, I only treated a few network states. + // However, I can add further features to treat error messages. + + if (networkState != null && networkState.getStatus() == Status.NOT_INITIALIZED) { + progressBar.setVisibility(View.GONE); + Toast.makeText(getContext(), networkState.getMsg(), Toast.LENGTH_LONG).show(); + } + + if (networkState != null && networkState.getStatus() == Status.INITIALIZED) { + progressBar.setVisibility(View.GONE); + } + + if (networkState != null && networkState.getStatus() == Status.FAILED) { + Toast.makeText(getContext(), networkState.getMsg(), Toast.LENGTH_LONG).show(); + } + + }); + + trendingRecyclerView.setAdapter(adapter); + + return view; + } + + @Override + public void onStart() { + super.onStart(); + + } + +} diff --git a/app/src/main/java/io/psisoft/codechallenge/utils/RepositoryDataSource.java b/app/src/main/java/io/psisoft/codechallenge/utils/RepositoryDataSource.java new file mode 100644 index 0000000..9ff2571 --- /dev/null +++ b/app/src/main/java/io/psisoft/codechallenge/utils/RepositoryDataSource.java @@ -0,0 +1,122 @@ +package io.psisoft.codechallenge.utils; + +import android.arch.lifecycle.MutableLiveData; +import android.arch.paging.PageKeyedDataSource; +import android.support.annotation.NonNull; + +import io.psisoft.codechallenge.api.ApiService; +import io.psisoft.codechallenge.api.NetworkState; +import io.psisoft.codechallenge.model.GithubResponse; +import retrofit2.Call; +import retrofit2.Callback; +import retrofit2.Response; + + +public class RepositoryDataSource extends PageKeyedDataSource { + + public static final int FIRST_PAGE = 1; + private static final String SORTED_BY = "stars"; + private static final String ORDER = "desc"; + private static final String LIST_SIZE = "created:>2017-10-22"; + + private ApiService apiService; + private MutableLiveData networkState; + + + public RepositoryDataSource() { + apiService = ApiService.getInstance(); + networkState = new MutableLiveData<>(); + } + + @Override + public void loadInitial(@NonNull LoadInitialParams params, @NonNull LoadInitialCallback callback) { + + networkState.postValue(NetworkState.LOADING); + + // Load data for the first page + apiService.getApiHelper() + .getGithubRepositories(LIST_SIZE, SORTED_BY, ORDER, FIRST_PAGE) + .enqueue(new Callback() { + @Override + public void onResponse(Call call, Response response) { + + if (response.isSuccessful() && response.body() != null) { + callback.onResult(response.body().getItems(), null, FIRST_PAGE + 1); + networkState.postValue(NetworkState.INITIALIZED); + } else { + networkState.postValue(NetworkState.NOT_INITIALIZED); + } + + } + + @Override + public void onFailure(Call call, Throwable t) { + networkState.postValue(NetworkState.NOT_INITIALIZED); + } + }); + } + + @Override + public void loadBefore(@NonNull LoadParams params, @NonNull LoadCallback callback) { + + networkState.postValue(NetworkState.LOADING); + + // Load data for the previews page + apiService.getApiHelper() + .getGithubRepositories(LIST_SIZE, SORTED_BY, ORDER, (int) params.key) + .enqueue(new Callback() { + @Override + public void onResponse(Call call, Response response) { + + if (response.isSuccessful() && response.body() != null) { + Integer key = ((int) params.key > 1) ? (int) params.key - 1 : null; + callback.onResult(response.body().getItems(), key); + networkState.postValue(NetworkState.LOADED); + } else { + networkState.postValue(NetworkState.FAILED); + } + + } + + @Override + public void onFailure(Call call, Throwable t) { + networkState.postValue(NetworkState.FAILED); + } + }); + + } + + @Override + public void loadAfter(@NonNull LoadParams params, @NonNull LoadCallback callback) { + + networkState.postValue(NetworkState.LOADING); + + // Load data for the next page + apiService.getApiHelper() + .getGithubRepositories(LIST_SIZE, SORTED_BY, ORDER, (int) params.key) + .enqueue(new Callback() { + @Override + public void onResponse(Call call, Response response) { + + if (response.isSuccessful() && response.body() != null) { + Integer key = response.body().isIncompleteResults() ? (int) params.key + 1 : null; + callback.onResult(response.body().getItems(), key); + networkState.postValue(NetworkState.LOADED); + } else { + networkState.postValue(NetworkState.FAILED); + } + + } + + @Override + public void onFailure(Call call, Throwable t) { + networkState.postValue(NetworkState.FAILED); + } + }); + + } + + public MutableLiveData getNetworkState() { + return networkState; + } +} diff --git a/app/src/main/java/io/psisoft/codechallenge/utils/RepositoryDataSourceFactory.java b/app/src/main/java/io/psisoft/codechallenge/utils/RepositoryDataSourceFactory.java new file mode 100644 index 0000000..91ff158 --- /dev/null +++ b/app/src/main/java/io/psisoft/codechallenge/utils/RepositoryDataSourceFactory.java @@ -0,0 +1,20 @@ +package io.psisoft.codechallenge.utils; + +import android.arch.lifecycle.MutableLiveData; +import android.arch.paging.DataSource; + +public class RepositoryDataSourceFactory extends DataSource.Factory { + + private MutableLiveData repositoryLiveDataSource = new MutableLiveData<>(); + + @Override + public DataSource create() { + RepositoryDataSource repositoryDataSource = new RepositoryDataSource(); + repositoryLiveDataSource.postValue(repositoryDataSource); + return repositoryDataSource; + } + + public MutableLiveData getItemLiveDataSource() { + return repositoryLiveDataSource; + } +} diff --git a/app/src/main/res/drawable-hdpi/ic_image_placeholder.png b/app/src/main/res/drawable-hdpi/ic_image_placeholder.png new file mode 100644 index 0000000..8277f53 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_image_placeholder.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_settings.png b/app/src/main/res/drawable-hdpi/ic_settings.png new file mode 100644 index 0000000..e20312f Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_settings.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_star.png b/app/src/main/res/drawable-hdpi/ic_star.png new file mode 100644 index 0000000..1a56a69 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_star.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_star_small.png b/app/src/main/res/drawable-hdpi/ic_star_small.png new file mode 100644 index 0000000..09ae689 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_star_small.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_image_placeholder.png b/app/src/main/res/drawable-mdpi/ic_image_placeholder.png new file mode 100644 index 0000000..7c4cf43 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_image_placeholder.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_settings.png b/app/src/main/res/drawable-mdpi/ic_settings.png new file mode 100644 index 0000000..378676d Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_settings.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_star.png b/app/src/main/res/drawable-mdpi/ic_star.png new file mode 100644 index 0000000..2606210 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_star.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_star_small.png b/app/src/main/res/drawable-mdpi/ic_star_small.png new file mode 100644 index 0000000..091d544 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_star_small.png differ diff --git a/app/src/main/res/drawable-v21/nav_item_background.xml b/app/src/main/res/drawable-v21/nav_item_background.xml new file mode 100644 index 0000000..39bdc37 --- /dev/null +++ b/app/src/main/res/drawable-v21/nav_item_background.xml @@ -0,0 +1,3 @@ + + \ No newline at end of file diff --git a/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml new file mode 100644 index 0000000..1f6bb29 --- /dev/null +++ b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + diff --git a/app/src/main/res/drawable-xhdpi/ic_image_placeholder.png b/app/src/main/res/drawable-xhdpi/ic_image_placeholder.png new file mode 100644 index 0000000..ac85e71 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_image_placeholder.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_settings.png b/app/src/main/res/drawable-xhdpi/ic_settings.png new file mode 100644 index 0000000..34a2ccd Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_settings.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_star.png b/app/src/main/res/drawable-xhdpi/ic_star.png new file mode 100644 index 0000000..2c606c4 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_star.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_star_small.png b/app/src/main/res/drawable-xhdpi/ic_star_small.png new file mode 100644 index 0000000..1a56a69 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_star_small.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_image_placeholder.png b/app/src/main/res/drawable-xxhdpi/ic_image_placeholder.png new file mode 100644 index 0000000..a4613dd Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_image_placeholder.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_settings.png b/app/src/main/res/drawable-xxhdpi/ic_settings.png new file mode 100644 index 0000000..e60ef2d Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_settings.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_star.png b/app/src/main/res/drawable-xxhdpi/ic_star.png new file mode 100644 index 0000000..a6c6a73 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_star.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_star_small.png b/app/src/main/res/drawable-xxhdpi/ic_star_small.png new file mode 100644 index 0000000..65327dc Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_star_small.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_image_placeholder.png b/app/src/main/res/drawable-xxxhdpi/ic_image_placeholder.png new file mode 100644 index 0000000..3a15464 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_image_placeholder.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_settings.png b/app/src/main/res/drawable-xxxhdpi/ic_settings.png new file mode 100644 index 0000000..2372703 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_settings.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_star.png b/app/src/main/res/drawable-xxxhdpi/ic_star.png new file mode 100644 index 0000000..a1a20b8 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_star.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_star_small.png b/app/src/main/res/drawable-xxxhdpi/ic_star_small.png new file mode 100644 index 0000000..a6c6a73 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_star_small.png differ diff --git a/app/src/main/res/drawable/background_toolbar.xml b/app/src/main/res/drawable/background_toolbar.xml new file mode 100644 index 0000000..2d3e98f --- /dev/null +++ b/app/src/main/res/drawable/background_toolbar.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + \ 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..0d025f9 --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/nav_item_background.xml b/app/src/main/res/drawable/nav_item_background.xml new file mode 100644 index 0000000..8933184 --- /dev/null +++ b/app/src/main/res/drawable/nav_item_background.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/state_navigation_item.xml b/app/src/main/res/drawable/state_navigation_item.xml new file mode 100644 index 0000000..bd34606 --- /dev/null +++ b/app/src/main/res/drawable/state_navigation_item.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml new file mode 100644 index 0000000..4b02693 --- /dev/null +++ b/app/src/main/res/layout/activity_main.xml @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/fragment_settings.xml b/app/src/main/res/layout/fragment_settings.xml new file mode 100644 index 0000000..156c34a --- /dev/null +++ b/app/src/main/res/layout/fragment_settings.xml @@ -0,0 +1,14 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_trending.xml b/app/src/main/res/layout/fragment_trending.xml new file mode 100644 index 0000000..d862f83 --- /dev/null +++ b/app/src/main/res/layout/fragment_trending.xml @@ -0,0 +1,23 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/repository_item_view.xml b/app/src/main/res/layout/repository_item_view.xml new file mode 100644 index 0000000..9eecca4 --- /dev/null +++ b/app/src/main/res/layout/repository_item_view.xml @@ -0,0 +1,96 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/menu/navigation.xml b/app/src/main/res/menu/navigation.xml new file mode 100644 index 0000000..360d67e --- /dev/null +++ b/app/src/main/res/menu/navigation.xml @@ -0,0 +1,16 @@ + + + + + + + + + + \ 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..eca70cf --- /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..eca70cf --- /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..09c9a31 --- /dev/null +++ b/app/src/main/res/values/colors.xml @@ -0,0 +1,6 @@ + + + #F7F6F6 + #c4c3c3 + #D81B60 + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml new file mode 100644 index 0000000..a7bdb95 --- /dev/null +++ b/app/src/main/res/values/strings.xml @@ -0,0 +1,11 @@ + + Code Challenge + + Trending Rpo + Trending + Settings + + + Under Constructions. Not yet available! + + diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml new file mode 100644 index 0000000..5885930 --- /dev/null +++ b/app/src/main/res/values/styles.xml @@ -0,0 +1,11 @@ + + + + + + diff --git a/app/src/test/java/io/psisoft/codechallenge/ExampleUnitTest.java b/app/src/test/java/io/psisoft/codechallenge/ExampleUnitTest.java new file mode 100644 index 0000000..164f8cb --- /dev/null +++ b/app/src/test/java/io/psisoft/codechallenge/ExampleUnitTest.java @@ -0,0 +1,17 @@ +package io.psisoft.codechallenge; + +import org.junit.Test; + +import static org.junit.Assert.*; + +/** + * Example local unit test, which will execute on the development machine (host). + * + * @see Testing documentation + */ +public class ExampleUnitTest { + @Test + public void addition_isCorrect() { + assertEquals(4, 2 + 2); + } +} \ No newline at end of file diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..fafc1b9 --- /dev/null +++ b/build.gradle @@ -0,0 +1,27 @@ +// Top-level build file where you can add configuration options common to all sub-projects/modules. + +buildscript { + repositories { + google() + jcenter() + + } + dependencies { + classpath 'com.android.tools.build:gradle:3.4.0' + + // 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..82618ce --- /dev/null +++ b/gradle.properties @@ -0,0 +1,15 @@ +# 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 + + 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..77c7429 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Wed May 15 11:31:21 WET 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/local.properties b/local.properties new file mode 100644 index 0000000..df3466d --- /dev/null +++ b/local.properties @@ -0,0 +1,10 @@ +## This file is automatically generated by Android Studio. +# Do not modify this file -- YOUR CHANGES WILL BE ERASED! +# +# This file should *NOT* be checked into Version Control Systems, +# as it contains information specific to your local configuration. +# +# Location of the SDK. This is only used by Gradle. +# For customization when using a Version Control System, please read the +# header note. +sdk.dir=/home/saber/Android/Sdk diff --git a/mobile-coding-challenge.iml b/mobile-coding-challenge.iml new file mode 100644 index 0000000..5761566 --- /dev/null +++ b/mobile-coding-challenge.iml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file 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'