diff --git a/DesafioAppProva/.gitignore b/DesafioAppProva/.gitignore new file mode 100644 index 00000000..0a002ee4 --- /dev/null +++ b/DesafioAppProva/.gitignore @@ -0,0 +1,11 @@ +*.iml +.gradle +/local.properties\ +*/.idea +gradlew.bat +.DS_Store +/build +/captures +.externalNativeBuild +libs/ +src/main/res/drawable/ diff --git a/DesafioAppProva/app/.gitignore b/DesafioAppProva/app/.gitignore new file mode 100644 index 00000000..796b96d1 --- /dev/null +++ b/DesafioAppProva/app/.gitignore @@ -0,0 +1 @@ +/build diff --git a/DesafioAppProva/app/build.gradle b/DesafioAppProva/app/build.gradle new file mode 100644 index 00000000..0d00272c --- /dev/null +++ b/DesafioAppProva/app/build.gradle @@ -0,0 +1,67 @@ +apply plugin: 'com.android.application' + +android { + compileSdkVersion 26 + buildToolsVersion "26.0.2" + defaultConfig { + applicationId "com.felipemiranda.desafioappprova" + minSdkVersion 19 + targetSdkVersion 26 + versionCode 1 + versionName "1.0" + testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" + vectorDrawables.useSupportLibrary true + } + buildTypes { + release { + minifyEnabled true + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } +} + +def SupportLibrary = '26.1.0' +def ButterKnife = '8.8.1' +def JUNIT = '4.12' +def EspressoCore = '3.0.1' +def Glide = '4.2.0' +def Gson = '2.8.2' +def GsonConverter = '2.3.0' +def Retrofit = '2.3.0' +def MultiDex = '1.0.2' +def Okhttp = '3.8.1' +def OkhttpInterceptor = '3.8.1' +def Fresco = '1.3.0' + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar']) + implementation "com.android.support:appcompat-v7:${SupportLibrary}" + implementation "com.android.support:support-v4:${SupportLibrary}" + implementation "com.android.support:design:${SupportLibrary}" + implementation "com.android.support:support-v4:${SupportLibrary}" + implementation "com.android.support:multidex:${MultiDex}" + + compile "com.jakewharton:butterknife:${ButterKnife}" + annotationProcessor "com.jakewharton:butterknife-compiler:${ButterKnife}" + + compile "com.google.code.gson:gson:${Gson}" + compile "com.squareup.retrofit2:converter-gson:${GsonConverter}" + + compile "com.github.bumptech.glide:glide:${Glide}" + + compile "com.squareup.retrofit2:retrofit:${Retrofit}" + + compile "com.squareup.okhttp3:okhttp:${Okhttp}" + compile "com.squareup.okhttp3:logging-interceptor:${OkhttpInterceptor}" + + compile "com.facebook.fresco:fresco:${Fresco}" + + testImplementation "junit:junit:${JUNIT}" + androidTestImplementation("com.android.support.test.espresso:espresso-core:${EspressoCore}", { + exclude group: 'com.android.support', module: 'support-annotations' + }) +} \ No newline at end of file diff --git a/DesafioAppProva/app/proguard-rules.pro b/DesafioAppProva/app/proguard-rules.pro new file mode 100644 index 00000000..c06e4243 --- /dev/null +++ b/DesafioAppProva/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/felipemiranda/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/DesafioAppProva/app/src/androidTest/java/com/felipemiranda/desafioappprova/ExampleInstrumentedTest.java b/DesafioAppProva/app/src/androidTest/java/com/felipemiranda/desafioappprova/ExampleInstrumentedTest.java new file mode 100644 index 00000000..7ac77591 --- /dev/null +++ b/DesafioAppProva/app/src/androidTest/java/com/felipemiranda/desafioappprova/ExampleInstrumentedTest.java @@ -0,0 +1,26 @@ +package com.felipemiranda.desafioappprova; + +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.felipemiranda.desafioappprova", appContext.getPackageName()); + } +} diff --git a/DesafioAppProva/app/src/main/AndroidManifest.xml b/DesafioAppProva/app/src/main/AndroidManifest.xml new file mode 100644 index 00000000..e8d35f69 --- /dev/null +++ b/DesafioAppProva/app/src/main/AndroidManifest.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/DesafioAppProva/app/src/main/java/com/felipemiranda/desafioappprova/AppProvaApplication.java b/DesafioAppProva/app/src/main/java/com/felipemiranda/desafioappprova/AppProvaApplication.java new file mode 100644 index 00000000..8083aae1 --- /dev/null +++ b/DesafioAppProva/app/src/main/java/com/felipemiranda/desafioappprova/AppProvaApplication.java @@ -0,0 +1,41 @@ +package com.felipemiranda.desafioappprova; + +import android.app.Application; +import android.content.Context; +import android.net.ConnectivityManager; +import android.net.NetworkInfo; +import android.support.multidex.MultiDex; + +import com.facebook.drawee.backends.pipeline.Fresco; +import com.felipemiranda.desafioappprova.network.InternetConnection; + +/** + * Created by felipemiranda on 26/10/17. + */ + +public class AppProvaApplication extends Application implements InternetConnection { + + private static AppProvaApplication mInstance; + + @Override + public void onCreate() { + super.onCreate(); + + Fresco.initialize(this); + + MultiDex.install(this); + + mInstance = this; + } + + public static AppProvaApplication getInstance() { + return mInstance; + } + + @Override + public boolean isOnline() { + ConnectivityManager cm = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE); + NetworkInfo netInfo = cm != null ? cm.getActiveNetworkInfo() : null; + return netInfo != null && netInfo.isConnectedOrConnecting(); + } +} diff --git a/DesafioAppProva/app/src/main/java/com/felipemiranda/desafioappprova/SplashActivity.java b/DesafioAppProva/app/src/main/java/com/felipemiranda/desafioappprova/SplashActivity.java new file mode 100644 index 00000000..dbc4f723 --- /dev/null +++ b/DesafioAppProva/app/src/main/java/com/felipemiranda/desafioappprova/SplashActivity.java @@ -0,0 +1,37 @@ +package com.felipemiranda.desafioappprova; + +import android.content.Intent; +import android.os.Bundle; +import android.os.Handler; +import android.support.v7.app.AppCompatActivity; +import android.widget.ImageView; + +import com.felipemiranda.desafioappprova.ui.home.HomeActivity; +import com.felipemiranda.desafioappprova.utils.Utils; + +import butterknife.BindView; + +/** + * Created by felipemiranda on 27/10/17. + */ + +public class SplashActivity extends AppCompatActivity { + + private static final long DELAY = 1500; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_splash); + + new Handler().postDelayed(this::openHomeActivity, DELAY); + } + + private void openHomeActivity() { + Intent intent = new Intent(this, HomeActivity.class); + intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION); + startActivity(intent); + overridePendingTransition(0, 0); + finish(); + } +} diff --git a/DesafioAppProva/app/src/main/java/com/felipemiranda/desafioappprova/model/Item.java b/DesafioAppProva/app/src/main/java/com/felipemiranda/desafioappprova/model/Item.java new file mode 100644 index 00000000..ec811455 --- /dev/null +++ b/DesafioAppProva/app/src/main/java/com/felipemiranda/desafioappprova/model/Item.java @@ -0,0 +1,81 @@ +package com.felipemiranda.desafioappprova.model; + + +import com.google.gson.annotations.SerializedName; + +import java.io.Serializable; + +/** + * Created by felipemiranda on 26/10/17. + */ + +public class Item implements Serializable { + + private String name; + @SerializedName("full_name") + private String full_name; + private Owner owner; + private String description; + @SerializedName("stargazers_count") + private Integer stargazers_count; + @SerializedName("forks_count") + private Integer forks_count; + @SerializedName("pulls_url") + private String pulls_url; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getFull_name() { + return full_name; + } + + public void setFull_name(String full_name) { + this.full_name = full_name; + } + + public Owner getOwner() { + return owner; + } + + public void setOwner(Owner owner) { + this.owner = owner; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public Integer getStargazers_count() { + return stargazers_count; + } + + public void setStargazers_count(Integer stargazers_count) { + this.stargazers_count = stargazers_count; + } + + public Integer getForks_count() { + return forks_count; + } + + public void setForks_count(Integer forks_count) { + this.forks_count = forks_count; + } + + public String getPulls_url() { + return pulls_url; + } + + public void setPulls_url(String pulls_url) { + this.pulls_url = pulls_url; + } +} \ No newline at end of file diff --git a/DesafioAppProva/app/src/main/java/com/felipemiranda/desafioappprova/model/ItemDetail.java b/DesafioAppProva/app/src/main/java/com/felipemiranda/desafioappprova/model/ItemDetail.java new file mode 100644 index 00000000..548ac5c1 --- /dev/null +++ b/DesafioAppProva/app/src/main/java/com/felipemiranda/desafioappprova/model/ItemDetail.java @@ -0,0 +1,60 @@ +package com.felipemiranda.desafioappprova.model; + +import com.google.gson.annotations.SerializedName; + +import java.io.Serializable; + +/** + * Created by felipemiranda on 27/10/17. + */ + +public class ItemDetail implements Serializable { + + private String title; + private Owner user; + @SerializedName("created_at") + private String created_at; + private String body; + @SerializedName("html_url") + private String html_url; + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public Owner getUser() { + return user; + } + + public void setUser(Owner user) { + this.user = user; + } + + public String getCreated_at() { + return created_at; + } + + public void setCreated_at(String created_at) { + this.created_at = created_at; + } + + public String getBody() { + return body; + } + + public void setBody(String body) { + this.body = body; + } + + public String getHtml_url() { + return html_url; + } + + public void setHtml_url(String html_url) { + this.html_url = html_url; + } +} diff --git a/DesafioAppProva/app/src/main/java/com/felipemiranda/desafioappprova/model/Owner.java b/DesafioAppProva/app/src/main/java/com/felipemiranda/desafioappprova/model/Owner.java new file mode 100644 index 00000000..7899ac9a --- /dev/null +++ b/DesafioAppProva/app/src/main/java/com/felipemiranda/desafioappprova/model/Owner.java @@ -0,0 +1,60 @@ +package com.felipemiranda.desafioappprova.model; + +import com.google.gson.annotations.SerializedName; + +import java.io.Serializable; + +/** + * Created by felipemiranda on 26/10/17. + */ + +public class Owner implements Serializable { + + private String login; + private Integer id; + @SerializedName("avatar_url") + private String avatar_url; + @SerializedName("gravatar_id") + private String gravatar_id; + private String url; + + public String getLogin() { + return login; + } + + public void setLogin(String login) { + this.login = login; + } + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getAvatar_url() { + return avatar_url; + } + + public void setAvatar_url(String avatar_url) { + this.avatar_url = avatar_url; + } + + public String getGravatar_id() { + return gravatar_id; + } + + public void setGravatar_id(String gravatar_id) { + this.gravatar_id = gravatar_id; + } + + public String getUrl() { + return url; + } + + public void setUrl(String url) { + this.url = url; + } +} \ No newline at end of file diff --git a/DesafioAppProva/app/src/main/java/com/felipemiranda/desafioappprova/model/response/GenericErrorResponse.java b/DesafioAppProva/app/src/main/java/com/felipemiranda/desafioappprova/model/response/GenericErrorResponse.java new file mode 100644 index 00000000..40871ea4 --- /dev/null +++ b/DesafioAppProva/app/src/main/java/com/felipemiranda/desafioappprova/model/response/GenericErrorResponse.java @@ -0,0 +1,75 @@ +package com.felipemiranda.desafioappprova.model.response; + +import android.os.Parcel; +import android.os.Parcelable; + +import com.google.gson.Gson; + +import okhttp3.ResponseBody; + +/** + * Created by felipemiranda on 26/10/17. + */ + +public class GenericErrorResponse implements Parcelable { + + private String message; + + public GenericErrorResponse() {} + + protected GenericErrorResponse(Parcel in) { + message = in.readString(); + } + + public static final Creator CREATOR = new Creator() { + @Override + public GenericErrorResponse createFromParcel(Parcel in) { + return new GenericErrorResponse(in); + } + + @Override + public GenericErrorResponse[] newArray(int size) { + return new GenericErrorResponse[size]; + } + }; + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public static GenericErrorResponse getResponseError(final ResponseBody responseBody) { + GenericErrorResponse error; + try { + error = new Gson().fromJson(responseBody.string(), GenericErrorResponse.class); + } catch (Exception e) { + error = getExceptionError(); + } + return error; + } + + public static GenericErrorResponse getExceptionError() { + final GenericErrorResponse error = new GenericErrorResponse(); + error.setMessage("Desculpe-nos, não foi possível processar a sua solicitação no momento."); + return error; + } + + public static GenericErrorResponse getConnectionError() { + final GenericErrorResponse error = new GenericErrorResponse(); + error.setMessage("Você não está conectado, por favor tente novamente mais tarde."); + return error; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(message); + } +} diff --git a/DesafioAppProva/app/src/main/java/com/felipemiranda/desafioappprova/model/response/SearchResponse.java b/DesafioAppProva/app/src/main/java/com/felipemiranda/desafioappprova/model/response/SearchResponse.java new file mode 100644 index 00000000..e1532a68 --- /dev/null +++ b/DesafioAppProva/app/src/main/java/com/felipemiranda/desafioappprova/model/response/SearchResponse.java @@ -0,0 +1,41 @@ +package com.felipemiranda.desafioappprova.model.response; + +import com.felipemiranda.desafioappprova.model.Item; + +import java.io.Serializable; +import java.util.ArrayList; + +/** + * Created by felipemiranda on 26/10/17. + */ + +public class SearchResponse implements Serializable { + + private Integer totalCount; + private Boolean incompleteResults; + private ArrayList items; + + public Integer getTotalCount() { + return totalCount; + } + + public void setTotalCount(Integer totalCount) { + this.totalCount = totalCount; + } + + public Boolean getIncompleteResults() { + return incompleteResults; + } + + public void setIncompleteResults(Boolean incompleteResults) { + this.incompleteResults = incompleteResults; + } + + public ArrayList getItems() { + return items; + } + + public void setItems(ArrayList items) { + this.items = items; + } +} diff --git a/DesafioAppProva/app/src/main/java/com/felipemiranda/desafioappprova/network/ApiCreator.java b/DesafioAppProva/app/src/main/java/com/felipemiranda/desafioappprova/network/ApiCreator.java new file mode 100644 index 00000000..a5a1b1d6 --- /dev/null +++ b/DesafioAppProva/app/src/main/java/com/felipemiranda/desafioappprova/network/ApiCreator.java @@ -0,0 +1,26 @@ +package com.felipemiranda.desafioappprova.network; + +import okhttp3.OkHttpClient; +import retrofit2.Retrofit; +import retrofit2.converter.gson.GsonConverterFactory; + +/** + * Created by felipemiranda on 26/10/17. + */ + +public class ApiCreator { + + private static final String API_BASE_URL = "https://api.github.com/"; + + private static OkHttpClient httpClient = NetworkSetup.getClient(); + + private static Retrofit.Builder builder = new Retrofit.Builder() + .baseUrl(API_BASE_URL) + .addConverterFactory(GsonConverterFactory.create()); + + + public static S createService(Class serviceClass) { + Retrofit retrofit = builder.client(httpClient).build(); + return retrofit.create(serviceClass); + } +} diff --git a/DesafioAppProva/app/src/main/java/com/felipemiranda/desafioappprova/network/InternetConnection.java b/DesafioAppProva/app/src/main/java/com/felipemiranda/desafioappprova/network/InternetConnection.java new file mode 100644 index 00000000..5329897a --- /dev/null +++ b/DesafioAppProva/app/src/main/java/com/felipemiranda/desafioappprova/network/InternetConnection.java @@ -0,0 +1,9 @@ +package com.felipemiranda.desafioappprova.network; + +/** + * Created by felipemiranda on 26/10/17. + */ + +public interface InternetConnection { + boolean isOnline(); +} diff --git a/DesafioAppProva/app/src/main/java/com/felipemiranda/desafioappprova/network/NetworkSetup.java b/DesafioAppProva/app/src/main/java/com/felipemiranda/desafioappprova/network/NetworkSetup.java new file mode 100644 index 00000000..cb5a2fde --- /dev/null +++ b/DesafioAppProva/app/src/main/java/com/felipemiranda/desafioappprova/network/NetworkSetup.java @@ -0,0 +1,37 @@ +package com.felipemiranda.desafioappprova.network; + +import java.util.concurrent.TimeUnit; + +import okhttp3.Dispatcher; +import okhttp3.OkHttpClient; +import okhttp3.logging.HttpLoggingInterceptor; + +/** + * Created by felipemiranda on 26/10/17. + */ + +public class NetworkSetup { + + public static OkHttpClient getClient() { + return new OkHttpClient() + .newBuilder() + .addInterceptor(getLoggingCapableHttpClient()) + .dispatcher(buildDispatcher()) + .connectTimeout(60, TimeUnit.SECONDS) + .readTimeout(60, TimeUnit.SECONDS) + .build(); + } + + private static HttpLoggingInterceptor getLoggingCapableHttpClient() { + HttpLoggingInterceptor mLogging = new HttpLoggingInterceptor(); + mLogging.setLevel(HttpLoggingInterceptor.Level.BODY); + + return mLogging; + } + + private static Dispatcher buildDispatcher() { + Dispatcher dispatcher = new Dispatcher(); + dispatcher.setMaxRequestsPerHost(20); + return dispatcher; + } +} diff --git a/DesafioAppProva/app/src/main/java/com/felipemiranda/desafioappprova/network/RestCallback.java b/DesafioAppProva/app/src/main/java/com/felipemiranda/desafioappprova/network/RestCallback.java new file mode 100644 index 00000000..8ef4a470 --- /dev/null +++ b/DesafioAppProva/app/src/main/java/com/felipemiranda/desafioappprova/network/RestCallback.java @@ -0,0 +1,40 @@ +package com.felipemiranda.desafioappprova.network; + +import com.felipemiranda.desafioappprova.AppProvaApplication; +import com.felipemiranda.desafioappprova.model.response.GenericErrorResponse; + +import retrofit2.Call; +import retrofit2.Callback; +import retrofit2.Response; + +/** + * Created by felipemiranda on 26/10/17. + */ + +public abstract class RestCallback implements Callback { + + public abstract void onSuccess(T response); + + public abstract void onError(GenericErrorResponse error); + + @Override + public void onResponse(Call call, Response response) { + if (response.isSuccessful()) + onSuccess(response.body()); + else { + GenericErrorResponse error = GenericErrorResponse.getResponseError(response.errorBody()); + if (error == null || error.getMessage() == null) + error = GenericErrorResponse.getExceptionError(); + + onError(error); + } + } + + @Override + public void onFailure(Call call, Throwable t) { + if (!AppProvaApplication.getInstance().isOnline()) + onError(GenericErrorResponse.getConnectionError()); + else + onError(GenericErrorResponse.getExceptionError()); + } +} diff --git a/DesafioAppProva/app/src/main/java/com/felipemiranda/desafioappprova/network/api/SearchApi.java b/DesafioAppProva/app/src/main/java/com/felipemiranda/desafioappprova/network/api/SearchApi.java new file mode 100644 index 00000000..6e41915e --- /dev/null +++ b/DesafioAppProva/app/src/main/java/com/felipemiranda/desafioappprova/network/api/SearchApi.java @@ -0,0 +1,23 @@ +package com.felipemiranda.desafioappprova.network.api; + +import com.felipemiranda.desafioappprova.model.ItemDetail; +import com.felipemiranda.desafioappprova.model.response.SearchResponse; + +import java.util.ArrayList; + +import retrofit2.Call; +import retrofit2.http.GET; +import retrofit2.http.Url; + +/** + * Created by felipemiranda on 26/10/17. + */ + +public interface SearchApi { + + @GET("search/repositories?q=language:Java&sort=stars&page=1") + Call requestSearch(); + + @GET + Call> requestItemDetail(@Url String url); +} diff --git a/DesafioAppProva/app/src/main/java/com/felipemiranda/desafioappprova/ui/home/HomeActivity.java b/DesafioAppProva/app/src/main/java/com/felipemiranda/desafioappprova/ui/home/HomeActivity.java new file mode 100644 index 00000000..3b609f43 --- /dev/null +++ b/DesafioAppProva/app/src/main/java/com/felipemiranda/desafioappprova/ui/home/HomeActivity.java @@ -0,0 +1,60 @@ +package com.felipemiranda.desafioappprova.ui.home; + +import android.support.annotation.AnimRes; +import android.support.v4.app.Fragment; +import android.support.v4.app.FragmentTransaction; +import android.support.v7.app.AppCompatActivity; +import android.os.Bundle; +import android.view.View; +import android.widget.ProgressBar; + +import com.felipemiranda.desafioappprova.R; + +import butterknife.BindView; +import butterknife.ButterKnife; + +public class HomeActivity extends AppCompatActivity { + + @BindView(R.id.progress) + ProgressBar progressBar; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_home); + + setTitle(getString(R.string.github_java)); + + ButterKnife.bind(this); + + getSupportFragmentManager() + .beginTransaction() + .add(R.id.content, HomeFragment.newInstance(), HomeFragment.TAG) + .commit(); + } + + public void showLoading(){ + progressBar.setVisibility(View.VISIBLE); + } + + public void hideLoading(){ + progressBar.setVisibility(View.GONE); + } + + public void addFragmentDefaultAnim(Fragment fragment, String tag, String tagBackStack) { + addFragment(fragment, tag, R.anim.slide_from_right, R.anim.slide_to_left, R.anim.slide_from_left, R.anim.slide_to_right, tagBackStack); + } + + private void addFragment(Fragment fragment, String tag, + @AnimRes int enter, @AnimRes int exit, + @AnimRes int popEnter, @AnimRes int popExit, + String tagBackStack) { + FragmentTransaction transaction = getSupportFragmentManager() + .beginTransaction(); + + transaction.setCustomAnimations(enter, exit, popEnter, popExit); + transaction.replace(R.id.content, fragment, tag); + transaction.addToBackStack(tagBackStack); + transaction.commit(); + } +} diff --git a/DesafioAppProva/app/src/main/java/com/felipemiranda/desafioappprova/ui/home/HomeFragment.java b/DesafioAppProva/app/src/main/java/com/felipemiranda/desafioappprova/ui/home/HomeFragment.java new file mode 100644 index 00000000..c7078558 --- /dev/null +++ b/DesafioAppProva/app/src/main/java/com/felipemiranda/desafioappprova/ui/home/HomeFragment.java @@ -0,0 +1,80 @@ +package com.felipemiranda.desafioappprova.ui.home; + +import android.os.Bundle; +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.felipemiranda.desafioappprova.R; +import com.felipemiranda.desafioappprova.model.response.SearchResponse; +import com.felipemiranda.desafioappprova.ui.home.adapter.ListItemsAdapter; +import com.felipemiranda.desafioappprova.ui.main.BaseFragment; +import com.felipemiranda.desafioappprova.ui.main.BaseLoadingPresenter; +import com.felipemiranda.desafioappprova.ui.search.ItemDetailFragment; +import com.felipemiranda.desafioappprova.utils.Utils; + +import butterknife.BindView; + +/** + * Created by felipemiranda on 26/10/17. + */ + +public class HomeFragment extends BaseFragment implements HomeView, ListItemsAdapter.OnClickItem { + + public static final String TAG = HomeFragment.class.getSimpleName(); + + @BindView(R.id.rv_list_items) + RecyclerView rvListItems; + + private HomePresenter mHomePresenter; + + public static HomeFragment newInstance() { + Bundle args = new Bundle(); + HomeFragment fragment = new HomeFragment(); + fragment.setArguments(args); + return fragment; + } + + @Override + public View onCreateViewBinder(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + return inflater.inflate(R.layout.fragment_home, container, false); + } + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + mHomePresenter = new HomePresenter(); + } + + @Override + public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + + getActivity().setTitle(getString(R.string.github_java)); + mHomePresenter.requestSearch(); + } + + @Override + public BaseLoadingPresenter getPresenter() { + return mHomePresenter; + } + + @Override + public void successSearch(SearchResponse response) { + if (response != null && response.getItems() != null && !response.getItems().isEmpty()) { + ListItemsAdapter listItemsAdapter = new ListItemsAdapter(response.getItems(), this); + rvListItems.setLayoutManager(new LinearLayoutManager(getActivity(), LinearLayoutManager.VERTICAL, false)); + rvListItems.setAdapter(listItemsAdapter); + Utils.recyclerViewAnimationEntrance(rvListItems); + } + } + + @Override + public void onClickItem(String url, String name) { + setFragmentContent(ItemDetailFragment.newInstance(url, name), TAG); + } +} diff --git a/DesafioAppProva/app/src/main/java/com/felipemiranda/desafioappprova/ui/home/HomePresenter.java b/DesafioAppProva/app/src/main/java/com/felipemiranda/desafioappprova/ui/home/HomePresenter.java new file mode 100644 index 00000000..a1ddcae1 --- /dev/null +++ b/DesafioAppProva/app/src/main/java/com/felipemiranda/desafioappprova/ui/home/HomePresenter.java @@ -0,0 +1,38 @@ +package com.felipemiranda.desafioappprova.ui.home; + +import com.felipemiranda.desafioappprova.model.response.GenericErrorResponse; +import com.felipemiranda.desafioappprova.model.response.SearchResponse; +import com.felipemiranda.desafioappprova.network.ApiCreator; +import com.felipemiranda.desafioappprova.network.RestCallback; +import com.felipemiranda.desafioappprova.network.api.SearchApi; +import com.felipemiranda.desafioappprova.ui.main.BaseLoadingPresenter; + +/** + * Created by felipemiranda on 26/10/17. + */ + +public class HomePresenter extends BaseLoadingPresenter { + + @Override + public void bindView(HomeView view) { + this.mView = view; + } + + public void requestSearch() { + addRequest(ApiCreator + .createService(SearchApi.class) + .requestSearch(), + + new RestCallback() { + @Override + public void onSuccess(SearchResponse response) { + mView.successSearch(response); + } + + @Override + public void onError(GenericErrorResponse error) { + mView.notifyError(error); + } + }); + } +} diff --git a/DesafioAppProva/app/src/main/java/com/felipemiranda/desafioappprova/ui/home/HomeView.java b/DesafioAppProva/app/src/main/java/com/felipemiranda/desafioappprova/ui/home/HomeView.java new file mode 100644 index 00000000..b5468a75 --- /dev/null +++ b/DesafioAppProva/app/src/main/java/com/felipemiranda/desafioappprova/ui/home/HomeView.java @@ -0,0 +1,13 @@ +package com.felipemiranda.desafioappprova.ui.home; + +import com.felipemiranda.desafioappprova.model.response.SearchResponse; +import com.felipemiranda.desafioappprova.ui.main.BaseLoadingView; + +/** + * Created by felipemiranda on 26/10/17. + */ + +public interface HomeView extends BaseLoadingView { + + void successSearch(SearchResponse response); +} diff --git a/DesafioAppProva/app/src/main/java/com/felipemiranda/desafioappprova/ui/home/adapter/ListItemsAdapter.java b/DesafioAppProva/app/src/main/java/com/felipemiranda/desafioappprova/ui/home/adapter/ListItemsAdapter.java new file mode 100644 index 00000000..90fde9ba --- /dev/null +++ b/DesafioAppProva/app/src/main/java/com/felipemiranda/desafioappprova/ui/home/adapter/ListItemsAdapter.java @@ -0,0 +1,115 @@ +package com.felipemiranda.desafioappprova.ui.home.adapter; + +import android.net.Uri; +import android.support.v7.widget.RecyclerView; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.LinearLayout; +import android.widget.TextView; + +import com.facebook.drawee.backends.pipeline.Fresco; +import com.facebook.drawee.view.SimpleDraweeView; +import com.facebook.imagepipeline.common.ResizeOptions; +import com.facebook.imagepipeline.request.ImageRequest; +import com.facebook.imagepipeline.request.ImageRequestBuilder; +import com.felipemiranda.desafioappprova.R; +import com.felipemiranda.desafioappprova.model.Item; + +import java.util.ArrayList; + +import butterknife.BindView; +import butterknife.ButterKnife; + +/** + * Created by felipemiranda on 26/10/17. + */ + +public class ListItemsAdapter extends RecyclerView.Adapter { + + private ArrayList mItems; + private OnClickItem mOnClickItem; + + public ListItemsAdapter(ArrayList mSearchResponses, OnClickItem onClickItem) { + this.mItems = mSearchResponses; + this.mOnClickItem = onClickItem; + } + + public interface OnClickItem { + void onClickItem(String url, String name); + } + + @Override + public AppViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + View itemView = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_list, parent, false); + return new ItemViewHolder(itemView); + } + + @Override + public void onBindViewHolder(AppViewHolder holder, int position) { + final Item item = mItems.get(holder.getAdapterPosition()); + ItemViewHolder holderItem = (ItemViewHolder) holder; + + holderItem.item.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + mOnClickItem.onClickItem(item.getPulls_url(), item.getName()); + } + }); + + holderItem.tvName.setText(item.getName() != null ? item.getName() : ""); + holderItem.tvDescription.setText(item.getDescription() != null ? item.getDescription() : ""); + holderItem.tvFork.setText(item.getForks_count() != null ? String.valueOf(item.getForks_count()) : "0"); + holderItem.tvStar.setText(item.getStargazers_count() != null ? String.valueOf(item.getStargazers_count()) : "0"); + holderItem.tvUsername.setText(item.getOwner().getLogin() != null ? item.getOwner().getLogin() : ""); + holderItem.tvUserFullName.setText(item.getFull_name() != null ? item.getFull_name() : ""); + + if (mItems.get(position).getOwner().getAvatar_url() != null) { + final int patternSizeImg = 420; + ImageRequest request = ImageRequestBuilder.newBuilderWithSource(Uri.parse(mItems.get(position).getOwner().getAvatar_url())) + .setResizeOptions(new ResizeOptions(patternSizeImg, patternSizeImg)) + .build(); + holderItem.ivUser.setController( + Fresco.newDraweeControllerBuilder() + .setOldController(holderItem.ivUser.getController()) + .setImageRequest(request) + .build()); + } + } + + @Override + public int getItemCount() { + return mItems.size(); + } + + public class AppViewHolder extends RecyclerView.ViewHolder { + public AppViewHolder(View itemView) { + super(itemView); + ButterKnife.bind(this, itemView); + } + } + + public class ItemViewHolder extends AppViewHolder { + + @BindView(R.id.tv_name) + TextView tvName; + @BindView(R.id.tv_description) + TextView tvDescription; + @BindView(R.id.tv_fork) + TextView tvFork; + @BindView(R.id.tv_star) + TextView tvStar; + @BindView(R.id.iv_user) + SimpleDraweeView ivUser; + @BindView(R.id.tv_username) + TextView tvUsername; + @BindView(R.id.tv_user_fullname) + TextView tvUserFullName; + @BindView(R.id.item) + LinearLayout item; + + public ItemViewHolder(View itemView) { + super(itemView); + } + } +} diff --git a/DesafioAppProva/app/src/main/java/com/felipemiranda/desafioappprova/ui/main/BaseFragment.java b/DesafioAppProva/app/src/main/java/com/felipemiranda/desafioappprova/ui/main/BaseFragment.java new file mode 100644 index 00000000..7951c1b4 --- /dev/null +++ b/DesafioAppProva/app/src/main/java/com/felipemiranda/desafioappprova/ui/main/BaseFragment.java @@ -0,0 +1,72 @@ +package com.felipemiranda.desafioappprova.ui.main; + +import android.os.Bundle; +import android.support.annotation.Nullable; +import android.support.v4.app.Fragment; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Toast; + +import com.felipemiranda.desafioappprova.R; +import com.felipemiranda.desafioappprova.model.response.GenericErrorResponse; +import com.felipemiranda.desafioappprova.ui.home.HomeActivity; + +import butterknife.ButterKnife; +import butterknife.Unbinder; + +/** + * Created by felipemiranda on 26/10/17. + */ + +public abstract class BaseFragment extends Fragment implements BaseLoadingView { + + public abstract BaseLoadingPresenter getPresenter(); + + private Unbinder mUnbinder; + + @Nullable + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + View view = onCreateViewBinder(inflater, container, savedInstanceState); + mUnbinder = ButterKnife.bind(this, view); + getPresenter().bindView(this); + return view; + } + + @Override + public void onDestroyView() { + super.onDestroyView(); + if (mUnbinder != null) + mUnbinder.unbind(); + } + + public void setFragmentContent(Fragment fragment, String tagBackStack) { + + ((HomeActivity) getActivity()).addFragmentDefaultAnim(fragment, fragment.getClass().getName(), tagBackStack); + } + + @Override + public void showLoading() { + ((HomeActivity) getActivity()).showLoading(); + } + + @Override + public void hideLoading() { + ((HomeActivity) getActivity()).hideLoading(); + } + + @Override + public void notifyError(GenericErrorResponse error) { + if (error != null) { + Toast.makeText(getActivity(), error.getMessage(), Toast.LENGTH_LONG).show(); + } else { + Toast.makeText(getActivity(), getString(R.string.sorry_unable_to_process), Toast.LENGTH_LONG).show(); + } + } + + @Override + public void retryLastRequest() { + getPresenter().retryLastRequest(); + } +} diff --git a/DesafioAppProva/app/src/main/java/com/felipemiranda/desafioappprova/ui/main/BaseLoadingPresenter.java b/DesafioAppProva/app/src/main/java/com/felipemiranda/desafioappprova/ui/main/BaseLoadingPresenter.java new file mode 100644 index 00000000..71af0d51 --- /dev/null +++ b/DesafioAppProva/app/src/main/java/com/felipemiranda/desafioappprova/ui/main/BaseLoadingPresenter.java @@ -0,0 +1,73 @@ +package com.felipemiranda.desafioappprova.ui.main; + +import com.felipemiranda.desafioappprova.model.response.GenericErrorResponse; +import com.felipemiranda.desafioappprova.network.RestCallback; + +import java.util.ArrayList; +import java.util.List; + +import retrofit2.Call; + +/** + * Created by felipemiranda on 26/10/17. + */ + +public abstract class BaseLoadingPresenter { + + public T mView; + private List mCalls; + private Request mLastRequest; + + public abstract void bindView(T view); + + public void retryLastRequest() { + if (mLastRequest != null) + doRequest(mLastRequest.mCall.clone(), mLastRequest.mRestCallback); + } + + public void addRequest(final Call call, final RestCallback restCallback) { + doRequest(call, restCallback); + } + + private void doRequest(final Call call, final RestCallback restCallback) { + mLastRequest = new Request(call, restCallback); + + if (mView == null) return; + + mView.showLoading(); + + if (mCalls == null) + mCalls = new ArrayList<>(); + + mCalls.add(call); + call.enqueue(new RestCallback() { + @Override + public void onSuccess(T response) { + mCalls.remove(call); + if (mView != null) { + mView.hideLoading(); + restCallback.onSuccess(response); + } + } + + @Override + public void onError(GenericErrorResponse error) { + mCalls.remove(call); + if (mView != null) { + mView.hideLoading(); + restCallback.onError(error); + } + } + }); + } + + class Request { + Call mCall; + RestCallback mRestCallback; + + private Request(Call mCall, RestCallback mRestCallback) { + this.mCall = mCall; + this.mRestCallback = mRestCallback; + } + } +} diff --git a/DesafioAppProva/app/src/main/java/com/felipemiranda/desafioappprova/ui/main/BaseLoadingView.java b/DesafioAppProva/app/src/main/java/com/felipemiranda/desafioappprova/ui/main/BaseLoadingView.java new file mode 100644 index 00000000..7525e622 --- /dev/null +++ b/DesafioAppProva/app/src/main/java/com/felipemiranda/desafioappprova/ui/main/BaseLoadingView.java @@ -0,0 +1,18 @@ +package com.felipemiranda.desafioappprova.ui.main; + +import com.felipemiranda.desafioappprova.model.response.GenericErrorResponse; + +/** + * Created by felipemiranda on 26/10/17. + */ + +public interface BaseLoadingView extends FragmentViewBinder { + + void showLoading(); + + void hideLoading(); + + void notifyError(GenericErrorResponse error); + + void retryLastRequest(); +} diff --git a/DesafioAppProva/app/src/main/java/com/felipemiranda/desafioappprova/ui/main/FragmentViewBinder.java b/DesafioAppProva/app/src/main/java/com/felipemiranda/desafioappprova/ui/main/FragmentViewBinder.java new file mode 100644 index 00000000..45ea94a5 --- /dev/null +++ b/DesafioAppProva/app/src/main/java/com/felipemiranda/desafioappprova/ui/main/FragmentViewBinder.java @@ -0,0 +1,15 @@ +package com.felipemiranda.desafioappprova.ui.main; + +import android.os.Bundle; +import android.support.annotation.Nullable; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +/** + * Created by felipemiranda on 27/10/17. + */ + +public interface FragmentViewBinder { + View onCreateViewBinder(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState); +} \ No newline at end of file diff --git a/DesafioAppProva/app/src/main/java/com/felipemiranda/desafioappprova/ui/search/ItemDetailFragment.java b/DesafioAppProva/app/src/main/java/com/felipemiranda/desafioappprova/ui/search/ItemDetailFragment.java new file mode 100644 index 00000000..c94c4d06 --- /dev/null +++ b/DesafioAppProva/app/src/main/java/com/felipemiranda/desafioappprova/ui/search/ItemDetailFragment.java @@ -0,0 +1,98 @@ +package com.felipemiranda.desafioappprova.ui.search; + +import android.content.Intent; +import android.net.Uri; +import android.os.Bundle; +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 android.widget.Toast; + +import com.felipemiranda.desafioappprova.R; +import com.felipemiranda.desafioappprova.model.ItemDetail; +import com.felipemiranda.desafioappprova.ui.main.BaseFragment; +import com.felipemiranda.desafioappprova.ui.main.BaseLoadingPresenter; +import com.felipemiranda.desafioappprova.ui.search.adapter.ItemDetailAdapter; +import com.felipemiranda.desafioappprova.utils.Utils; + +import java.util.ArrayList; + +import butterknife.BindView; + +/** + * Created by felipemiranda on 26/10/17. + */ + +public class ItemDetailFragment extends BaseFragment implements ItemDetailView, ItemDetailAdapter.OnClickItem { + + @BindView(R.id.rv_list_items) + RecyclerView rvListItems; + + private static final String URL = "URL"; + private static final String NAME = "NAME"; + + private ItemDetailPresenter mItemDetailPresenter; + + public static ItemDetailFragment newInstance(String url, String name) { + Bundle args = new Bundle(); + args.putString(URL, url); + args.putString(NAME, name); + ItemDetailFragment fragment = new ItemDetailFragment(); + fragment.setArguments(args); + return fragment; + } + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + mItemDetailPresenter = new ItemDetailPresenter(); + } + + @Override + public View onCreateViewBinder(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + return inflater.inflate(R.layout.fragment_item_detail, container, false); + } + + @Override + public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + + String url = getArguments().getString(URL); + url = url.substring(0, url.length() - 9); + + mItemDetailPresenter.requestItemDetail(url); + } + + @Override + public BaseLoadingPresenter getPresenter() { + return mItemDetailPresenter; + } + + @Override + public void successItemDetail(ArrayList response) { + if (response != null && !response.isEmpty()) { + getActivity().setTitle(getArguments().getString(NAME)); + ItemDetailAdapter itemDetailAdapter = new ItemDetailAdapter(response, this); + rvListItems.setLayoutManager(new LinearLayoutManager(getActivity(), LinearLayoutManager.VERTICAL, false)); + rvListItems.setAdapter(itemDetailAdapter); + Utils.recyclerViewAnimationEntrance(rvListItems); + } else { + getActivity().onBackPressed(); + Toast.makeText(getActivity(), getString(R.string.not_pull), Toast.LENGTH_LONG).show(); + } + } + + @Override + public void onClickItem(String url) { + try { + Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(url)); + startActivity(browserIntent); + } catch (Exception ex) { + ex.printStackTrace(); + } + } +} diff --git a/DesafioAppProva/app/src/main/java/com/felipemiranda/desafioappprova/ui/search/ItemDetailPresenter.java b/DesafioAppProva/app/src/main/java/com/felipemiranda/desafioappprova/ui/search/ItemDetailPresenter.java new file mode 100644 index 00000000..87b14d28 --- /dev/null +++ b/DesafioAppProva/app/src/main/java/com/felipemiranda/desafioappprova/ui/search/ItemDetailPresenter.java @@ -0,0 +1,40 @@ +package com.felipemiranda.desafioappprova.ui.search; + +import com.felipemiranda.desafioappprova.model.ItemDetail; +import com.felipemiranda.desafioappprova.model.response.GenericErrorResponse; +import com.felipemiranda.desafioappprova.network.ApiCreator; +import com.felipemiranda.desafioappprova.network.RestCallback; +import com.felipemiranda.desafioappprova.network.api.SearchApi; +import com.felipemiranda.desafioappprova.ui.main.BaseLoadingPresenter; + +import java.util.ArrayList; + +/** + * Created by felipemiranda on 26/10/17. + */ + +public class ItemDetailPresenter extends BaseLoadingPresenter { + + @Override + public void bindView(ItemDetailView view) { + this.mView = view; + } + + public void requestItemDetail(String url) { + addRequest(ApiCreator + .createService(SearchApi.class) + .requestItemDetail(url), + + new RestCallback>() { + @Override + public void onSuccess(ArrayList response) { + mView.successItemDetail(response); + } + + @Override + public void onError(GenericErrorResponse error) { + mView.notifyError(error); + } + }); + } +} diff --git a/DesafioAppProva/app/src/main/java/com/felipemiranda/desafioappprova/ui/search/ItemDetailView.java b/DesafioAppProva/app/src/main/java/com/felipemiranda/desafioappprova/ui/search/ItemDetailView.java new file mode 100644 index 00000000..e205734d --- /dev/null +++ b/DesafioAppProva/app/src/main/java/com/felipemiranda/desafioappprova/ui/search/ItemDetailView.java @@ -0,0 +1,15 @@ +package com.felipemiranda.desafioappprova.ui.search; + +import com.felipemiranda.desafioappprova.model.ItemDetail; +import com.felipemiranda.desafioappprova.ui.main.BaseLoadingView; + +import java.util.ArrayList; + +/** + * Created by felipemiranda on 27/10/17. + */ + +public interface ItemDetailView extends BaseLoadingView { + + void successItemDetail(ArrayList response); +} diff --git a/DesafioAppProva/app/src/main/java/com/felipemiranda/desafioappprova/ui/search/adapter/ItemDetailAdapter.java b/DesafioAppProva/app/src/main/java/com/felipemiranda/desafioappprova/ui/search/adapter/ItemDetailAdapter.java new file mode 100644 index 00000000..88a1b9b9 --- /dev/null +++ b/DesafioAppProva/app/src/main/java/com/felipemiranda/desafioappprova/ui/search/adapter/ItemDetailAdapter.java @@ -0,0 +1,127 @@ +package com.felipemiranda.desafioappprova.ui.search.adapter; + +import android.annotation.SuppressLint; +import android.net.Uri; +import android.support.v7.widget.RecyclerView; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.LinearLayout; +import android.widget.TextView; + +import com.facebook.drawee.backends.pipeline.Fresco; +import com.facebook.drawee.view.SimpleDraweeView; +import com.facebook.imagepipeline.common.ResizeOptions; +import com.facebook.imagepipeline.request.ImageRequest; +import com.facebook.imagepipeline.request.ImageRequestBuilder; +import com.felipemiranda.desafioappprova.R; +import com.felipemiranda.desafioappprova.model.ItemDetail; + +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Date; + +import butterknife.BindView; +import butterknife.ButterKnife; + +/** + * Created by felipemiranda on 26/10/17. + */ + +public class ItemDetailAdapter extends RecyclerView.Adapter { + + private ArrayList mItems; + private OnClickItem mOnClickItem; + + public ItemDetailAdapter(ArrayList itemDetails, OnClickItem onClickItem) { + this.mItems = itemDetails; + this.mOnClickItem = onClickItem; + } + + public interface OnClickItem { + void onClickItem(String url); + } + + @Override + public AppViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + View itemView = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_detail, parent, false); + return new ItemViewHolder(itemView); + } + + @Override + public void onBindViewHolder(AppViewHolder holder, int position) { + final ItemDetail item = mItems.get(holder.getAdapterPosition()); + ItemViewHolder holderItem = (ItemViewHolder) holder; + + holderItem.item.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + mOnClickItem.onClickItem(item.getHtml_url()); + } + }); + + if(item.getCreated_at() != null) { + @SuppressLint("SimpleDateFormat") + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); + SimpleDateFormat output = new SimpleDateFormat("dd/MM/yyyy HH:mm"); + String formattedTime = ""; + + try { + Date date = sdf.parse(item.getCreated_at()); + formattedTime = output.format(date); + } catch (ParseException e) { + e.printStackTrace(); + } + + holderItem.tvDate.setText(formattedTime); + } + holderItem.tvUsername.setText(item.getUser().getLogin() != null ? item.getUser().getLogin() : ""); + holderItem.tvName.setText(item.getTitle() != null ? item.getTitle() : ""); + holderItem.tvDescription.setText(item.getBody() != null ? item.getBody() : ""); + + if (mItems.get(position).getUser().getAvatar_url() != null) { + final int patternSizeImg = 420; + ImageRequest request = ImageRequestBuilder.newBuilderWithSource(Uri.parse(mItems.get(position).getUser().getAvatar_url())) + .setResizeOptions(new ResizeOptions(patternSizeImg, patternSizeImg)) + .build(); + holderItem.ivUser.setController( + Fresco.newDraweeControllerBuilder() + .setOldController(holderItem.ivUser.getController()) + .setImageRequest(request) + .build()); + } + } + + @Override + public int getItemCount() { + return mItems.size(); + } + + public class AppViewHolder extends RecyclerView.ViewHolder { + public AppViewHolder(View itemView) { + super(itemView); + ButterKnife.bind(this, itemView); + } + } + + public class ItemViewHolder extends AppViewHolder { + + @BindView(R.id.tv_name) + TextView tvName; + @BindView(R.id.tv_date) + TextView tvDate; + @BindView(R.id.tv_description) + TextView tvDescription; + @BindView(R.id.iv_user) + SimpleDraweeView ivUser; + @BindView(R.id.tv_username) + TextView tvUsername; + @BindView(R.id.item) + LinearLayout item; + + public ItemViewHolder(View itemView) { + super(itemView); + } + } +} diff --git a/DesafioAppProva/app/src/main/java/com/felipemiranda/desafioappprova/utils/Utils.java b/DesafioAppProva/app/src/main/java/com/felipemiranda/desafioappprova/utils/Utils.java new file mode 100644 index 00000000..733bc623 --- /dev/null +++ b/DesafioAppProva/app/src/main/java/com/felipemiranda/desafioappprova/utils/Utils.java @@ -0,0 +1,42 @@ +package com.felipemiranda.desafioappprova.utils; + +import android.support.v7.widget.RecyclerView; +import android.view.animation.AccelerateInterpolator; +import android.view.animation.AlphaAnimation; +import android.view.animation.Animation; +import android.view.animation.AnimationSet; +import android.view.animation.LayoutAnimationController; +import android.view.animation.TranslateAnimation; + +/** + * Created by felipemiranda on 27/10/17. + */ + +public class Utils { + + public static void recyclerViewAnimationEntrance(RecyclerView recyclerView) { + AnimationSet set = new AnimationSet(true); + Animation animation = new AlphaAnimation(0.0f, 1.0f); + animation.setDuration(1000); + set.addAnimation(animation); + + animation = new TranslateAnimation( + Animation.RELATIVE_TO_SELF, -1.0f, Animation.RELATIVE_TO_SELF, 0.0f, + Animation.RELATIVE_TO_SELF, 0.0f, Animation.RELATIVE_TO_SELF, 0.0f + ); + animation.setDuration(500); + set.addAnimation(animation); + + LayoutAnimationController controller = new LayoutAnimationController(set, 0.1f); + controller.setInterpolator(new AccelerateInterpolator()); + recyclerView.setLayoutAnimation(controller); + } + + public static AlphaAnimation alphaAnimation(float fromAlpha, float toAlpha, int duration){ + AlphaAnimation alphaAnimation = new AlphaAnimation(fromAlpha, toAlpha); + alphaAnimation.setDuration(duration); + alphaAnimation.setFillAfter(true); + + return alphaAnimation; + } +} diff --git a/DesafioAppProva/app/src/main/res/anim/slide_from_left.xml b/DesafioAppProva/app/src/main/res/anim/slide_from_left.xml new file mode 100644 index 00000000..3db834fd --- /dev/null +++ b/DesafioAppProva/app/src/main/res/anim/slide_from_left.xml @@ -0,0 +1,9 @@ + + + + + \ No newline at end of file diff --git a/DesafioAppProva/app/src/main/res/anim/slide_from_right.xml b/DesafioAppProva/app/src/main/res/anim/slide_from_right.xml new file mode 100644 index 00000000..5f6f19e6 --- /dev/null +++ b/DesafioAppProva/app/src/main/res/anim/slide_from_right.xml @@ -0,0 +1,9 @@ + + + + + \ No newline at end of file diff --git a/DesafioAppProva/app/src/main/res/anim/slide_to_left.xml b/DesafioAppProva/app/src/main/res/anim/slide_to_left.xml new file mode 100644 index 00000000..9b061f62 --- /dev/null +++ b/DesafioAppProva/app/src/main/res/anim/slide_to_left.xml @@ -0,0 +1,9 @@ + + + + + \ No newline at end of file diff --git a/DesafioAppProva/app/src/main/res/anim/slide_to_right.xml b/DesafioAppProva/app/src/main/res/anim/slide_to_right.xml new file mode 100644 index 00000000..c14a1a59 --- /dev/null +++ b/DesafioAppProva/app/src/main/res/anim/slide_to_right.xml @@ -0,0 +1,9 @@ + + + + + \ No newline at end of file diff --git a/DesafioAppProva/app/src/main/res/drawable/appprova.png b/DesafioAppProva/app/src/main/res/drawable/appprova.png new file mode 100644 index 00000000..da2f7fe1 Binary files /dev/null and b/DesafioAppProva/app/src/main/res/drawable/appprova.png differ diff --git a/DesafioAppProva/app/src/main/res/drawable/icon_fork.png b/DesafioAppProva/app/src/main/res/drawable/icon_fork.png new file mode 100644 index 00000000..a2f002f3 Binary files /dev/null and b/DesafioAppProva/app/src/main/res/drawable/icon_fork.png differ diff --git a/DesafioAppProva/app/src/main/res/drawable/icon_star.xml b/DesafioAppProva/app/src/main/res/drawable/icon_star.xml new file mode 100644 index 00000000..850f2619 --- /dev/null +++ b/DesafioAppProva/app/src/main/res/drawable/icon_star.xml @@ -0,0 +1,13 @@ + + + + + + \ No newline at end of file diff --git a/DesafioAppProva/app/src/main/res/drawable/progress.xml b/DesafioAppProva/app/src/main/res/drawable/progress.xml new file mode 100644 index 00000000..c2ec8e12 --- /dev/null +++ b/DesafioAppProva/app/src/main/res/drawable/progress.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/DesafioAppProva/app/src/main/res/layout/activity_home.xml b/DesafioAppProva/app/src/main/res/layout/activity_home.xml new file mode 100644 index 00000000..804d5fe9 --- /dev/null +++ b/DesafioAppProva/app/src/main/res/layout/activity_home.xml @@ -0,0 +1,20 @@ + + + + + + + + \ No newline at end of file diff --git a/DesafioAppProva/app/src/main/res/layout/activity_splash.xml b/DesafioAppProva/app/src/main/res/layout/activity_splash.xml new file mode 100644 index 00000000..4ad3e7c4 --- /dev/null +++ b/DesafioAppProva/app/src/main/res/layout/activity_splash.xml @@ -0,0 +1,33 @@ + + + + + + + + + \ No newline at end of file diff --git a/DesafioAppProva/app/src/main/res/layout/fragment_home.xml b/DesafioAppProva/app/src/main/res/layout/fragment_home.xml new file mode 100644 index 00000000..900157e9 --- /dev/null +++ b/DesafioAppProva/app/src/main/res/layout/fragment_home.xml @@ -0,0 +1,12 @@ + + + + + + \ No newline at end of file diff --git a/DesafioAppProva/app/src/main/res/layout/fragment_item_detail.xml b/DesafioAppProva/app/src/main/res/layout/fragment_item_detail.xml new file mode 100644 index 00000000..644b738b --- /dev/null +++ b/DesafioAppProva/app/src/main/res/layout/fragment_item_detail.xml @@ -0,0 +1,13 @@ + + + + + + \ No newline at end of file diff --git a/DesafioAppProva/app/src/main/res/layout/item_detail.xml b/DesafioAppProva/app/src/main/res/layout/item_detail.xml new file mode 100644 index 00000000..1390fc01 --- /dev/null +++ b/DesafioAppProva/app/src/main/res/layout/item_detail.xml @@ -0,0 +1,79 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/DesafioAppProva/app/src/main/res/layout/item_list.xml b/DesafioAppProva/app/src/main/res/layout/item_list.xml new file mode 100644 index 00000000..e260353f --- /dev/null +++ b/DesafioAppProva/app/src/main/res/layout/item_list.xml @@ -0,0 +1,124 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/DesafioAppProva/app/src/main/res/mipmap-xhdpi/appprova.png b/DesafioAppProva/app/src/main/res/mipmap-xhdpi/appprova.png new file mode 100644 index 00000000..3d573ed8 Binary files /dev/null and b/DesafioAppProva/app/src/main/res/mipmap-xhdpi/appprova.png differ diff --git a/DesafioAppProva/app/src/main/res/values/colors.xml b/DesafioAppProva/app/src/main/res/values/colors.xml new file mode 100644 index 00000000..4a7a34c8 --- /dev/null +++ b/DesafioAppProva/app/src/main/res/values/colors.xml @@ -0,0 +1,6 @@ + + + #343438 + #343438 + #000 + diff --git a/DesafioAppProva/app/src/main/res/values/strings.xml b/DesafioAppProva/app/src/main/res/values/strings.xml new file mode 100644 index 00000000..19467b2a --- /dev/null +++ b/DesafioAppProva/app/src/main/res/values/strings.xml @@ -0,0 +1,10 @@ + + Desafio AppProva + + Desculpe-nos, não foi possível processar a sua solicitação no momento. + Você não está conectado, por favor tente novamente mais tarde. + Esse repositório não possui Pull request no momento. + Github Java + Felipe Miranda + \@felipemirandareis + diff --git a/DesafioAppProva/app/src/main/res/values/styles.xml b/DesafioAppProva/app/src/main/res/values/styles.xml new file mode 100644 index 00000000..5885930d --- /dev/null +++ b/DesafioAppProva/app/src/main/res/values/styles.xml @@ -0,0 +1,11 @@ + + + + + + diff --git a/DesafioAppProva/app/src/test/java/com/felipemiranda/desafioappprova/ExampleUnitTest.java b/DesafioAppProva/app/src/test/java/com/felipemiranda/desafioappprova/ExampleUnitTest.java new file mode 100644 index 00000000..e792cc92 --- /dev/null +++ b/DesafioAppProva/app/src/test/java/com/felipemiranda/desafioappprova/ExampleUnitTest.java @@ -0,0 +1,17 @@ +package com.felipemiranda.desafioappprova; + +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() throws Exception { + assertEquals(4, 2 + 2); + } +} \ No newline at end of file diff --git a/DesafioAppProva/build.gradle b/DesafioAppProva/build.gradle new file mode 100644 index 00000000..f5ee59d3 --- /dev/null +++ b/DesafioAppProva/build.gradle @@ -0,0 +1,31 @@ +// Top-level build file where you can add configuration options common to all sub-projects/modules. + +buildscript { + repositories { + jcenter() + maven { + url 'https://maven.google.com/' + name 'Google' + } + } + dependencies { + classpath 'com.android.tools.build:gradle:3.0.0-rc2' + + // NOTE: Do not place your application dependencies here; they belong + // in the individual module build.gradle files + } +} + +allprojects { + repositories { + jcenter() + maven { + url 'https://maven.google.com/' + name 'Google' + } + } +} + +task clean(type: Delete) { + delete rootProject.buildDir +} diff --git a/DesafioAppProva/gradle.properties b/DesafioAppProva/gradle.properties new file mode 100644 index 00000000..aac7c9b4 --- /dev/null +++ b/DesafioAppProva/gradle.properties @@ -0,0 +1,17 @@ +# 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/DesafioAppProva/gradle/wrapper/gradle-wrapper.jar b/DesafioAppProva/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 00000000..13372aef Binary files /dev/null and b/DesafioAppProva/gradle/wrapper/gradle-wrapper.jar differ diff --git a/DesafioAppProva/gradle/wrapper/gradle-wrapper.properties b/DesafioAppProva/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 00000000..d2b931c7 --- /dev/null +++ b/DesafioAppProva/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Thu Oct 26 19:54:05 BRST 2017 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-4.1-all.zip diff --git a/DesafioAppProva/gradlew b/DesafioAppProva/gradlew new file mode 100755 index 00000000..9d82f789 --- /dev/null +++ b/DesafioAppProva/gradlew @@ -0,0 +1,160 @@ +#!/usr/bin/env bash + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# 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 +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; +esac + +# 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 + +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" ] ; 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 + +# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules +function splitJvmOpts() { + JVM_OPTS=("$@") +} +eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS +JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" + +exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" diff --git a/DesafioAppProva/gradlew.bat b/DesafioAppProva/gradlew.bat new file mode 100644 index 00000000..8a0b282a --- /dev/null +++ b/DesafioAppProva/gradlew.bat @@ -0,0 +1,90 @@ +@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 + +@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= + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@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 Windowz variants + +if not "%OS%" == "Windows_NT" goto win9xME_args +if "%@eval[2+2]" == "4" goto 4NT_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=%* +goto execute + +:4NT_args +@rem Get arguments from the 4NT Shell from JP Software +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/DesafioAppProva/settings.gradle b/DesafioAppProva/settings.gradle new file mode 100644 index 00000000..e7b4def4 --- /dev/null +++ b/DesafioAppProva/settings.gradle @@ -0,0 +1 @@ +include ':app' diff --git a/README.md b/README.md index 80966847..f95733f1 100644 --- a/README.md +++ b/README.md @@ -4,54 +4,60 @@ A ideia deste desafio é nos permitir avaliar melhor as habilidades de candidato 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. +## Ferramentas -## Descrição do projeto +Android Studio 3.0 RC2 -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. +Android SDK -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. +Utilizei os seguintes padrões: -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. +Delimitador de fim de linha: UNIX (LF ou \n) -Ao tocar em um item, deve abrir no browser a página do Pull Request em questão. +Codificação de arquivos: UTF-8 -Você pode se basear neste mockup para criar as telas: +Indentação: 4 espaços -![mockup](https://raw.githubusercontent.com/myfreecomm/desafio-mobile-android/master/mockup-android.png) -Sua aplicação deve: +## Referência API -- 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. +https://developer.github.com/v3/ -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) +## Arquitetura -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. +com.felipemiranda.desafioappprova -## Avaliação +MVP -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? +## Dependências -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. +ButterKnife 8.8.1 + +Facilita o trabalho de encontrar e ligar Views com membros de uma classe, sem a necessidade de se fazer casts e findViewById(int), utilizando anotações. + +Gson 2.8.2 + +Serialização e desserialização de POJOs em formato JSON. + +Retrofit 2.3.0 + +Um cliente HTTP para Java e Android que facilita a escrita de chamadas de APIs RESTful. + +Android Support 26.1.0 + +Bibliotecas da Google que oferecem retrocompatibilidade de versões do Android. Neste projeto, estamos utilizando: + +MultiDex 1.0.2 + +Okhttp 3.8.1 + +Fresco 1.3.0 + + +*build.gradle* + +Observe que utilizei o build.gradle para gerenciar o número de versão de algumas dependências, como as bibliotecas android-support e play-services. Isso nos ajuda a manter todas as dependências nas versões corretas.