Skip to content

Code challenge saber #3

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Ignore ./idea folder
.idea/*
Binary file not shown.
Binary file not shown.
Binary file added .gradle/5.1.1/fileChanges/last-build.bin
Binary file not shown.
Binary file added .gradle/5.1.1/fileContent/fileContent.lock
Binary file not shown.
Binary file added .gradle/5.1.1/fileHashes/fileHashes.bin
Binary file not shown.
Binary file added .gradle/5.1.1/fileHashes/fileHashes.lock
Binary file not shown.
Binary file added .gradle/5.1.1/fileHashes/resourceHashesCache.bin
Binary file not shown.
Empty file added .gradle/5.1.1/gc.properties
Empty file.
Binary file added .gradle/5.1.1/javaCompile/classAnalysis.bin
Binary file not shown.
Binary file added .gradle/5.1.1/javaCompile/javaCompile.lock
Binary file not shown.
Binary file added .gradle/5.1.1/javaCompile/taskHistory.bin
Binary file not shown.
Binary file added .gradle/buildOutputCleanup/buildOutputCleanup.lock
Binary file not shown.
2 changes: 2 additions & 0 deletions .gradle/buildOutputCleanup/cache.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
#Wed May 15 11:39:32 WET 2019
gradle.version=5.1.1
Binary file added .gradle/buildOutputCleanup/outputFiles.bin
Binary file not shown.
Empty file added .gradle/vcs-1/gc.properties
Empty file.
20 changes: 20 additions & 0 deletions CodeChallenge.iml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8"?>
<module external.linked.project.id="CodeChallenge" external.linked.project.path="$MODULE_DIR$" external.root.project.path="$MODULE_DIR$" external.system.id="GRADLE" type="JAVA_MODULE" version="4">
<component name="FacetManager">
<facet type="java-gradle" name="Java-Gradle">
<configuration>
<option name="BUILD_FOLDER_PATH" value="$MODULE_DIR$/build" />
<option name="BUILDABLE" value="false" />
</configuration>
</facet>
</component>
<component name="NewModuleRootManager" LANGUAGE_LEVEL="JDK_1_7" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/.gradle" />
<excludeFolder url="file://$MODULE_DIR$/build" />
</content>
<orderEntry type="jdk" jdkName="1.8" jdkType="JavaSDK" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>
14 changes: 10 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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).

1 change: 1 addition & 0 deletions app/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/build
196 changes: 196 additions & 0 deletions app/app.iml

Large diffs are not rendered by default.

61 changes: 61 additions & 0 deletions app/build.gradle
Original file line number Diff line number Diff line change
@@ -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'
}
21 changes: 21 additions & 0 deletions app/proguard-rules.pro
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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 <a href="http://d.android.com/tools/testing">Testing documentation</a>
*/
@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());
}
}
24 changes: 24 additions & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="io.psisoft.codechallenge">

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.Design.Light.NoActionBar">
<activity android:name=".ui.MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />

<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>

</manifest>
Original file line number Diff line number Diff line change
@@ -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<GithubRepository, RepositoryViewHolder> {

public RepositoryAdapter() {
super(DIFF_CALLBACK);
}

private static DiffUtil.ItemCallback<GithubRepository> DIFF_CALLBACK =
new DiffUtil.ItemCallback<GithubRepository>() {

@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);
}
}
}
Original file line number Diff line number Diff line change
@@ -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<Long, String> 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<Long, String> 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;
}
}
27 changes: 27 additions & 0 deletions app/src/main/java/io/psisoft/codechallenge/api/ApiHelper.java
Original file line number Diff line number Diff line change
@@ -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<GithubResponse> getGithubRepositories(
@Query("q") String createdDate,
@Query("sort") String sort,
@Query("order") String order,
@Query("page") int page
);
}
34 changes: 34 additions & 0 deletions app/src/main/java/io/psisoft/codechallenge/api/ApiService.java
Original file line number Diff line number Diff line change
@@ -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);
}
}
Loading