diff --git a/README.md b/README.md
index 80966847..8b137891 100644
--- a/README.md
+++ b/README.md
@@ -1,57 +1 @@
-# Desafio de programação mobile Android
-A ideia deste desafio é nos permitir avaliar melhor as habilidades de candidatos a vagas de programador, de vários níveis.
-
-Este desafio deve ser feito por você em sua casa. Gaste o tempo que você quiser, porém normalmente você não deve precisar de mais do que algumas horas.
-
-## Instruções de entrega do desafio
-
-1. Primeiro, faça um fork deste projeto para sua conta no GitHub (crie uma se você não possuir).
-1. Em seguida, implemente o projeto tal qual descrito abaixo, em seu próprio fork.
-1. Por fim, empurre todas as suas alterações para o seu fork no GitHub e envie um pull request para este repositório original. Se você já entrou em contato com alguém da AppProva sobre uma vaga, avise também essa pessoa por email, incluindo no email o seu usuário no GitHub.
-
-## Descrição do projeto
-
-Você deve criar um aplicativo que irá listar os repositórios públicos mais populares relacionados a Java no GitHub, usando a [API do GitHub](https://developer.github.com/v3/) para buscar os dados necessários.
-
-O aplicativo deve exibir inicialmente uma lista paginada dos repositórios, ordenados por popularidade decrescente (exemplo de chamada da API: `https://api.github.com/search/repositories?q=language:Java&sort=stars&page=1`).
-
-Cada repositório deve exibir Nome do repositório, Descrição do Repositório, Nome / Foto do autor, Número de Stars, Número de Forks.
-
-Ao tocar em um item, deve levar a lista de Pull Requests do repositório. Cada item da lista deve exibir Nome / Foto do autor do PR, Título do PR, Data do PR e Body do PR.
-
-Ao tocar em um item, deve abrir no browser a página do Pull Request em questão.
-
-Você pode se basear neste mockup para criar as telas:
-
-
-
-Sua aplicação deve:
-
-- usar um sistema de build. Ex: Gradle
-- fazer mapeamento json -> Objeto . Ex: GSON / Jackson / Moshi / etc
-- usar um arquivo .gitignore no seu repositório
-- usar Material Design
-- usar o padrão MVP ou MVVM
-- possuir boa cobertura de testes unitários no projeto.
-
-Você ganha mais pontos se:
-
-- criar testes funcionais
-- fazer cache de imagens
-- utilizar Kotlin
-- utilizar RxJava ou RxKotlin
-- utilizar os Android Architecture Components
-- suportar mudanças de orientação das telas sem perder estado (Sem utilizar o configChanges)
-
-As sugestões de bibliotecas fornecidas são só um guideline, sinta-se a vontade para usar soluções diferentes e nos surpreender. O importante de fato é que os objetivos macros sejam atingidos.
-
-## Avaliação
-
-Seu projeto será avaliado de acordo com os seguintes critérios.
-
-1. Sua aplicação preenche os requerimentos básicos?
-1. Você documentou a maneira de configurar o ambiente e rodar sua aplicação?
-1. Você seguiu as instruções de envio do desafio?
-
-Adicionalmente, tentaremos verificar a sua familiarização com as bibliotecas padrões (standard libs), bem como sua experiência com programação orientada a objetos a partir da estrutura de seu projeto.
diff --git a/desafio-mobile-android/.gitignore b/desafio-mobile-android/.gitignore
new file mode 100644
index 00000000..39fb081a
--- /dev/null
+++ b/desafio-mobile-android/.gitignore
@@ -0,0 +1,9 @@
+*.iml
+.gradle
+/local.properties
+/.idea/workspace.xml
+/.idea/libraries
+.DS_Store
+/build
+/captures
+.externalNativeBuild
diff --git a/desafio-mobile-android/app/.gitignore b/desafio-mobile-android/app/.gitignore
new file mode 100644
index 00000000..796b96d1
--- /dev/null
+++ b/desafio-mobile-android/app/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/desafio-mobile-android/app/build.gradle b/desafio-mobile-android/app/build.gradle
new file mode 100644
index 00000000..c228f433
--- /dev/null
+++ b/desafio-mobile-android/app/build.gradle
@@ -0,0 +1,66 @@
+apply plugin: 'com.android.application'
+
+android {
+ compileSdkVersion 26
+ buildToolsVersion "26.0.0"
+ defaultConfig {
+ applicationId "com.app.desafio_mobile_android"
+ minSdkVersion 19
+ targetSdkVersion 26
+ versionCode 1
+ versionName "1.0"
+ testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
+ vectorDrawables.useSupportLibrary = true
+ }
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+ }
+ }
+}
+
+dependencies {
+ compile fileTree(dir: 'libs', include: ['*.jar'])
+ compile "com.android.support:appcompat-v7:$rootProject.ext.supportLibraryVersion"
+ compile "com.android.support:design:$rootProject.ext.supportLibraryVersion"
+ compile "com.android.support:recyclerview-v7:$rootProject.ext.supportLibraryVersion"
+ compile "com.android.support:support-v4:$rootProject.supportLibraryVersion"
+ compile "com.android.support:support-v4:$rootProject.supportLibraryVersion"
+
+ compile "com.android.support.constraint:constraint-layout:$rootProject.constraint"
+
+ compile "com.squareup.retrofit2:retrofit:$rootProject.retrofit"
+ compile "com.squareup.okhttp3:logging-interceptor:$rootProject.okhttp"
+ compile "com.squareup.okhttp3:okhttp:$rootProject.okhttp"
+ compile "com.squareup.retrofit2:converter-gson:$rootProject.gson"
+
+ compile "com.jakewharton:butterknife:$rootProject.butterknife"
+ annotationProcessor "com.jakewharton:butterknife-compiler:$rootProject.butterknife"
+
+ compile "com.squareup.picasso:picasso:$rootProject.picasso"
+
+ compile "com.lusfold.spinnerloading:library:$rootProject.spinnerloading"
+
+ androidTestCompile "junit:junit:$rootProject.ext.junitVersion"
+ androidTestCompile("com.android.support.test.espresso:espresso-core:$rootProject.ext.espressoVersion", {
+ exclude group: 'com.android.support', module: 'support-annotations'
+ })
+ androidTestCompile "org.powermock:powermock-module-junit4:$rootProject.ext.powermockVersion"
+ androidTestCompile "org.powermock:powermock-module-junit4-rule:$rootProject.ext.powermockVersion"
+ androidTestCompile ("com.android.support.test:runner:0.5", {
+ exclude group: 'com.android.support', module: 'support-annotations'
+ })
+ androidTestCompile "com.android.support:support-annotations:$rootProject.supportLibraryVersion"
+
+
+ testCompile "junit:junit:$rootProject.ext.junitVersion"
+ testCompile "org.mockito:mockito-all:$rootProject.ext.mockitoVersion"
+ testCompile "org.powermock:powermock-module-junit4:$rootProject.ext.powermockVersion"
+ testCompile "org.powermock:powermock-api-mockito:$rootProject.ext.powermockVersion"
+ testCompile "org.hamcrest:hamcrest-all:$rootProject.ext.hamcrestAllVersion"
+
+ androidTestCompile "org.mockito:mockito-core:$rootProject.ext.mockitoVersion"
+
+
+}
diff --git a/desafio-mobile-android/app/proguard-rules.pro b/desafio-mobile-android/app/proguard-rules.pro
new file mode 100644
index 00000000..347b42ca
--- /dev/null
+++ b/desafio-mobile-android/app/proguard-rules.pro
@@ -0,0 +1,25 @@
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in /Users/selem/Library/Android/sdk/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the proguardFiles
+# directive in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# Add any project specific keep options here:
+
+# 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/desafio-mobile-android/app/src/androidTest/java/com/app/desafio_mobile_android/ExampleInstrumentedTest.java b/desafio-mobile-android/app/src/androidTest/java/com/app/desafio_mobile_android/ExampleInstrumentedTest.java
new file mode 100644
index 00000000..45d97235
--- /dev/null
+++ b/desafio-mobile-android/app/src/androidTest/java/com/app/desafio_mobile_android/ExampleInstrumentedTest.java
@@ -0,0 +1,26 @@
+package com.app.desafio_mobile_android;
+
+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.*;
+
+/**
+ * Instrumentation test, which will execute on an Android device.
+ *
+ * @see Testing documentation
+ */
+@RunWith(AndroidJUnit4.class)
+public class ExampleInstrumentedTest {
+ @Test
+ public void useAppContext() throws Exception {
+ // Context of the app under test.
+ Context appContext = InstrumentationRegistry.getTargetContext();
+
+ assertEquals("com.app.desafio_mobile_android", appContext.getPackageName());
+ }
+}
diff --git a/desafio-mobile-android/app/src/androidTest/java/com/app/desafio_mobile_android/MainActivityTest.java b/desafio-mobile-android/app/src/androidTest/java/com/app/desafio_mobile_android/MainActivityTest.java
new file mode 100644
index 00000000..1653356d
--- /dev/null
+++ b/desafio-mobile-android/app/src/androidTest/java/com/app/desafio_mobile_android/MainActivityTest.java
@@ -0,0 +1,29 @@
+package com.app.desafio_mobile_android;
+
+
+import android.support.test.rule.ActivityTestRule;
+import android.support.test.runner.AndroidJUnit4;
+
+import com.app.desafio_mobile_android.presentation.home.MainActivity;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import static android.support.test.espresso.Espresso.onView;
+import static android.support.test.espresso.assertion.ViewAssertions.matches;
+import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed;
+import static android.support.test.espresso.matcher.ViewMatchers.withId;
+
+@RunWith(AndroidJUnit4.class)
+public class MainActivityTest {
+
+ @Rule
+ public ActivityTestRule mMainActivityActivityTestRule = new
+ ActivityTestRule<>(MainActivity.class, false, true);
+
+ @Test
+ public void whenActivityIsLaunched_shouldDisplayInitialState() {
+ onView(withId(R.id.recyclerview_repositorie_list)).check(matches(isDisplayed()));
+ }
+}
diff --git a/desafio-mobile-android/app/src/main/AndroidManifest.xml b/desafio-mobile-android/app/src/main/AndroidManifest.xml
new file mode 100644
index 00000000..5d0794cf
--- /dev/null
+++ b/desafio-mobile-android/app/src/main/AndroidManifest.xml
@@ -0,0 +1,32 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/desafio-mobile-android/app/src/main/java/com/app/desafio_mobile_android/business/PullBusiness.java b/desafio-mobile-android/app/src/main/java/com/app/desafio_mobile_android/business/PullBusiness.java
new file mode 100644
index 00000000..fb189f8b
--- /dev/null
+++ b/desafio-mobile-android/app/src/main/java/com/app/desafio_mobile_android/business/PullBusiness.java
@@ -0,0 +1,57 @@
+package com.app.desafio_mobile_android.business;
+
+import android.content.Context;
+
+import com.app.desafio_mobile_android.intrastructure.error.OperationError;
+import com.app.desafio_mobile_android.intrastructure.util.NetworkUtil;
+import com.app.desafio_mobile_android.intrastructure.util.ServiceListener;
+import com.app.desafio_mobile_android.presentation.pull.PullAdapter;
+import com.app.desafio_mobile_android.presentation.pull.PullPresenter;
+import com.app.desafio_mobile_android.presentation.pull.PullViewModel;
+import com.app.desafio_mobile_android.service.Api;
+import com.app.desafio_mobile_android.service.ApiInstance;
+import com.app.desafio_mobile_android.service.model.pull.Pull;
+import com.app.desafio_mobile_android.service.repository.PullRepository;
+
+import java.util.List;
+
+public class PullBusiness {
+ private PullRepository mPullRepository;
+ private ServiceListener mCallback;
+
+ public PullBusiness(Context context) {
+ mPullRepository = new PullRepository(ApiInstance.getAPI().create(Api.class),
+ new NetworkUtil(context));
+ }
+
+ public void callPullList(String owner, String repo, ServiceListener callback) {
+ mCallback = callback;
+ mPullRepository.requestRepositoryList(owner, repo, new RequestPullCallback());
+ }
+
+ private void processResult(List pullList) {
+ int opened = 0;
+ int closed = 0;
+ for (Pull pull : pullList) {
+ if (pull.isClosed()) {
+ closed++;
+ } else {
+ opened++;
+ }
+ }
+ mCallback.onServiceSuccess(new PullViewModel(pullList, opened, closed));
+ }
+
+ private class RequestPullCallback extends ServiceListener> {
+
+ @Override
+ public void onServiceSuccess(List pullList) {
+ processResult(pullList);
+ }
+
+ @Override
+ public void onServiceError(OperationError error) {
+ mCallback.onServiceError(error);
+ }
+ }
+}
diff --git a/desafio-mobile-android/app/src/main/java/com/app/desafio_mobile_android/business/RepositorieBusiness.java b/desafio-mobile-android/app/src/main/java/com/app/desafio_mobile_android/business/RepositorieBusiness.java
new file mode 100644
index 00000000..71fbe81f
--- /dev/null
+++ b/desafio-mobile-android/app/src/main/java/com/app/desafio_mobile_android/business/RepositorieBusiness.java
@@ -0,0 +1,24 @@
+package com.app.desafio_mobile_android.business;
+
+import android.content.Context;
+
+import com.app.desafio_mobile_android.intrastructure.util.NetworkUtil;
+import com.app.desafio_mobile_android.intrastructure.util.ServiceListener;
+import com.app.desafio_mobile_android.service.Api;
+import com.app.desafio_mobile_android.service.ApiInstance;
+import com.app.desafio_mobile_android.service.model.repositorie.Repositorie;
+import com.app.desafio_mobile_android.service.repository.RepositorieRepository;
+
+public class RepositorieBusiness {
+
+ private RepositorieRepository mRepositorieRepository;
+
+ public RepositorieBusiness(Context context) {
+ mRepositorieRepository = new RepositorieRepository(ApiInstance.getAPI().create(Api.class),
+ new NetworkUtil(context));
+ }
+
+ public void callServiceRepositorie(int pageNumber, ServiceListener callback) {
+ mRepositorieRepository.requestRepositoryList(pageNumber, callback);
+ }
+}
diff --git a/desafio-mobile-android/app/src/main/java/com/app/desafio_mobile_android/intrastructure/Constants.java b/desafio-mobile-android/app/src/main/java/com/app/desafio_mobile_android/intrastructure/Constants.java
new file mode 100644
index 00000000..5e64acec
--- /dev/null
+++ b/desafio-mobile-android/app/src/main/java/com/app/desafio_mobile_android/intrastructure/Constants.java
@@ -0,0 +1,15 @@
+package com.app.desafio_mobile_android.intrastructure;
+
+public class Constants {
+
+ public static final String ABOUT_URL = "https://www.linkedin.com/in/selem-afonso-a0b75a8";
+ public static final String BASE_API = "https://api.github.com/";
+ public enum ErrorType {
+ NETWORK_ERROR, SERVICE_ERROR, GENERIC_ERROR, CUSTOM_ERROR
+ }
+
+ public static class DatePattern {
+ public static final String DATE_ENGLISH = "yyyy-MM-dd'T'HH:mm:ss";
+ public static final String DEFAULT_FORMAT = "MM/dd/yyyy hh:mm:ss aa";
+ }
+}
diff --git a/desafio-mobile-android/app/src/main/java/com/app/desafio_mobile_android/intrastructure/error/OperationError.java b/desafio-mobile-android/app/src/main/java/com/app/desafio_mobile_android/intrastructure/error/OperationError.java
new file mode 100644
index 00000000..fc948cd6
--- /dev/null
+++ b/desafio-mobile-android/app/src/main/java/com/app/desafio_mobile_android/intrastructure/error/OperationError.java
@@ -0,0 +1,30 @@
+package com.app.desafio_mobile_android.intrastructure.error;
+
+
+import com.app.desafio_mobile_android.intrastructure.Constants;
+
+public class OperationError {
+
+ private Constants.ErrorType errorType;
+ private String errorMsg;
+
+ public Constants.ErrorType getErrorType() {
+ return errorType;
+ }
+
+ public void setErrorType(Constants.ErrorType errorType) {
+ this.errorType = errorType;
+ }
+
+ public String getErrorMsg() {
+ return errorMsg;
+ }
+
+ public void setErrorMsg(String errorMsg) {
+ this.errorMsg = errorMsg;
+ }
+
+ public boolean isCustomError() {
+ return errorType == Constants.ErrorType.CUSTOM_ERROR;
+ }
+}
diff --git a/desafio-mobile-android/app/src/main/java/com/app/desafio_mobile_android/intrastructure/util/ActivityUtils.java b/desafio-mobile-android/app/src/main/java/com/app/desafio_mobile_android/intrastructure/util/ActivityUtils.java
new file mode 100644
index 00000000..95fe193f
--- /dev/null
+++ b/desafio-mobile-android/app/src/main/java/com/app/desafio_mobile_android/intrastructure/util/ActivityUtils.java
@@ -0,0 +1,33 @@
+
+package com.app.desafio_mobile_android.intrastructure.util;
+
+import android.support.annotation.NonNull;
+import android.support.v4.app.Fragment;
+import android.support.v4.app.FragmentManager;
+import android.support.v4.app.FragmentTransaction;
+
+import static android.support.v4.util.Preconditions.checkNotNull;
+
+
+/**
+ * This provides methods to help Activities load their UI.
+ */
+public class ActivityUtils {
+
+ /**
+ * The {@code fragment} is added to the container view with id {@code frameId}. The operation is
+ * performed by the {@code fragmentManager}.
+ *
+ */
+ public static void addFragmentToActivity (@NonNull FragmentManager fragmentManager,
+ @NonNull Fragment fragment, int frameId) {
+ checkNotNull(fragmentManager);
+ checkNotNull(fragment);
+ FragmentTransaction transaction = fragmentManager.beginTransaction();
+ transaction.add(frameId, fragment);
+ transaction.commit();
+ }
+
+
+
+}
\ No newline at end of file
diff --git a/desafio-mobile-android/app/src/main/java/com/app/desafio_mobile_android/intrastructure/util/CircleTransform.java b/desafio-mobile-android/app/src/main/java/com/app/desafio_mobile_android/intrastructure/util/CircleTransform.java
new file mode 100644
index 00000000..7fa7dab1
--- /dev/null
+++ b/desafio-mobile-android/app/src/main/java/com/app/desafio_mobile_android/intrastructure/util/CircleTransform.java
@@ -0,0 +1,43 @@
+package com.app.desafio_mobile_android.intrastructure.util;
+
+import android.graphics.Bitmap;
+import android.graphics.BitmapShader;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+
+import com.squareup.picasso.Transformation;
+
+public class CircleTransform implements Transformation {
+ @Override
+ public Bitmap transform(Bitmap source) {
+ int size = Math.min(source.getWidth(), source.getHeight());
+
+ int x = (source.getWidth() - size) / 2;
+ int y = (source.getHeight() - size) / 2;
+
+ Bitmap squaredBitmap = Bitmap.createBitmap(source, x, y, size, size);
+ if (squaredBitmap != source) {
+ source.recycle();
+ }
+
+ Bitmap bitmap = Bitmap.createBitmap(size, size, source.getConfig());
+
+ Canvas canvas = new Canvas(bitmap);
+ Paint paint = new Paint();
+ BitmapShader shader = new BitmapShader(squaredBitmap,
+ BitmapShader.TileMode.CLAMP, BitmapShader.TileMode.CLAMP);
+ paint.setShader(shader);
+ paint.setAntiAlias(true);
+
+ float r = size / 2f;
+ canvas.drawCircle(r, r, r, paint);
+
+ squaredBitmap.recycle();
+ return bitmap;
+ }
+
+ @Override
+ public String key() {
+ return "circle";
+ }
+}
diff --git a/desafio-mobile-android/app/src/main/java/com/app/desafio_mobile_android/intrastructure/util/NetworkUtil.java b/desafio-mobile-android/app/src/main/java/com/app/desafio_mobile_android/intrastructure/util/NetworkUtil.java
new file mode 100644
index 00000000..3d47ef5a
--- /dev/null
+++ b/desafio-mobile-android/app/src/main/java/com/app/desafio_mobile_android/intrastructure/util/NetworkUtil.java
@@ -0,0 +1,21 @@
+package com.app.desafio_mobile_android.intrastructure.util;
+
+
+import android.content.Context;
+import android.net.ConnectivityManager;
+import android.net.NetworkInfo;
+
+public class NetworkUtil {
+ private Context context;
+
+ public NetworkUtil(Context context) {
+ this.context = context;
+ }
+
+ public boolean isNetworkAvailable() {
+ ConnectivityManager connectivityManager
+ = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
+ NetworkInfo activeNetworkInfo = connectivityManager.getActiveNetworkInfo();
+ return activeNetworkInfo != null && activeNetworkInfo.isConnected();
+ }
+}
diff --git a/desafio-mobile-android/app/src/main/java/com/app/desafio_mobile_android/intrastructure/util/ServiceListener.java b/desafio-mobile-android/app/src/main/java/com/app/desafio_mobile_android/intrastructure/util/ServiceListener.java
new file mode 100644
index 00000000..02dda741
--- /dev/null
+++ b/desafio-mobile-android/app/src/main/java/com/app/desafio_mobile_android/intrastructure/util/ServiceListener.java
@@ -0,0 +1,10 @@
+package com.app.desafio_mobile_android.intrastructure.util;
+
+import com.app.desafio_mobile_android.intrastructure.error.OperationError;
+
+public abstract class ServiceListener {
+
+ public abstract void onServiceSuccess(final TResult result);
+
+ public abstract void onServiceError(final OperationError error);
+}
diff --git a/desafio-mobile-android/app/src/main/java/com/app/desafio_mobile_android/presentation/base/BaseActivity.java b/desafio-mobile-android/app/src/main/java/com/app/desafio_mobile_android/presentation/base/BaseActivity.java
new file mode 100644
index 00000000..1525fd85
--- /dev/null
+++ b/desafio-mobile-android/app/src/main/java/com/app/desafio_mobile_android/presentation/base/BaseActivity.java
@@ -0,0 +1,64 @@
+package com.app.desafio_mobile_android.presentation.base;
+
+import android.support.v7.app.ActionBar;
+import android.support.v7.app.AppCompatActivity;
+import android.support.v7.widget.Toolbar;
+
+import com.app.desafio_mobile_android.R;
+import com.app.desafio_mobile_android.intrastructure.error.OperationError;
+import com.app.desafio_mobile_android.presentation.dialog.ErrorDialogFragment;
+import com.app.desafio_mobile_android.presentation.dialog.ProgressDialogFragment;
+
+public class BaseActivity extends AppCompatActivity {
+ private static final String FRAGMENT_ALERT_ID = "FRAGMENT_ALERT_ID";
+ private ProgressDialogFragment mProgressDialog;
+ private ErrorDialogFragment errorDialog;
+
+ public void showError(OperationError error, ErrorDialogFragment.MyOnErrorClick myOnErrorClick) {
+ errorDialog = ErrorDialogFragment.newInstance(getErrorMsg(error));
+ errorDialog.setListener(myOnErrorClick);
+ errorDialog.show(getSupportFragmentManager(), FRAGMENT_ALERT_ID);
+ }
+
+ private String getErrorMsg(OperationError error) {
+
+ switch (error.getErrorType()) {
+ case NETWORK_ERROR:
+ return getString(R.string.default_network_error);
+ case SERVICE_ERROR:
+ return getString(R.string.service_error);
+ case GENERIC_ERROR:
+ default:
+ return getString(R.string.generic_error);
+ }
+ }
+
+ public void showProgress() {
+ mProgressDialog = new ProgressDialogFragment();
+ mProgressDialog.show(getSupportFragmentManager(), FRAGMENT_ALERT_ID);
+ }
+
+
+ public void hideProgress() {
+ mProgressDialog.dismiss();
+ }
+
+ protected void initToolbar(Toolbar toolbar, String title, boolean hasHomeAsUpIndicator) {
+ setTitle(title);
+ setSupportActionBar(toolbar);
+
+ if (hasHomeAsUpIndicator) {
+ configureToolbarHomeAsUpIndicator();
+ }
+ }
+
+ protected void configureToolbarHomeAsUpIndicator() {
+ ActionBar toolbar = getSupportActionBar();
+
+ if (toolbar != null) {
+ toolbar.setHomeButtonEnabled(true);
+ toolbar.setDisplayHomeAsUpEnabled(true);
+ toolbar.setDisplayShowHomeEnabled(true);
+ }
+ }
+}
diff --git a/desafio-mobile-android/app/src/main/java/com/app/desafio_mobile_android/presentation/base/BaseFragment.java b/desafio-mobile-android/app/src/main/java/com/app/desafio_mobile_android/presentation/base/BaseFragment.java
new file mode 100644
index 00000000..3c088db1
--- /dev/null
+++ b/desafio-mobile-android/app/src/main/java/com/app/desafio_mobile_android/presentation/base/BaseFragment.java
@@ -0,0 +1,10 @@
+package com.app.desafio_mobile_android.presentation.base;
+
+import android.support.v4.app.Fragment;
+
+import com.app.desafio_mobile_android.intrastructure.error.OperationError;
+
+
+public class BaseFragment extends Fragment {
+
+}
diff --git a/desafio-mobile-android/app/src/main/java/com/app/desafio_mobile_android/presentation/base/BasePresenter.java b/desafio-mobile-android/app/src/main/java/com/app/desafio_mobile_android/presentation/base/BasePresenter.java
new file mode 100644
index 00000000..0307d03f
--- /dev/null
+++ b/desafio-mobile-android/app/src/main/java/com/app/desafio_mobile_android/presentation/base/BasePresenter.java
@@ -0,0 +1,6 @@
+package com.app.desafio_mobile_android.presentation.base;
+
+public interface BasePresenter {
+
+ void start();
+}
diff --git a/desafio-mobile-android/app/src/main/java/com/app/desafio_mobile_android/presentation/base/BaseView.java b/desafio-mobile-android/app/src/main/java/com/app/desafio_mobile_android/presentation/base/BaseView.java
new file mode 100644
index 00000000..5e59e611
--- /dev/null
+++ b/desafio-mobile-android/app/src/main/java/com/app/desafio_mobile_android/presentation/base/BaseView.java
@@ -0,0 +1,17 @@
+package com.app.desafio_mobile_android.presentation.base;
+
+import android.content.Context;
+import android.support.annotation.NonNull;
+
+import com.app.desafio_mobile_android.intrastructure.error.OperationError;
+
+public interface BaseView {
+
+ Context getContext();
+
+ void setPresenter(@NonNull T presenter);
+
+ void showError(OperationError error);
+
+ void setLoading(boolean isLoading);
+}
diff --git a/desafio-mobile-android/app/src/main/java/com/app/desafio_mobile_android/presentation/dialog/ErrorDialogFragment.java b/desafio-mobile-android/app/src/main/java/com/app/desafio_mobile_android/presentation/dialog/ErrorDialogFragment.java
new file mode 100644
index 00000000..f47b62d5
--- /dev/null
+++ b/desafio-mobile-android/app/src/main/java/com/app/desafio_mobile_android/presentation/dialog/ErrorDialogFragment.java
@@ -0,0 +1,91 @@
+package com.app.desafio_mobile_android.presentation.dialog;
+
+import android.app.Dialog;
+import android.graphics.Color;
+import android.graphics.drawable.ColorDrawable;
+import android.os.Bundle;
+import android.support.annotation.Nullable;
+import android.support.v4.app.DialogFragment;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+import com.app.desafio_mobile_android.R;
+
+import butterknife.BindView;
+import butterknife.ButterKnife;
+import butterknife.OnClick;
+import butterknife.Unbinder;
+
+public class ErrorDialogFragment extends DialogFragment {
+ private final static String KEY_ERROR_MSG = "KEY_ERROR_MSG";
+
+ @BindView(R.id.textview_dialog_error_description)
+ TextView mTextviewErrorDescription;
+
+ private Unbinder unbinder;
+ private MyOnErrorClick listener;
+
+ public static ErrorDialogFragment newInstance(String errorMsg) {
+
+ Bundle args = new Bundle();
+
+ ErrorDialogFragment fragment = new ErrorDialogFragment();
+ args.putString(KEY_ERROR_MSG, errorMsg);
+ fragment.setArguments(args);
+ return fragment;
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+ View view = inflater.inflate(R.layout.fragment_dialog_erro, null);
+ unbinder = ButterKnife.bind(this, view);
+
+ return view;
+ }
+
+ @Override
+ public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
+ super.onViewCreated(view, savedInstanceState);
+ configMsgError(getArguments().getString(KEY_ERROR_MSG));
+ }
+
+ private void configMsgError(String errorMsg) {
+ mTextviewErrorDescription.setText(errorMsg);
+ }
+
+ @Override
+ public void onStart() {
+ super.onStart();
+
+ Dialog dialog = getDialog();
+ if (dialog != null) {
+ dialog.getWindow().setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
+ dialog.getWindow().setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
+ }
+ }
+
+ @Override
+ public void onDestroyView() {
+ super.onDestroyView();
+ unbinder.unbind();
+ }
+
+ @OnClick(R.id.error_button_try_again)
+ public void clickTryAgain() {
+ if (listener != null) {
+ listener.clickTryAgain();
+ }
+ }
+
+ public void setListener(MyOnErrorClick listener) {
+ this.listener = listener;
+ }
+
+ public interface MyOnErrorClick {
+ void clickTryAgain();
+ }
+
+
+}
\ No newline at end of file
diff --git a/desafio-mobile-android/app/src/main/java/com/app/desafio_mobile_android/presentation/dialog/ProgressDialogFragment.java b/desafio-mobile-android/app/src/main/java/com/app/desafio_mobile_android/presentation/dialog/ProgressDialogFragment.java
new file mode 100644
index 00000000..7e49b975
--- /dev/null
+++ b/desafio-mobile-android/app/src/main/java/com/app/desafio_mobile_android/presentation/dialog/ProgressDialogFragment.java
@@ -0,0 +1,38 @@
+package com.app.desafio_mobile_android.presentation.dialog;
+
+import android.app.Dialog;
+import android.graphics.Color;
+import android.graphics.drawable.ColorDrawable;
+import android.os.Bundle;
+import android.support.v4.app.DialogFragment;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.app.desafio_mobile_android.R;
+
+
+public class ProgressDialogFragment extends DialogFragment {
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+ View view = inflater.inflate(R.layout.fragment_dialog_loading, null);
+ return view;
+ }
+
+ @Override
+ public void onDestroyView() {
+ super.onDestroyView();
+ }
+
+ @Override
+ public void onStart() {
+ super.onStart();
+
+ Dialog dialog = getDialog();
+ if (dialog != null) {
+ dialog.getWindow().setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
+ dialog.getWindow().setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
+ }
+ }
+}
\ No newline at end of file
diff --git a/desafio-mobile-android/app/src/main/java/com/app/desafio_mobile_android/presentation/home/MainActivity.java b/desafio-mobile-android/app/src/main/java/com/app/desafio_mobile_android/presentation/home/MainActivity.java
new file mode 100644
index 00000000..fd8e785a
--- /dev/null
+++ b/desafio-mobile-android/app/src/main/java/com/app/desafio_mobile_android/presentation/home/MainActivity.java
@@ -0,0 +1,92 @@
+package com.app.desafio_mobile_android.presentation.home;
+
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Bundle;
+import android.support.design.widget.NavigationView;
+import android.support.v4.app.FragmentManager;
+import android.support.v4.view.GravityCompat;
+import android.support.v4.widget.DrawerLayout;
+import android.support.v7.app.ActionBarDrawerToggle;
+import android.support.v7.widget.Toolbar;
+import android.view.MenuItem;
+
+import com.app.desafio_mobile_android.R;
+import com.app.desafio_mobile_android.intrastructure.Constants;
+import com.app.desafio_mobile_android.intrastructure.util.ActivityUtils;
+import com.app.desafio_mobile_android.presentation.base.BaseActivity;
+import com.app.desafio_mobile_android.presentation.home.repositorielist.RepositorieListFragment;
+import com.app.desafio_mobile_android.presentation.home.repositorielist.RepositorieListPresenter;
+
+import butterknife.BindView;
+import butterknife.ButterKnife;
+
+public class MainActivity extends BaseActivity
+ implements NavigationView.OnNavigationItemSelectedListener {
+
+ @BindView(R.id.toolbar)
+ Toolbar mToolbar;
+ @BindView(R.id.nav_view)
+ NavigationView mNavView;
+ @BindView(R.id.drawer_layout)
+ DrawerLayout mDrawerLayout;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_main);
+ ButterKnife.bind(this);
+ configDrawer();
+ showHome();
+ }
+
+ private void configDrawer() {
+ setSupportActionBar(mToolbar);
+
+ ActionBarDrawerToggle toggle = new ActionBarDrawerToggle(
+ this, mDrawerLayout, mToolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close);
+ mDrawerLayout.setDrawerListener(toggle);
+ toggle.syncState();
+
+ mNavView.setNavigationItemSelectedListener(this);
+ }
+
+ @Override
+ public void onBackPressed() {
+ if (mDrawerLayout.isDrawerOpen(GravityCompat.START)) {
+ mDrawerLayout.closeDrawer(GravityCompat.START);
+ } else {
+ super.onBackPressed();
+ }
+ }
+
+ @SuppressWarnings("StatementWithEmptyBody")
+ @Override
+ public boolean onNavigationItemSelected(MenuItem item) {
+ int id = item.getItemId();
+
+ if (id == R.id.nav_home) {
+ showHome();
+ } else if (id == R.id.nav_about) {
+ showAbout();
+ }
+
+ mDrawerLayout.closeDrawer(GravityCompat.START);
+ return true;
+ }
+
+ private void showHome() {
+ FragmentManager fragmentManager = getSupportFragmentManager();
+ RepositorieListFragment fragment = (RepositorieListFragment) fragmentManager.findFragmentById(R.id.framelayout_main);
+ if(fragment == null) {
+ fragment = RepositorieListFragment.newInstance();
+ ActivityUtils.addFragmentToActivity(fragmentManager, fragment, R.id.framelayout_main);
+ }
+
+ fragment.setPresenter(new RepositorieListPresenter(this, fragment));
+ }
+
+ private void showAbout() {
+ startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(Constants.ABOUT_URL)));
+ }
+}
diff --git a/desafio-mobile-android/app/src/main/java/com/app/desafio_mobile_android/presentation/home/repositorielist/RepositorieListAdapter.java b/desafio-mobile-android/app/src/main/java/com/app/desafio_mobile_android/presentation/home/repositorielist/RepositorieListAdapter.java
new file mode 100644
index 00000000..819a283a
--- /dev/null
+++ b/desafio-mobile-android/app/src/main/java/com/app/desafio_mobile_android/presentation/home/repositorielist/RepositorieListAdapter.java
@@ -0,0 +1,111 @@
+package com.app.desafio_mobile_android.presentation.home.repositorielist;
+
+import android.content.Context;
+import android.support.v7.widget.RecyclerView;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.RelativeLayout;
+import android.widget.TextView;
+
+import com.app.desafio_mobile_android.R;
+import com.app.desafio_mobile_android.intrastructure.util.CircleTransform;
+import com.app.desafio_mobile_android.service.model.repositorie.RepositorieItem;
+import com.squareup.picasso.Picasso;
+
+import java.text.NumberFormat;
+import java.util.List;
+
+import butterknife.BindView;
+import butterknife.ButterKnife;
+import butterknife.OnClick;
+
+public class RepositorieListAdapter extends RecyclerView.Adapter {
+
+ private List mRepositorieItems;
+ private Context mContext;
+ private RepositorieListPresenter.RepositorieListListener mListListener;
+
+ public RepositorieListAdapter(Context mContext, List repositorieItems, RepositorieListPresenter.RepositorieListListener listListener) {
+ this.mContext = mContext;
+ this.mRepositorieItems = repositorieItems;
+ this.mListListener = listListener;
+ }
+
+ @Override
+ public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+ LayoutInflater inflater = LayoutInflater.from(parent.getContext());
+ View view = inflater.inflate(R.layout.item_repositorie_list, parent, false);
+ return new ViewHolder(view);
+ }
+
+ @Override
+ public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
+ configHolder(mRepositorieItems.get(position), (ViewHolder) holder);
+ }
+
+ @Override
+ public int getItemCount() {
+ return mRepositorieItems.size();
+ }
+
+ public void addAll(List repositorieItemList) {
+ for (RepositorieItem repositorieItem : repositorieItemList) {
+ add(repositorieItem);
+ }
+ }
+
+ public void add(RepositorieItem repositorieItem) {
+ mRepositorieItems.add(repositorieItem);
+ notifyItemInserted(mRepositorieItems.size() - 1);
+ }
+
+ private void configHolder(RepositorieItem item, ViewHolder holder) {
+ setAvatar(item, holder);
+ holder.mTextViewRepoName.setText(item.getName());
+ holder.mTextViewDescription.setText(item.getDescription());
+ NumberFormat numberFormat = NumberFormat.getInstance();
+ holder.mTextViewForks.setText(numberFormat.format(item.getForks()));
+ holder.mTextViewStars.setText(numberFormat.format(item.getStargazersCount()));
+ holder.mTextViewUsername.setText(item.getOwner().getLogin());
+ holder.mTextViewFullName.setText(item.getFullName());
+ }
+
+ private void setAvatar(RepositorieItem item, ViewHolder holder) {
+ Picasso.with(mContext).load(item.getOwner().getAvatarUrl())
+ .transform(new CircleTransform())
+ .placeholder(R.drawable.ic_user)
+ .into(holder.mImageViewAvatar);
+ }
+
+
+ class ViewHolder extends RecyclerView.ViewHolder {
+ @BindView(R.id.textview_item_repositorie_list_repo_name)
+ TextView mTextViewRepoName;
+ @BindView(R.id.textview_item_respositorie_list_description)
+ TextView mTextViewDescription;
+ @BindView(R.id.textview_item_respositorie_list_forks)
+ TextView mTextViewForks;
+ @BindView(R.id.textview_item_respositorie_list_stars)
+ TextView mTextViewStars;
+ @BindView(R.id.imageview_item_respositorie_list_avatar)
+ ImageView mImageViewAvatar;
+ @BindView(R.id.textview_item_respositorie_list_username)
+ TextView mTextViewUsername;
+ @BindView(R.id.textview_item_respositorie_list_fullname)
+ TextView mTextViewFullName;
+ @BindView(R.id.relativelayout_item_repositorie_main_content)
+ RelativeLayout mRelativeLayoutMainContent;
+
+ ViewHolder(View view) {
+ super(view);
+ ButterKnife.bind(this, view);
+ }
+
+ @OnClick(R.id.relativelayout_item_repositorie_main_content)
+ public void onViewClicked() {
+ mListListener.onItemSelected(getAdapterPosition());
+ }
+ }
+}
diff --git a/desafio-mobile-android/app/src/main/java/com/app/desafio_mobile_android/presentation/home/repositorielist/RepositorieListContract.java b/desafio-mobile-android/app/src/main/java/com/app/desafio_mobile_android/presentation/home/repositorielist/RepositorieListContract.java
new file mode 100644
index 00000000..fe02f76d
--- /dev/null
+++ b/desafio-mobile-android/app/src/main/java/com/app/desafio_mobile_android/presentation/home/repositorielist/RepositorieListContract.java
@@ -0,0 +1,20 @@
+package com.app.desafio_mobile_android.presentation.home.repositorielist;
+
+import com.app.desafio_mobile_android.presentation.base.BasePresenter;
+import com.app.desafio_mobile_android.presentation.base.BaseView;
+
+public class RepositorieListContract {
+
+ interface View extends BaseView {
+ void updateList(RepositorieListAdapter adapter);
+
+ void updatePageSize(int pageSize);
+
+ void showPullRequest(String owner, String repo);
+ }
+
+ interface Presenter extends BasePresenter {
+ void callService();
+ void refreshList();
+ }
+}
diff --git a/desafio-mobile-android/app/src/main/java/com/app/desafio_mobile_android/presentation/home/repositorielist/RepositorieListFragment.java b/desafio-mobile-android/app/src/main/java/com/app/desafio_mobile_android/presentation/home/repositorielist/RepositorieListFragment.java
new file mode 100644
index 00000000..fefb172c
--- /dev/null
+++ b/desafio-mobile-android/app/src/main/java/com/app/desafio_mobile_android/presentation/home/repositorielist/RepositorieListFragment.java
@@ -0,0 +1,151 @@
+package com.app.desafio_mobile_android.presentation.home.repositorielist;
+
+import android.content.Context;
+import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+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 com.app.desafio_mobile_android.R;
+import com.app.desafio_mobile_android.intrastructure.error.OperationError;
+import com.app.desafio_mobile_android.presentation.base.BaseFragment;
+import com.app.desafio_mobile_android.presentation.dialog.ErrorDialogFragment;
+import com.app.desafio_mobile_android.presentation.home.MainActivity;
+import com.app.desafio_mobile_android.presentation.pull.PullActivity;
+
+import butterknife.BindView;
+import butterknife.ButterKnife;
+import butterknife.Unbinder;
+
+public class RepositorieListFragment extends BaseFragment implements RepositorieListContract.View {
+
+ @BindView(R.id.recyclerview_repositorie_list)
+ RecyclerView mRecyclerview;
+ Unbinder unbinder;
+ private MainActivity mMainActivity;
+ private RepositorieListContract.Presenter mPresenter;
+ private int mPageSize;
+ private boolean mIsLoading;
+ private ErrorListener mErrorListener;
+
+ public static RepositorieListFragment newInstance() {
+
+ Bundle args = new Bundle();
+
+ RepositorieListFragment fragment = new RepositorieListFragment();
+ fragment.setArguments(args);
+ return fragment;
+ }
+
+ @Override
+ public void onAttach(Context context) {
+ super.onAttach(context);
+ mMainActivity = (MainActivity) context;
+ }
+
+ @Nullable
+ @Override
+ public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
+ View view = inflater.inflate(R.layout.fragment_repositorie_list, container, false);
+ unbinder = ButterKnife.bind(this, view);
+ mErrorListener = new ErrorListener();
+ initRecyclerView();
+ return view;
+ }
+
+ @Override
+ public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
+ super.onViewCreated(view, savedInstanceState);
+ mPresenter.start();
+ }
+
+ @Override
+ public void onDestroyView() {
+ super.onDestroyView();
+ unbinder.unbind();
+ }
+
+ @Override
+ public void setPresenter(@NonNull RepositorieListContract.Presenter presenter) {
+ mPresenter = presenter;
+ }
+
+ @Override
+ public void showError(OperationError error) {
+ mMainActivity.showError(error, mErrorListener);
+ }
+
+ @Override
+ public void updateList(RepositorieListAdapter adapter) {
+ mRecyclerview.setAdapter(adapter);
+ }
+
+ @Override
+ public void setLoading(boolean isLoading) {
+ mIsLoading = isLoading;
+ if (isLoading) {
+ mMainActivity.showProgress();
+ } else {
+ mMainActivity.hideProgress();
+ }
+ }
+
+ @Override
+ public void updatePageSize(int pageSize) {
+ mPageSize = pageSize;
+ }
+
+ @Override
+ public void showPullRequest(String owner, String repo) {
+ startActivity(PullActivity.getLaunchIntent(mMainActivity, owner, repo));
+
+ }
+
+ private void initRecyclerView() {
+ LinearLayoutManager linearLayoutManager = new LinearLayoutManager(mMainActivity);
+ mRecyclerview.setLayoutManager(linearLayoutManager);
+ mRecyclerview.addOnScrollListener(new RecyclerViewListener(linearLayoutManager));
+ }
+
+ public class RecyclerViewListener extends RecyclerView.OnScrollListener {
+
+ LinearLayoutManager mLayoutManager;
+
+ public RecyclerViewListener(LinearLayoutManager linearLayoutManager) {
+ this.mLayoutManager = linearLayoutManager;
+ }
+
+ @Override
+ public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
+ super.onScrollStateChanged(recyclerView, newState);
+ }
+
+ @Override
+ public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
+ super.onScrolled(recyclerView, dx, dy);
+ int visibleItemCount = mLayoutManager.getChildCount();
+ int totalItemCount = mLayoutManager.getItemCount();
+ int firstVisibleItemPosition = mLayoutManager.findFirstVisibleItemPosition();
+
+ if (!mIsLoading) {
+ if ((visibleItemCount + firstVisibleItemPosition) >= totalItemCount
+ && firstVisibleItemPosition >= 0
+ && totalItemCount >= mPageSize) {
+ mPresenter.refreshList();
+ }
+ }
+ }
+ }
+
+ private class ErrorListener implements ErrorDialogFragment.MyOnErrorClick {
+
+ @Override
+ public void clickTryAgain() {
+ mPresenter.callService();
+ }
+ }
+}
diff --git a/desafio-mobile-android/app/src/main/java/com/app/desafio_mobile_android/presentation/home/repositorielist/RepositorieListPresenter.java b/desafio-mobile-android/app/src/main/java/com/app/desafio_mobile_android/presentation/home/repositorielist/RepositorieListPresenter.java
new file mode 100644
index 00000000..cb32882f
--- /dev/null
+++ b/desafio-mobile-android/app/src/main/java/com/app/desafio_mobile_android/presentation/home/repositorielist/RepositorieListPresenter.java
@@ -0,0 +1,91 @@
+package com.app.desafio_mobile_android.presentation.home.repositorielist;
+
+import android.content.Context;
+
+import com.app.desafio_mobile_android.business.RepositorieBusiness;
+import com.app.desafio_mobile_android.intrastructure.error.OperationError;
+import com.app.desafio_mobile_android.intrastructure.util.ServiceListener;
+import com.app.desafio_mobile_android.service.model.repositorie.Repositorie;
+import com.app.desafio_mobile_android.service.model.repositorie.RepositorieItem;
+
+import java.util.List;
+
+public class RepositorieListPresenter implements RepositorieListContract.Presenter {
+ private final static int FIRST_PAGE_REPOSITORIE = 1;
+
+ private RepositorieListContract.View mView;
+ private RepositorieBusiness mRepositorieBusiness;
+ private int mPageRequest;
+ private RepositorieListAdapter mAdapter;
+ private List mRepositorieItemList;
+ private Context mContext;
+
+ public RepositorieListPresenter(Context context, RepositorieListContract.View view) {
+ this.mView = view;
+ mContext = context;
+ mRepositorieBusiness = new RepositorieBusiness(mContext);
+ mPageRequest = FIRST_PAGE_REPOSITORIE;
+ }
+
+ @Override
+ public void start() {
+ callService();
+ }
+
+ @Override
+ public void callService() {
+ mView.setLoading(true);
+ mRepositorieBusiness.callServiceRepositorie(mPageRequest, new RequestRepositoriesCallback());
+ }
+
+ @Override
+ public void refreshList() {
+ mPageRequest += 1;
+ callService();
+ }
+
+ public class RequestRepositoriesCallback extends ServiceListener {
+
+ @Override
+ public void onServiceSuccess(Repositorie repositorie) {
+ mView.setLoading(false);
+ mView.updatePageSize(repositorie.getItems().size());
+ if (mAdapter == null) {
+ configAdapter(repositorie.getItems());
+ } else {
+ notifyMoreItens(repositorie.getItems());
+ }
+ }
+
+ @Override
+ public void onServiceError(OperationError error) {
+ mView.setLoading(false);
+ mView.showError(error);
+ }
+
+ private void configAdapter(List items) {
+ mRepositorieItemList = items;
+ mAdapter = new RepositorieListAdapter(mContext, mRepositorieItemList, new RepositorieCallback());
+ mView.updateList(mAdapter);
+ }
+
+ private void notifyMoreItens(List items) {
+ mAdapter.addAll(items);
+ }
+ }
+
+
+ public class RepositorieCallback implements RepositorieListListener {
+
+ @Override
+ public void onItemSelected(int position) {
+ RepositorieItem repositorieItem = mRepositorieItemList.get(position);
+ mView.showPullRequest(repositorieItem.getOwner().getLogin(),
+ repositorieItem.getName());
+ }
+ }
+
+ interface RepositorieListListener {
+ void onItemSelected(int position);
+ }
+}
diff --git a/desafio-mobile-android/app/src/main/java/com/app/desafio_mobile_android/presentation/pull/PullActivity.java b/desafio-mobile-android/app/src/main/java/com/app/desafio_mobile_android/presentation/pull/PullActivity.java
new file mode 100644
index 00000000..1d4d09ce
--- /dev/null
+++ b/desafio-mobile-android/app/src/main/java/com/app/desafio_mobile_android/presentation/pull/PullActivity.java
@@ -0,0 +1,65 @@
+package com.app.desafio_mobile_android.presentation.pull;
+
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.support.annotation.Nullable;
+import android.support.v4.app.FragmentManager;
+import android.support.v7.widget.Toolbar;
+import android.view.MenuItem;
+
+import com.app.desafio_mobile_android.R;
+import com.app.desafio_mobile_android.intrastructure.util.ActivityUtils;
+import com.app.desafio_mobile_android.presentation.base.BaseActivity;
+
+import butterknife.BindView;
+import butterknife.ButterKnife;
+
+public class PullActivity extends BaseActivity {
+ private final static String KEY_OWNER = "KEY_OWNER";
+ private final static String KEY_REPO = "KEY_REPO";
+
+ @BindView(R.id.toolbar)
+ Toolbar mToolbar;
+
+ @Override
+ protected void onCreate(@Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_pull);
+ ButterKnife.bind(this);
+
+ inflateFragment();
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ if (item.getItemId() == android.R.id.home) {
+ super.onBackPressed();
+ return true;
+ }
+
+ return super.onOptionsItemSelected(item);
+ }
+
+ private void inflateFragment() {
+ FragmentManager fragmentManager = getSupportFragmentManager();
+ PullFragment fragment = (PullFragment) fragmentManager.findFragmentById(R.id.framelayout_pull);
+ if (fragment == null) {
+ fragment = PullFragment.newInstance();
+ ActivityUtils.addFragmentToActivity(fragmentManager, fragment, R.id.framelayout_pull);
+ }
+
+ String owner = getIntent().getExtras().getString(KEY_OWNER);
+ String repo = getIntent().getExtras().getString(KEY_REPO);
+
+ initToolbar(mToolbar, repo, true);
+ fragment.setPresenter(new PullPresenter(this, owner, repo, fragment));
+ }
+
+ public static Intent getLaunchIntent(Context context, String owner, String repo) {
+ Intent intent = new Intent(context, PullActivity.class);
+ intent.putExtra(KEY_OWNER, owner);
+ intent.putExtra(KEY_REPO, repo);
+ return intent;
+ }
+}
diff --git a/desafio-mobile-android/app/src/main/java/com/app/desafio_mobile_android/presentation/pull/PullAdapter.java b/desafio-mobile-android/app/src/main/java/com/app/desafio_mobile_android/presentation/pull/PullAdapter.java
new file mode 100644
index 00000000..fd07702d
--- /dev/null
+++ b/desafio-mobile-android/app/src/main/java/com/app/desafio_mobile_android/presentation/pull/PullAdapter.java
@@ -0,0 +1,88 @@
+package com.app.desafio_mobile_android.presentation.pull;
+
+import android.content.Context;
+import android.support.v7.widget.RecyclerView;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import com.app.desafio_mobile_android.R;
+import com.app.desafio_mobile_android.intrastructure.util.CircleTransform;
+import com.app.desafio_mobile_android.service.model.pull.Pull;
+import com.squareup.picasso.Picasso;
+
+import java.util.List;
+
+import butterknife.BindView;
+import butterknife.ButterKnife;
+import butterknife.OnClick;
+
+public class PullAdapter extends RecyclerView.Adapter {
+
+ private List mPullList;
+ private Context mContext;
+ private PullPresenter.PullListListener mPullListListener;
+
+ public PullAdapter(Context mContext, List pullList, PullPresenter.PullListListener pullListListener) {
+ this.mContext = mContext;
+ this.mPullList = pullList;
+ this.mPullListListener = pullListListener;
+ }
+
+ @Override
+ public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+ LayoutInflater inflater = LayoutInflater.from(parent.getContext());
+ View view = inflater.inflate(R.layout.item_pull, parent, false);
+ return new ViewHolder(view);
+ }
+
+ @Override
+ public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
+ configHolder(mPullList.get(position), (ViewHolder) holder);
+ }
+
+ @Override
+ public int getItemCount() {
+ return mPullList.size();
+ }
+
+ private void configHolder(Pull item, ViewHolder holder) {
+ setAvatar(item, holder);
+ holder.mTextViewName.setText(item.getTitle());
+ holder.mTextViewDescription.setText(item.getBody());
+ holder.mTextViewUsername.setText(item.getUser().getLogin());
+ holder.mTextViewDate.setText(item.dateCreate());
+ }
+
+ private void setAvatar(Pull item, ViewHolder holder) {
+ Picasso.with(mContext).load(item.getUser().getAvatarUrl())
+ .transform(new CircleTransform())
+ .placeholder(R.drawable.ic_user)
+ .into(holder.mImageViewAvatar);
+ }
+
+ class ViewHolder extends RecyclerView.ViewHolder {
+ @BindView(R.id.textview_item_pull_name)
+ TextView mTextViewName;
+ @BindView(R.id.textview_item_pull_description)
+ TextView mTextViewDescription;
+ @BindView(R.id.imageview_item_pull_user_avatar)
+ ImageView mImageViewAvatar;
+ @BindView(R.id.textview_item_pull_username)
+ TextView mTextViewUsername;
+ @BindView(R.id.textview_item_pull_date)
+ TextView mTextViewDate;
+
+ ViewHolder(View view) {
+ super(view);
+ ButterKnife.bind(this, view);
+ }
+
+ @OnClick(R.id.relativelayout_item_pull_main_content)
+ public void onViewClicked() {
+ mPullListListener.onItemClick(getAdapterPosition());
+ }
+ }
+}
diff --git a/desafio-mobile-android/app/src/main/java/com/app/desafio_mobile_android/presentation/pull/PullContract.java b/desafio-mobile-android/app/src/main/java/com/app/desafio_mobile_android/presentation/pull/PullContract.java
new file mode 100644
index 00000000..8f95fbe4
--- /dev/null
+++ b/desafio-mobile-android/app/src/main/java/com/app/desafio_mobile_android/presentation/pull/PullContract.java
@@ -0,0 +1,19 @@
+package com.app.desafio_mobile_android.presentation.pull;
+
+
+import com.app.desafio_mobile_android.presentation.base.BasePresenter;
+import com.app.desafio_mobile_android.presentation.base.BaseView;
+
+public class PullContract {
+
+ interface View extends BaseView {
+ void updateList(PullAdapter adapter);
+ void showPage(String url);
+ void showHeader(int closed, int opened);
+ }
+
+ interface Presenter extends BasePresenter {
+ void callPullListService();
+ }
+
+}
diff --git a/desafio-mobile-android/app/src/main/java/com/app/desafio_mobile_android/presentation/pull/PullFragment.java b/desafio-mobile-android/app/src/main/java/com/app/desafio_mobile_android/presentation/pull/PullFragment.java
new file mode 100644
index 00000000..71ba013a
--- /dev/null
+++ b/desafio-mobile-android/app/src/main/java/com/app/desafio_mobile_android/presentation/pull/PullFragment.java
@@ -0,0 +1,128 @@
+package com.app.desafio_mobile_android.presentation.pull;
+
+import android.content.Context;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.v4.content.ContextCompat;
+import android.support.v7.widget.LinearLayoutManager;
+import android.support.v7.widget.RecyclerView;
+import android.text.Spannable;
+import android.text.SpannableString;
+import android.text.style.ForegroundColorSpan;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+import com.app.desafio_mobile_android.R;
+import com.app.desafio_mobile_android.intrastructure.error.OperationError;
+import com.app.desafio_mobile_android.presentation.base.BaseFragment;
+import com.app.desafio_mobile_android.presentation.dialog.ErrorDialogFragment;
+
+import butterknife.BindView;
+import butterknife.ButterKnife;
+import butterknife.Unbinder;
+
+public class PullFragment extends BaseFragment implements PullContract.View {
+ @BindView(R.id.recyclerview_pull)
+ RecyclerView mRecyclerview;
+ @BindView(R.id.textview_pull_header)
+ TextView mTextviewHeader;
+
+ private Unbinder unbinder;
+ private PullContract.Presenter mPresenter;
+ private PullActivity mActivity;
+
+
+ public static PullFragment newInstance() {
+
+ Bundle args = new Bundle();
+
+ PullFragment fragment = new PullFragment();
+ fragment.setArguments(args);
+ return fragment;
+ }
+
+ @Override
+ public void onAttach(Context context) {
+ super.onAttach(context);
+ mActivity = (PullActivity) context;
+ }
+
+ @Nullable
+ @Override
+ public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
+ View view = inflater.inflate(R.layout.fragment_pull, container, false);
+ unbinder = ButterKnife.bind(this, view);
+ initRecyclerView();
+ return view;
+ }
+
+ @Override
+ public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
+ super.onViewCreated(view, savedInstanceState);
+ mPresenter.start();
+ }
+
+ @Override
+ public void onDestroyView() {
+ super.onDestroyView();
+ unbinder.unbind();
+ }
+
+ @Override
+ public void setPresenter(@NonNull PullContract.Presenter presenter) {
+ mPresenter = presenter;
+ }
+
+ @Override
+ public void showError(OperationError error) {
+ mActivity.showError(error, new ErroClickListener());
+ }
+
+ @Override
+ public void updateList(PullAdapter adapter) {
+ mRecyclerview.setAdapter(adapter);
+ }
+
+ @Override
+ public void setLoading(boolean isLoading) {
+ if (isLoading) {
+ mActivity.showProgress();
+ } else {
+ mActivity.hideProgress();
+ }
+ }
+
+ @Override
+ public void showPage(String url) {
+ startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(url)));
+ }
+
+ @Override
+ public void showHeader(int closed, int opened) {
+ String header = String.format(getString(R.string.pull_header), opened, closed);
+ Spannable wordtoSpan = new SpannableString(header);
+ wordtoSpan.setSpan(new ForegroundColorSpan(ContextCompat.getColor(mActivity, R.color.yellow_info)),
+ 0, header.indexOf("/"), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+
+ mTextviewHeader.setText(wordtoSpan);
+ }
+
+ private void initRecyclerView() {
+ LinearLayoutManager linearLayoutManager = new LinearLayoutManager(mActivity);
+ mRecyclerview.setLayoutManager(linearLayoutManager);
+ }
+
+ private class ErroClickListener implements ErrorDialogFragment.MyOnErrorClick {
+
+ @Override
+ public void clickTryAgain() {
+ mActivity.hideProgress();
+ mPresenter.callPullListService();
+ }
+ }
+}
diff --git a/desafio-mobile-android/app/src/main/java/com/app/desafio_mobile_android/presentation/pull/PullPresenter.java b/desafio-mobile-android/app/src/main/java/com/app/desafio_mobile_android/presentation/pull/PullPresenter.java
new file mode 100644
index 00000000..0f962a19
--- /dev/null
+++ b/desafio-mobile-android/app/src/main/java/com/app/desafio_mobile_android/presentation/pull/PullPresenter.java
@@ -0,0 +1,69 @@
+package com.app.desafio_mobile_android.presentation.pull;
+
+import android.content.Context;
+
+import com.app.desafio_mobile_android.business.PullBusiness;
+import com.app.desafio_mobile_android.intrastructure.error.OperationError;
+import com.app.desafio_mobile_android.intrastructure.util.ServiceListener;
+import com.app.desafio_mobile_android.service.model.pull.Pull;
+
+import java.util.List;
+
+public class PullPresenter implements PullContract.Presenter {
+ private String mOwnerName;
+ private String mRepoName;
+ private Context mContext;
+ private PullContract.View mView;
+ private PullBusiness mPullBusiness;
+ private RequestPullCallback mRequestPullCallback;
+ private List mPullList;
+
+ public PullPresenter(Context context, String ownerName, String repoName, PullContract.View view) {
+ this.mOwnerName = ownerName;
+ this.mRepoName = repoName;
+ this.mContext = context;
+ this.mView = view;
+ mPullBusiness = new PullBusiness(context);
+ mRequestPullCallback = new RequestPullCallback();
+ }
+
+ @Override
+ public void start() {
+ callPullListService();
+ }
+
+ @Override
+ public void callPullListService() {
+ mView.setLoading(true);
+ mPullBusiness.callPullList(mOwnerName, mRepoName, mRequestPullCallback);
+ }
+
+ private class RequestPullCallback extends ServiceListener {
+
+ @Override
+ public void onServiceSuccess(PullViewModel pullViewModel) {
+ mPullList = pullViewModel.getPullList();
+ mView.showHeader(pullViewModel.getClosed(), pullViewModel.getOpened());
+ mView.setLoading(false);
+ mView.updateList(new PullAdapter(mView.getContext(), mPullList, new OnPullListCallback()));
+ }
+
+ @Override
+ public void onServiceError(OperationError error) {
+ mView.setLoading(false);
+ mView.showError(error);
+ }
+ }
+
+ private class OnPullListCallback implements PullListListener {
+
+ @Override
+ public void onItemClick(int position) {
+ mView.showPage(mPullList.get(position).getUrl());
+ }
+ }
+
+ interface PullListListener {
+ void onItemClick(int position);
+ }
+}
diff --git a/desafio-mobile-android/app/src/main/java/com/app/desafio_mobile_android/presentation/pull/PullViewModel.java b/desafio-mobile-android/app/src/main/java/com/app/desafio_mobile_android/presentation/pull/PullViewModel.java
new file mode 100644
index 00000000..29160429
--- /dev/null
+++ b/desafio-mobile-android/app/src/main/java/com/app/desafio_mobile_android/presentation/pull/PullViewModel.java
@@ -0,0 +1,41 @@
+package com.app.desafio_mobile_android.presentation.pull;
+
+import com.app.desafio_mobile_android.service.model.pull.Pull;
+
+import java.util.List;
+
+public class PullViewModel {
+ private List pullList;
+ private int opened;
+ private int closed;
+
+ public PullViewModel(List pullList, int opened, int closed) {
+ this.pullList = pullList;
+ this.opened = opened;
+ this.closed = closed;
+ }
+
+ public List getPullList() {
+ return pullList;
+ }
+
+ public void setPullList(List pullList) {
+ this.pullList = pullList;
+ }
+
+ public int getOpened() {
+ return opened;
+ }
+
+ public void setOpened(int opened) {
+ this.opened = opened;
+ }
+
+ public int getClosed() {
+ return closed;
+ }
+
+ public void setClosed(int closed) {
+ this.closed = closed;
+ }
+}
diff --git a/desafio-mobile-android/app/src/main/java/com/app/desafio_mobile_android/service/Api.java b/desafio-mobile-android/app/src/main/java/com/app/desafio_mobile_android/service/Api.java
new file mode 100644
index 00000000..a538b940
--- /dev/null
+++ b/desafio-mobile-android/app/src/main/java/com/app/desafio_mobile_android/service/Api.java
@@ -0,0 +1,23 @@
+package com.app.desafio_mobile_android.service;
+
+import com.app.desafio_mobile_android.service.model.pull.Pull;
+import com.app.desafio_mobile_android.service.model.repositorie.Repositorie;
+
+import java.util.List;
+
+import retrofit2.Call;
+import retrofit2.http.GET;
+import retrofit2.http.Path;
+import retrofit2.http.Query;
+
+public interface Api {
+
+ @GET("search/repositories?q=language:Java")
+ Call getRepositorieList(@Query("sort") String sortType,
+ @Query("page") int pageNumber);
+
+ @GET("/repos/{owner}/{repo}/pulls")
+ Call> getPullList(@Path("owner") String owner,
+ @Path("repo") String repo,
+ @Query("state") String state);
+}
diff --git a/desafio-mobile-android/app/src/main/java/com/app/desafio_mobile_android/service/ApiInstance.java b/desafio-mobile-android/app/src/main/java/com/app/desafio_mobile_android/service/ApiInstance.java
new file mode 100644
index 00000000..d594ba31
--- /dev/null
+++ b/desafio-mobile-android/app/src/main/java/com/app/desafio_mobile_android/service/ApiInstance.java
@@ -0,0 +1,31 @@
+package com.app.desafio_mobile_android.service;
+
+import com.app.desafio_mobile_android.intrastructure.Constants;
+
+import okhttp3.OkHttpClient;
+import okhttp3.logging.HttpLoggingInterceptor;
+import retrofit2.Retrofit;
+import retrofit2.converter.gson.GsonConverterFactory;
+
+public class ApiInstance {
+
+ private static String BASE_URL = Constants.BASE_API;
+
+ public static Retrofit getAPI() {
+ HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
+ interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
+ OkHttpClient client = new OkHttpClient.Builder().addInterceptor(interceptor).build();
+
+ Retrofit retrofit = new Retrofit
+ .Builder()
+ .client(client)
+ .baseUrl(BASE_URL)
+ .addConverterFactory(GsonConverterFactory.create())
+ .build();
+
+
+
+ return retrofit;
+ }
+
+}
diff --git a/desafio-mobile-android/app/src/main/java/com/app/desafio_mobile_android/service/model/Owner.java b/desafio-mobile-android/app/src/main/java/com/app/desafio_mobile_android/service/model/Owner.java
new file mode 100644
index 00000000..11cb5a7f
--- /dev/null
+++ b/desafio-mobile-android/app/src/main/java/com/app/desafio_mobile_android/service/model/Owner.java
@@ -0,0 +1,173 @@
+package com.app.desafio_mobile_android.service.model;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.google.gson.annotations.SerializedName;
+
+/**
+ * Classe de mapeamento do objeto Owner - Reposta de lista de repositórios
+ */
+
+public class Owner implements Parcelable{
+ @SerializedName("login")
+ private String login;
+ @SerializedName("id")
+ private int id;
+ @SerializedName("avatar_url")
+ private String avatarUrl;
+ @SerializedName("gravatar_id")
+ private String gravatarId;
+ @SerializedName("url")
+ private String url;
+ @SerializedName("html_url")
+ private String htmlUrl;
+ @SerializedName("followers_url")
+ private String followersUrl;
+ @SerializedName("following_url")
+ private String followingUrl;
+ @SerializedName("gists_url")
+ private String gistsUrl;
+ @SerializedName("starred_url")
+ private String starredUrl;
+ @SerializedName("subscriptions_url")
+ private String subscriptionsUrl;
+ @SerializedName("organizations_url")
+ private String organizationsUrl;
+ @SerializedName("repos_url")
+ private String reposUrl;
+ @SerializedName("events_url")
+ private String eventsUrl;
+ @SerializedName("received_events_url")
+ private String receivedEventsUrl;
+ @SerializedName("type")
+ private String type;
+ @SerializedName("site_admin")
+ private boolean siteAdmin;
+
+ protected Owner(Parcel in) {
+ login = in.readString();
+ id = in.readInt();
+ avatarUrl = in.readString();
+ gravatarId = in.readString();
+ url = in.readString();
+ htmlUrl = in.readString();
+ followersUrl = in.readString();
+ followingUrl = in.readString();
+ gistsUrl = in.readString();
+ starredUrl = in.readString();
+ subscriptionsUrl = in.readString();
+ organizationsUrl = in.readString();
+ reposUrl = in.readString();
+ eventsUrl = in.readString();
+ receivedEventsUrl = in.readString();
+ type = in.readString();
+ siteAdmin = in.readByte() != 0;
+ }
+
+ public static final Creator CREATOR = new Creator() {
+ @Override
+ public Owner createFromParcel(Parcel in) {
+ return new Owner(in);
+ }
+
+ @Override
+ public Owner[] newArray(int size) {
+ return new Owner[size];
+ }
+ };
+
+ public String getLogin() {
+ return login;
+ }
+
+ public int getId() {
+ return id;
+ }
+
+ public String getAvatarUrl() {
+ return avatarUrl;
+ }
+
+ public String getGravatarId() {
+ return gravatarId;
+ }
+
+ public String getUrl() {
+ return url;
+ }
+
+ public String getHtmlUrl() {
+ return htmlUrl;
+ }
+
+ public String getFollowersUrl() {
+ return followersUrl;
+ }
+
+ public String getFollowingUrl() {
+ return followingUrl;
+ }
+
+ public String getGistsUrl() {
+ return gistsUrl;
+ }
+
+ public String getStarredUrl() {
+ return starredUrl;
+ }
+
+ public String getSubscriptionsUrl() {
+ return subscriptionsUrl;
+ }
+
+ public String getOrganizationsUrl() {
+ return organizationsUrl;
+ }
+
+ public String getReposUrl() {
+ return reposUrl;
+ }
+
+ public String getEventsUrl() {
+ return eventsUrl;
+ }
+
+ public String getReceivedEventsUrl() {
+ return receivedEventsUrl;
+ }
+
+ public String getType() {
+ return type;
+ }
+
+ public boolean isSiteAdmin() {
+ return siteAdmin;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel parcel, int i) {
+ parcel.writeString(login);
+ parcel.writeInt(id);
+ parcel.writeString(avatarUrl);
+ parcel.writeString(gravatarId);
+ parcel.writeString(url);
+ parcel.writeString(htmlUrl);
+ parcel.writeString(followersUrl);
+ parcel.writeString(followingUrl);
+ parcel.writeString(gistsUrl);
+ parcel.writeString(starredUrl);
+ parcel.writeString(subscriptionsUrl);
+ parcel.writeString(organizationsUrl);
+ parcel.writeString(reposUrl);
+ parcel.writeString(eventsUrl);
+ parcel.writeString(receivedEventsUrl);
+ parcel.writeString(type);
+ parcel.writeByte((byte) (siteAdmin ? 1 : 0));
+ }
+}
diff --git a/desafio-mobile-android/app/src/main/java/com/app/desafio_mobile_android/service/model/pull/Pull.java b/desafio-mobile-android/app/src/main/java/com/app/desafio_mobile_android/service/model/pull/Pull.java
new file mode 100644
index 00000000..89ed00fc
--- /dev/null
+++ b/desafio-mobile-android/app/src/main/java/com/app/desafio_mobile_android/service/model/pull/Pull.java
@@ -0,0 +1,280 @@
+package com.app.desafio_mobile_android.service.model.pull;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.app.desafio_mobile_android.intrastructure.Constants;
+import com.app.desafio_mobile_android.service.model.Owner;
+import com.google.gson.annotations.SerializedName;
+
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.List;
+
+public class Pull implements Parcelable {
+ private final static String STATE_CLOSED = "closed";
+
+ @SerializedName("url")
+ private String url;
+ @SerializedName("id")
+ private Integer id;
+ @SerializedName("html_url")
+ private String htmlUrl;
+ @SerializedName("diff_url")
+ private String diffUrl;
+ @SerializedName("patch_url")
+ private String patchUrl;
+ @SerializedName("issue_url")
+ private String issueUrl;
+ @SerializedName("number")
+ private Integer number;
+ @SerializedName("state")
+ private String state;
+ @SerializedName("locked")
+ private Boolean locked;
+ @SerializedName("title")
+ private String title;
+ @SerializedName("user")
+ private Owner user;
+ @SerializedName("body")
+ private String body;
+ @SerializedName("created_at")
+ private String createdAt;
+ @SerializedName("updated_at")
+ private String updatedAt;
+ @SerializedName("closed_at")
+ private Object closedAt;
+ @SerializedName("merged_at")
+ private Object mergedAt;
+ @SerializedName("merge_commit_sha")
+ private String mergeCommitSha;
+ @SerializedName("assignee")
+ private Object assignee;
+ @SerializedName("assignees")
+ private List