re-implementing login flow where now FastHub has its own login screen. Improved overall spacing and dividers, validation for an unauthorized user to be directed to login screen, modified events actions to be displayed properly in feeds list and loads more things i can't remember.

This commit is contained in:
Kosh 2017-03-12 06:21:59 +08:00
parent ece9ff87fc
commit a30e5bbeca
36 changed files with 494 additions and 199 deletions

View File

@ -28,7 +28,8 @@
android:name=".ui.modules.login.LoginView"
android:configChanges="keyboard|orientation|screenSize"
android:label="@string/app_name"
android:launchMode="singleTop">
android:launchMode="singleTop"
android:theme="@style/LoginTheme">
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
@ -132,9 +133,9 @@
<activity
android:name=".ui.modules.notification.NotificationActivityView"
android:label="@string/notifictions"
android:parentActivityName=".ui.modules.main.MainView">
android:parentActivityName=".ui.modules.main.MainView"
android:theme="@style/WhenLargeTheme">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value=".ui.modules.main.MainView"/>
@ -146,7 +147,6 @@
<activity
android:name=".ui.modules.about.FastHubAboutActivity"
android:parentActivityName=".ui.modules.main.MainView"
android:theme="@style/AppTheme.AboutActivity"/>
<activity

View File

@ -1,5 +1,8 @@
package com.fastaccess.data.dao;
import android.os.Parcel;
import android.os.Parcelable;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@ -10,7 +13,28 @@ import lombok.Setter;
@Getter @Setter @NoArgsConstructor
public class AccessTokenModel {
private String accessToken;
private String tokenType;
public class AccessTokenModel implements Parcelable {
long id;
private String token;
private String hashedToken;
@Override public int describeContents() { return 0; }
@Override public void writeToParcel(Parcel dest, int flags) {
dest.writeLong(this.id);
dest.writeString(this.token);
dest.writeString(this.hashedToken);
}
protected AccessTokenModel(Parcel in) {
this.id = in.readLong();
this.token = in.readString();
this.hashedToken = in.readString();
}
public static final Parcelable.Creator<AccessTokenModel> CREATOR = new Parcelable.Creator<AccessTokenModel>() {
@Override public AccessTokenModel createFromParcel(Parcel source) {return new AccessTokenModel(source);}
@Override public AccessTokenModel[] newArray(int size) {return new AccessTokenModel[size];}
};
}

View File

@ -0,0 +1,51 @@
package com.fastaccess.data.dao;
import android.os.Parcel;
import android.os.Parcelable;
import java.util.List;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
/**
* Created by Kosh on 12 Mar 2017, 3:16 AM
*/
@Getter @Setter @NoArgsConstructor
public class AuthModel implements Parcelable {
private String clientId;
private String clientSecret;
private String redirectUri;
private List<String> scopes;
private String state;
private String note;
@Override public int describeContents() { return 0; }
@Override public void writeToParcel(Parcel dest, int flags) {
dest.writeString(this.clientId);
dest.writeString(this.clientSecret);
dest.writeString(this.redirectUri);
dest.writeStringList(this.scopes);
dest.writeString(this.state);
dest.writeString(this.note);
}
protected AuthModel(Parcel in) {
this.clientId = in.readString();
this.clientSecret = in.readString();
this.redirectUri = in.readString();
this.scopes = in.createStringArrayList();
this.state = in.readString();
this.note = in.readString();
}
public static final Creator<AuthModel> CREATOR = new Creator<AuthModel>() {
@Override public AuthModel createFromParcel(Parcel source) {return new AuthModel(source);}
@Override public AuthModel[] newArray(int size) {return new AuthModel[size];}
};
}

View File

@ -3,6 +3,7 @@ package com.fastaccess.data.service;
import android.support.annotation.NonNull;
import com.fastaccess.data.dao.AccessTokenModel;
import com.fastaccess.data.dao.AuthModel;
import com.fastaccess.data.dao.EventsModel;
import com.fastaccess.data.dao.LoginModel;
import com.fastaccess.data.dao.Pageable;
@ -10,6 +11,7 @@ import com.fastaccess.data.dao.RepoModel;
import com.fastaccess.data.dao.UserModel;
import retrofit2.Response;
import retrofit2.http.Body;
import retrofit2.http.DELETE;
import retrofit2.http.Field;
import retrofit2.http.FormUrlEncoded;
@ -33,6 +35,11 @@ public interface UserRestService {
@NonNull @Field("state") String state,
@NonNull @Field("redirect_uri") String redirectUrl);
@PUT("authorizations/clients/{clientId}") Observable<AccessTokenModel> login(@NonNull @Path("clientId") String clientId,
@NonNull @Body AuthModel authModel);
@DELETE("authorizations/{id}") Observable<Response<Boolean>> deleteToken(@Path("id") long id);
@GET("user") Observable<LoginModel> getUser();
@GET("users/{username}") Observable<UserModel> getUser(@Path("username") @NonNull String username);

View File

@ -0,0 +1,55 @@
package com.fastaccess.provider.rest;
import android.support.annotation.NonNull;
import com.fastaccess.BuildConfig;
import com.fastaccess.data.service.UserRestService;
import com.fastaccess.provider.rest.converters.GithubResponseConverter;
import com.fastaccess.provider.rest.interceptors.AuthenticationInterceptor;
import com.google.gson.FieldNamingPolicy;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import java.lang.reflect.Modifier;
import okhttp3.OkHttpClient;
import okhttp3.logging.HttpLoggingInterceptor;
import retrofit2.Retrofit;
import retrofit2.adapter.rxjava.RxJavaCallAdapterFactory;
/**
* Created by Kosh on 08 Feb 2017, 8:37 PM
*/
public class LoginProvider {
private final static Gson gson = new GsonBuilder()
.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)
.excludeFieldsWithModifiers(Modifier.FINAL, Modifier.TRANSIENT, Modifier.STATIC)
.setDateFormat("yyyy-MM-dd HH:mm:ss")
.setPrettyPrinting()
.create();
private static OkHttpClient provideOkHttpClient(@NonNull String authToken) {
OkHttpClient.Builder client = new OkHttpClient.Builder();
if (BuildConfig.DEBUG) {
client.addInterceptor(new HttpLoggingInterceptor()
.setLevel(HttpLoggingInterceptor.Level.BODY));
}
client.addInterceptor(new AuthenticationInterceptor(authToken));
return client.build();
}
private static Retrofit provideRetrofit(@NonNull String authToken) {
return new Retrofit.Builder()
.baseUrl(BuildConfig.REST_URL)
.client(provideOkHttpClient(authToken))
.addConverterFactory(new GithubResponseConverter(gson))
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.build();
}
@NonNull public static UserRestService getLoginRestService(@NonNull String authToken) {
return provideRetrofit(authToken).create(UserRestService.class);
}
}

View File

@ -0,0 +1,21 @@
package com.fastaccess.provider.rest.interceptors;
import java.io.IOException;
import lombok.AllArgsConstructor;
import okhttp3.Interceptor;
import okhttp3.Request;
import okhttp3.Response;
@AllArgsConstructor
public class AuthenticationInterceptor implements Interceptor {
private String authToken;
@Override public Response intercept(Chain chain) throws IOException {
Request original = chain.request();
Request.Builder builder = original.newBuilder().header("Authorization", authToken);
Request request = builder.build();
return chain.proceed(request);
}
}

View File

@ -7,6 +7,7 @@ import android.view.ViewGroup;
import com.fastaccess.R;
import com.fastaccess.data.dao.EventsModel;
import com.fastaccess.data.dao.types.EventsType;
import com.fastaccess.helper.ParseDateFormat;
import com.fastaccess.ui.widgets.AvatarLayout;
import com.fastaccess.ui.widgets.FontTextView;
@ -41,10 +42,16 @@ public class FeedsViewHolder extends BaseViewHolder<EventsModel> {
avatar.setUrl(null, null);
}
SpannableBuilder spannableBuilder = SpannableBuilder.builder();
spannableBuilder.append(eventsModel.getActor() != null ? eventsModel.getActor().getLogin() : "n/a").append(" ");
spannableBuilder.append(eventsModel.getActor() != null ? eventsModel.getActor().getLogin() : "N/A").append(" ");
if (eventsModel.getType() != null)
spannableBuilder.bold(itemView.getResources().getString(eventsModel.getType().getType()).toLowerCase()).append(" ");
spannableBuilder.append(eventsModel.getRepo() != null ? eventsModel.getRepo().getName() : "n/a");
spannableBuilder.bold(eventsModel.getPayload() != null ? eventsModel.getPayload().getAction() : "")
.append(eventsModel.getPayload() != null && eventsModel.getPayload().getAction() != null ? " " : "");
if (eventsModel.getType() != EventsType.WatchEvent) {
spannableBuilder
.bold(itemView.getResources().getString(eventsModel.getType().getType()).toLowerCase())
.append(" ");
}
spannableBuilder.append(eventsModel.getRepo() != null ? eventsModel.getRepo().getName() : "N/A");
title.setText(spannableBuilder);
date.setText(ParseDateFormat.getTimeAgo(eventsModel.getCreatedAt()));
}

View File

@ -12,6 +12,7 @@ import android.support.v7.widget.Toolbar;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.webkit.CookieManager;
import android.widget.Toast;
import com.fastaccess.BuildConfig;
@ -184,6 +185,13 @@ public abstract class BaseActivity<V extends BaseMvp.FAView, P extends BasePrese
}
}
@Override public void onRequireLogin() {
CookieManager.getInstance().removeAllCookies(null);
PrefGetter.clear();
LoginModel.deleteTable().execute();
recreate();
}
private void setupToolbarAndStatusBar(@Nullable Toolbar toolbar) {
changeStatusBarColor(isTransparent());
if (toolbar != null) {

View File

@ -103,6 +103,10 @@ public abstract class BaseDialogFragment<V extends BaseMvp.FAView, P extends Bas
}
@Override public void onRequireLogin() {
callback.onRequireLogin();
}
@Override public void onDestroyView() {
super.onDestroyView();
if (unbinder != null) unbinder.unbind();

View File

@ -10,6 +10,7 @@ import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import com.fastaccess.data.dao.LoginModel;
import com.fastaccess.ui.base.mvp.BaseMvp;
import com.fastaccess.ui.base.mvp.presenter.BasePresenter;
@ -68,7 +69,9 @@ public abstract class BaseFragment<V extends BaseMvp.FAView, P extends BasePrese
@Override public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
onFragmentCreated(view, savedInstanceState);
if (LoginModel.getUser() != null) {
onFragmentCreated(view, savedInstanceState);
}
}
@Override public void onDestroyView() {
@ -100,6 +103,10 @@ public abstract class BaseFragment<V extends BaseMvp.FAView, P extends BasePrese
return callback.isLoggedIn();
}
@Override public void onRequireLogin() {
callback.onRequireLogin();
}
@Override public void onMessageDialogActionClicked(boolean isOk, @Nullable Bundle bundle) {
}

View File

@ -31,6 +31,8 @@ public interface BaseMvp {
@CallOnMainThread void showErrorMessage(@NonNull String msgRes);
boolean isLoggedIn();
void onRequireLogin();
}
interface FAPresenter {

View File

@ -2,6 +2,7 @@ package com.fastaccess.ui.base.mvp.presenter;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.StringRes;
import com.fastaccess.R;
import com.fastaccess.data.dao.GitHubErrorResponse;
@ -41,6 +42,10 @@ public class BasePresenter<V extends BaseMvp.FAView> extends TiPresenter<V> impl
@Override public void onError(@NonNull Throwable throwable) {
throwable.printStackTrace();
if (RestProvider.getErrorCode(throwable) == 401) {
sendToView(BaseMvp.FAView::onRequireLogin);
return;
}
GitHubErrorResponse errorResponse = RestProvider.getErrorResponse(throwable);
Logger.e(errorResponse);
if (errorResponse != null && errorResponse.getMessage() != null) {
@ -57,4 +62,8 @@ public class BasePresenter<V extends BaseMvp.FAView> extends TiPresenter<V> impl
.subscribe(onNext, this::onError, () -> apiCalled = true)
);
}
@StringRes private int getPrettifiedErrorMessage(@Nullable Throwable throwable) {
return 0;
}
}

View File

@ -1,13 +1,11 @@
package com.fastaccess.ui.modules.login;
import android.net.Uri;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import com.fastaccess.data.dao.AccessTokenModel;
import com.fastaccess.data.dao.LoginModel;
import com.fastaccess.ui.base.mvp.BaseMvp;
import com.fastaccess.ui.widgets.AppbarRefreshLayout;
/**
* Created by Kosh on 09 Nov 2016, 9:41 PM
@ -15,20 +13,24 @@ import com.fastaccess.ui.widgets.AppbarRefreshLayout;
interface LoginMvp {
interface View extends BaseMvp.FAView, AppbarRefreshLayout.OnRefreshListener {
interface View extends BaseMvp.FAView {
void onEmptyUserName(boolean isEmpty);
void onEmptyPassword(boolean isEmpty);
void onSuccessfullyLoggedIn();
}
interface Presenter extends BaseMvp.FAPresenter {
@Nullable String getCode(@NonNull String url);
@NonNull Uri getAuthorizationUrl();
void onGetToken(@NonNull String code);
void onTokenResponse(@Nullable AccessTokenModel response);
void onUserResponse(@Nullable LoginModel response);
void login(@NonNull String username, @NonNull String password);
}
}

View File

@ -1,51 +1,29 @@
package com.fastaccess.ui.modules.login;
import android.net.Uri;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import com.fastaccess.BuildConfig;
import com.fastaccess.R;
import com.fastaccess.data.dao.AccessTokenModel;
import com.fastaccess.data.dao.AuthModel;
import com.fastaccess.data.dao.LoginModel;
import com.fastaccess.helper.InputHelper;
import com.fastaccess.helper.PrefGetter;
import com.fastaccess.provider.rest.LoginProvider;
import com.fastaccess.provider.rest.RestProvider;
import com.fastaccess.ui.base.mvp.presenter.BasePresenter;
import java.util.Arrays;
import okhttp3.Credentials;
/**
* Created by Kosh on 09 Nov 2016, 9:43 PM
*/
class LoginPresenter extends BasePresenter<LoginMvp.View> implements LoginMvp.Presenter {
@Nullable @Override public String getCode(@NonNull String url) {
Uri uri = Uri.parse(url);
if (uri != null && uri.toString().startsWith(BuildConfig.REDIRECT_URL)) {
String code = uri.getQueryParameter("code");
if (code != null) {
return code;
} else if (uri.getQueryParameter("error") != null) {
sendToView(view -> view.showMessage(R.string.error, R.string.failed_login));
}
}
return null;
}
@NonNull @Override public Uri getAuthorizationUrl() {
return new Uri.Builder()
.scheme("https")
.authority("github.com")
.appendPath("login")
.appendPath("oauth")
.appendPath("authorize")
.appendQueryParameter("client_id", BuildConfig.GITHUB_CLIENT_ID)
.appendQueryParameter("redirect_uri", BuildConfig.REDIRECT_URL)
.appendQueryParameter("scope", "user,repo,gist,notifications")
.appendQueryParameter("state", BuildConfig.APPLICATION_ID)
.build();
}
@Override public void onGetToken(@NonNull String code) {
makeRestCall(RestProvider.getLoginRestService().getAccessToken(code,
BuildConfig.GITHUB_CLIENT_ID, BuildConfig.GITHUB_SECRET,
@ -55,7 +33,7 @@ class LoginPresenter extends BasePresenter<LoginMvp.View> implements LoginMvp.Pr
@Override public void onTokenResponse(@Nullable AccessTokenModel modelResponse) {
if (modelResponse != null) {
String token = modelResponse.getAccessToken();
String token = modelResponse.getToken();
if (!InputHelper.isEmpty(token)) {
PrefGetter.setToken(token);
makeRestCall(RestProvider.getUserService().getUser(), this::onUserResponse);
@ -74,4 +52,27 @@ class LoginPresenter extends BasePresenter<LoginMvp.View> implements LoginMvp.Pr
}
sendToView(view -> view.showMessage(R.string.error, R.string.failed_login));
}
@Override public void login(@NonNull String username, @NonNull String password) {
boolean usernameIsEmpty = InputHelper.isEmpty(username);
boolean passwordIsEmpty = InputHelper.isEmpty(password);
if (getView() == null) return;
getView().onEmptyUserName(usernameIsEmpty);
getView().onEmptyPassword(passwordIsEmpty);
if (!usernameIsEmpty && !passwordIsEmpty) {
String authToken = Credentials.basic(username, password);
AuthModel authModel = new AuthModel();
authModel.setScopes(Arrays.asList("user", "repo", "gist", "notifications"));
authModel.setNote(BuildConfig.APPLICATION_ID + "-" + authToken);//make it unique to FastHub.
authModel.setClientSecret(BuildConfig.GITHUB_SECRET);
makeRestCall(LoginProvider.getLoginRestService(authToken).login(BuildConfig.GITHUB_CLIENT_ID, authModel), tokenModel -> {
if (InputHelper.isEmpty(tokenModel.getToken())) {
makeRestCall(LoginProvider.getLoginRestService(authToken).deleteToken(tokenModel.getId()),
response -> login(username, password));
} else {
onTokenResponse(tokenModel);
}
});
}
}
}

View File

@ -1,21 +1,22 @@
package com.fastaccess.ui.modules.login;
import android.annotation.SuppressLint;
import android.content.Intent;
import android.os.Build;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.webkit.WebChromeClient;
import android.webkit.WebResourceRequest;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.support.annotation.StringRes;
import android.support.design.widget.FloatingActionButton;
import android.support.design.widget.TextInputEditText;
import android.support.design.widget.TextInputLayout;
import android.widget.ProgressBar;
import com.fastaccess.R;
import com.fastaccess.helper.AnimHelper;
import com.fastaccess.helper.InputHelper;
import com.fastaccess.ui.base.BaseActivity;
import com.fastaccess.ui.modules.main.MainView;
import com.fastaccess.ui.widgets.AppbarRefreshLayout;
import butterknife.BindView;
import butterknife.OnClick;
/**
* Created by Kosh on 08 Feb 2017, 9:10 PM
@ -24,8 +25,12 @@ import butterknife.BindView;
public class LoginView extends BaseActivity<LoginMvp.View, LoginPresenter> implements LoginMvp.View {
@BindView(R.id.webView) WebView webView;
@BindView(R.id.refresh) AppbarRefreshLayout refresh;
@BindView(R.id.usernameEditText) TextInputEditText usernameEditText;
@BindView(R.id.username) TextInputLayout username;
@BindView(R.id.passwordEditText) TextInputEditText passwordEditText;
@BindView(R.id.password) TextInputLayout password;
@BindView(R.id.login) FloatingActionButton login;
@BindView(R.id.progress) ProgressBar progress;
@Override protected int layout() {
return R.layout.login_layout;
@ -47,8 +52,12 @@ public class LoginView extends BaseActivity<LoginMvp.View, LoginPresenter> imple
return new LoginPresenter();
}
@Override public void onRefresh() {
webView.loadUrl(getPresenter().getAuthorizationUrl().toString());
@Override public void onEmptyUserName(boolean isEmpty) {
username.setError(isEmpty ? getString(R.string.required_field) : null);
}
@Override public void onEmptyPassword(boolean isEmpty) {
password.setError(isEmpty ? getString(R.string.required_field) : null);
}
@Override public void onSuccessfullyLoggedIn() {
@ -57,44 +66,41 @@ public class LoginView extends BaseActivity<LoginMvp.View, LoginPresenter> imple
finish();
}
@SuppressLint("SetJavaScriptEnabled") @Override protected void onCreate(Bundle savedInstanceState) {
@Override protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
refresh.setOnRefreshListener(this);
webView.getSettings().setSaveFormData(false);
webView.getSettings().setJavaScriptEnabled(true);
webView.setWebChromeClient(new WebChromeClient() {
@Override public void onProgressChanged(WebView view, int progress) {
super.onProgressChanged(view, progress);
if (progress == 100) {
refresh.setRefreshing(false);
} else if (progress < 100) {
refresh.setRefreshing(true);
}
}
@OnClick(R.id.login) public void onClick() {
getPresenter().login(InputHelper.toString(username), InputHelper.toString(password));
}
@Override public void showErrorMessage(@NonNull String msgRes) {
hideProgress();
super.showErrorMessage(msgRes);
}
@Override public void showMessage(@StringRes int titleRes, @StringRes int msgRes) {
hideProgress();
super.showMessage(titleRes, msgRes);
}
@Override public void showProgress(@StringRes int resId) {
AnimHelper.animateVisibility(login, false, new AnimHelper.AnimationCallback() {
@Override public void onAnimationEnd() {
AnimHelper.animateVisibility(progress, true);
}
@Override public void onAnimationStart() {}
});
}
@Override public void hideProgress() {
AnimHelper.animateVisibility(progress, false, new AnimHelper.AnimationCallback() {
@Override public void onAnimationEnd() {
AnimHelper.animateVisibility(login, true);
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
webView.setWebViewClient(new WebViewClient() {
@Override public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {
String code = getPresenter().getCode(request.getUrl().toString());
if (code != null) {
getPresenter().onGetToken(code);
}
return false;
}
});
} else {
webView.setWebViewClient(new WebViewClient() {
@SuppressWarnings("deprecation") @Override public boolean shouldOverrideUrlLoading(WebView view, String url) {
String code = getPresenter().getCode(url);
if (code != null) {
getPresenter().onGetToken(code);
}
return false;
}
});
}
onRefresh();
@Override public void onAnimationStart() {}
});
}
}

View File

@ -13,7 +13,6 @@ import android.support.v4.widget.DrawerLayout;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.webkit.CookieManager;
import android.widget.TextView;
import android.widget.Toast;
@ -182,10 +181,7 @@ public class MainView extends BaseActivity<MainMvp.View, MainPresenter> implemen
}
@Override public void onLogout() {
CookieManager.getInstance().removeAllCookies(null);
PrefGetter.clear();
LoginModel.deleteTable().execute();
recreate();
onRequireLogin();
}
@Override public void openFasHubRepo() {

View File

@ -10,6 +10,7 @@ import android.util.AttributeSet;
import android.view.View;
import com.fastaccess.R;
import com.fastaccess.helper.ViewHelper;
import com.fastaccess.ui.widgets.StateLayout;
@ -107,16 +108,18 @@ public class DynamicRecyclerView extends RecyclerView {
}
public void addKeyLineDivider() {
Resources resources = getResources();
addItemDecoration(new InsetDividerDecoration(resources.getDimensionPixelSize(R.dimen.divider_height),
resources.getDimensionPixelSize(R.dimen.keyline_1),
ContextCompat.getColor(getContext(), R.color.divider)));
if (!ViewHelper.isTablet(getContext())) {
Resources resources = getResources();
addItemDecoration(new InsetDividerDecoration(resources.getDimensionPixelSize(R.dimen.divider_height),
resources.getDimensionPixelSize(R.dimen.keyline_1), ContextCompat.getColor(getContext(), R.color.divider)));
}
}
public void addDivider() {
Resources resources = getResources();
addItemDecoration(new InsetDividerDecoration(resources.getDimensionPixelSize(R.dimen.divider_height),
resources.getDimensionPixelSize(R.dimen.spacing_xs_large),
ContextCompat.getColor(getContext(), R.color.divider)));
if (!ViewHelper.isTablet(getContext())) {
Resources resources = getResources();
addItemDecoration(new InsetDividerDecoration(resources.getDimensionPixelSize(R.dimen.divider_height), 0,
ContextCompat.getColor(getContext(), R.color.divider)));
}
}
}

View File

@ -35,15 +35,18 @@ class InsetDividerDecoration extends RecyclerView.ItemDecoration {
boolean hasDividers = false;
for (int i = 0; i < childCount; i++) {
View child = parent.getChildAt(i);
int position = parent.getChildAdapterPosition(child);
if (child.isActivated() || (i + 1 < childCount && parent.getChildAt(i + 1).isActivated())) {
continue;
}
lines[i * 4] = inset + lm.getDecoratedLeft(child);
lines[(i * 4) + 2] = lm.getDecoratedRight(child);
int y = lm.getDecoratedBottom(child) + (int) child.getTranslationY() - height;
lines[(i * 4) + 1] = y;
lines[(i * 4) + 3] = y;
hasDividers = true;
if (position != (state.getItemCount() - 1)) {
lines[i * 4] = inset == 0 ? inset : inset + lm.getDecoratedLeft(child);
lines[(i * 4) + 2] = lm.getDecoratedRight(child);
int y = lm.getDecoratedBottom(child) + (int) child.getTranslationY() - height;
lines[(i * 4) + 1] = y;
lines[(i * 4) + 3] = y;
hasDividers = true;
}
}
if (hasDividers) {
canvas.drawLines(lines, paint);

View File

@ -9,7 +9,6 @@
android:orientation="horizontal"
android:paddingBottom="@dimen/spacing_micro"
android:paddingEnd="@dimen/spacing_xs_large"
android:paddingStart="@dimen/spacing_xs_large"
android:paddingTop="@dimen/spacing_micro"
tools:showIn="@layout/repo_file_layout">
@ -25,6 +24,8 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginEnd="@dimen/avatar_margin"
android:layout_marginStart="@dimen/avatar_margin"
android:background="?selectableItemBackgroundBorderless"
android:contentDescription="@string/home"
android:padding="@dimen/spacing_normal"
@ -35,7 +36,6 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginStart="@dimen/spacing_xs_large"
android:orientation="horizontal"
app:layoutManager="@string/linear_layout_manager"
tools:listitem="@layout/file_path_row_item"/>
@ -53,6 +53,8 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginEnd="@dimen/avatar_margin"
android:layout_marginStart="@dimen/avatar_margin"
android:contentDescription="@string/switch_branch"
android:padding="@dimen/spacing_normal"
android:src="@drawable/ic_branch"/>
@ -62,8 +64,7 @@
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginEnd="@dimen/spacing_xs_large"
android:layout_marginStart="@dimen/spacing_xs_large"
android:layout_marginEnd="@dimen/spacing_normal"
android:layout_weight="1"
android:spinnerMode="dialog"
app:backgroundTint="?colorAccent"

View File

@ -9,7 +9,6 @@
android:orientation="horizontal"
android:paddingBottom="@dimen/spacing_micro"
android:paddingEnd="@dimen/spacing_xs_large"
android:paddingStart="@dimen/spacing_xs_large"
android:paddingTop="@dimen/spacing_micro"
tools:showIn="@layout/repo_file_layout">
@ -25,6 +24,8 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginEnd="@dimen/avatar_margin"
android:layout_marginStart="@dimen/avatar_margin"
android:background="?selectableItemBackgroundBorderless"
android:contentDescription="@string/home"
android:padding="@dimen/spacing_normal"
@ -35,7 +36,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginStart="@dimen/spacing_xs_large"
android:layout_marginEnd="@dimen/spacing_normal"
android:orientation="horizontal"
app:layoutManager="@string/linear_layout_manager"
tools:listitem="@layout/file_path_row_item"/>
@ -53,6 +54,8 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginEnd="@dimen/avatar_margin"
android:layout_marginStart="@dimen/avatar_margin"
android:contentDescription="@string/switch_branch"
android:padding="@dimen/spacing_normal"
android:src="@drawable/ic_branch"/>
@ -62,8 +65,7 @@
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginEnd="@dimen/spacing_xs_large"
android:layout_marginStart="@dimen/spacing_xs_large"
android:layout_marginEnd="@dimen/spacing_normal"
android:layout_weight="1"
android:spinnerMode="dialog"
app:backgroundTint="?colorAccent"

View File

@ -1,52 +1,115 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
<android.support.design.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
android:background="@color/material_indigo_700"
android:orientation="vertical">
<android.support.design.widget.AppBarLayout
<android.support.v4.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fitsSystemWindows="true">
android:layout_gravity="center">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="100dp"
<android.support.v7.widget.CardView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:fitsSystemWindows="true"
android:gravity="center"
android:orientation="vertical"
android:paddingEnd="@dimen/spacing_xs_large"
android:paddingStart="@dimen/spacing_xs_large"
app:layout_scrollFlags="scroll|enterAlways">
android:layout_margin="@dimen/spacing_s_large"
android:minHeight="350dp"
android:minWidth="250dp"
app:cardElevation="@dimen/spacing_normal">
<com.fastaccess.ui.widgets.FontTextView
style="@style/TextAppearance.AppCompat.Title"
android:layout_width="wrap_content"
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:gravity="center"
android:text="@string/sign_in_to_github"
android:textColor="?colorAccent"/>
android:orientation="vertical">
</LinearLayout>
</android.support.design.widget.AppBarLayout>
<com.fastaccess.ui.widgets.FontTextView
android:id="@+id/mainCard"
style="@style/TextAppearance.AppCompat.Title.Inverse"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:background="@color/material_indigo_700"
android:gravity="center"
android:paddingBottom="@dimen/spacing_xlarge"
android:paddingEnd="@dimen/spacing_xs_large"
android:paddingStart="@dimen/spacing_xs_large"
android:paddingTop="@dimen/spacing_xs_large"
android:text="@string/sign_in_to_github"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginTop="@dimen/spacing_normal"
android:gravity="center"
android:orientation="vertical"
android:paddingBottom="@dimen/spacing_normal"
android:paddingEnd="@dimen/spacing_xs_large"
android:paddingStart="@dimen/spacing_xs_large"
android:paddingTop="@dimen/spacing_normal">
<android.support.design.widget.TextInputLayout
android:id="@+id/username"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/username">
<android.support.design.widget.TextInputEditText
android:id="@+id/usernameEditText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="text"
android:maxLines="1"/>
</android.support.design.widget.TextInputLayout>
<android.support.design.widget.TextInputLayout
android:id="@+id/password"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/spacing_xs_large"
android:hint="@string/password"
app:passwordToggleEnabled="true"
app:passwordToggleTint="?colorAccent">
<android.support.design.widget.TextInputEditText
android:id="@+id/passwordEditText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textPassword"
android:maxLines="1"/>
</android.support.design.widget.TextInputLayout>
<FrameLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<android.support.design.widget.FloatingActionButton
android:id="@+id/login"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_margin="@dimen/fab_margin"
android:src="@drawable/ic_send"
android:tint="@color/white"
app:fabSize="normal"/>
<ProgressBar
android:id="@+id/progress"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_margin="@dimen/fab_margin"
android:visibility="gone"/>
</FrameLayout>
</LinearLayout>
</LinearLayout>
</android.support.v7.widget.CardView>
</android.support.v4.widget.NestedScrollView>
<com.fastaccess.ui.widgets.AppbarRefreshLayout
android:id="@+id/refresh"
android:layout_width="match_parent"
android:layout_height="match_parent">
<WebView
android:id="@+id/webView"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</com.fastaccess.ui.widgets.AppbarRefreshLayout>
</LinearLayout>
</android.support.design.widget.CoordinatorLayout>

View File

@ -14,6 +14,7 @@
android:id="@+id/recycler"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:listDivider="@color/divider"
app:layoutManager="@string/staggered_layout_manager"
app:spanCount="@integer/small_spans"/>

View File

@ -10,20 +10,23 @@
android:clipToPadding="false"
android:minHeight="?actionBarSize"
android:orientation="horizontal"
android:padding="@dimen/spacing_xs_large"
android:paddingBottom="@dimen/spacing_xs_large"
android:paddingEnd="@dimen/spacing_xs_large"
android:paddingTop="@dimen/spacing_xs_large"
app:layout_scrollFlags="scroll|enterAlways"
tools:showIn="@layout/repo_pager_activity">
<com.fastaccess.ui.widgets.AvatarLayout
android:id="@+id/avatarLayout"
android:layout_width="48dp"
android:layout_height="48dp"/>
android:layout_height="48dp"
android:layout_marginEnd="@dimen/avatar_margin"
android:layout_marginStart="@dimen/avatar_margin"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="top"
android:layout_marginStart="@dimen/spacing_s_large"
android:orientation="vertical">
<com.fastaccess.ui.widgets.FontTextView

View File

@ -14,6 +14,7 @@
android:id="@+id/recycler"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:listDivider="@color/divider"
app:layoutManager="@string/linear_layout_manager"/>
</com.fastaccess.ui.widgets.AppbarRefreshLayout>

View File

@ -10,18 +10,22 @@
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/spacing_xs_large"
android:orientation="horizontal"
android:padding="@dimen/spacing_xs_large">
android:paddingBottom="@dimen/spacing_xs_large"
android:paddingEnd="@dimen/spacing_xs_large"
android:paddingTop="@dimen/spacing_xs_large">
<com.fastaccess.ui.widgets.AvatarLayout
android:id="@+id/avatarLayout"
android:layout_width="@dimen/header_icon_zie"
android:layout_height="@dimen/header_icon_zie"/>
android:layout_height="@dimen/header_icon_zie"
android:layout_marginEnd="@dimen/avatar_margin"
android:layout_marginStart="@dimen/avatar_margin"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginStart="@dimen/spacing_xs_large"
android:layout_marginEnd="@dimen/spacing_normal"
android:orientation="vertical">
<com.fastaccess.ui.widgets.FontTextView

View File

@ -10,7 +10,6 @@
android:foreground="?android:selectableItemBackground"
android:stateListAnimator="@animator/cardview_selector"
app:contentPaddingBottom="@dimen/spacing_normal"
app:contentPaddingLeft="@dimen/spacing_xs_large"
app:contentPaddingRight="@dimen/spacing_xs_large"
app:contentPaddingTop="@dimen/spacing_normal">
@ -27,7 +26,9 @@
<com.fastaccess.ui.widgets.AvatarLayout
android:id="@+id/avatarView"
android:layout_width="48dp"
android:layout_height="48dp"/>
android:layout_height="48dp"
android:layout_marginEnd="@dimen/avatar_margin"
android:layout_marginStart="@dimen/avatar_margin"/>
<com.fastaccess.ui.widgets.FontTextView
android:id="@+id/name"
@ -35,8 +36,7 @@
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginEnd="@dimen/spacing_xs_large"
android:layout_marginStart="@dimen/spacing_xs_large"
android:layout_marginEnd="@dimen/spacing_normal"
android:layout_weight="1"
android:ellipsize="end"
android:maxLines="1"
@ -58,6 +58,8 @@
android:id="@+id/comment"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginEnd="@dimen/avatar_margin"
android:layout_marginStart="@dimen/avatar_margin"
android:layout_marginTop="@dimen/spacing_normal"/>
</LinearLayout>
</android.support.v7.widget.CardView>

View File

@ -9,7 +9,6 @@
android:foreground="?android:selectableItemBackground"
android:paddingBottom="@dimen/spacing_normal"
android:paddingEnd="@dimen/spacing_xs_large"
android:paddingStart="@dimen/spacing_xs_large"
android:paddingTop="@dimen/spacing_normal"
android:stateListAnimator="@animator/raise_selector">
@ -22,13 +21,15 @@
android:id="@+id/avatarLayout"
android:layout_width="@dimen/large_icon_zie"
android:layout_height="@dimen/large_icon_zie"
android:layout_marginEnd="@dimen/avatar_margin"
android:layout_marginStart="@dimen/avatar_margin"
android:transitionName="@string/icon_transition"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginStart="@dimen/spacing_xs_large"
android:orientation="vertical">
<com.fastaccess.ui.widgets.FontTextView

View File

@ -9,7 +9,6 @@
android:clickable="true"
android:foreground="?android:selectableItemBackground"
app:contentPaddingBottom="@dimen/spacing_normal"
app:contentPaddingLeft="@dimen/spacing_xs_large"
app:contentPaddingRight="@dimen/spacing_xs_large"
app:contentPaddingTop="@dimen/spacing_normal">
@ -26,7 +25,9 @@
<com.fastaccess.ui.widgets.AvatarLayout
android:id="@+id/avatarView"
android:layout_width="48dp"
android:layout_height="48dp"/>
android:layout_height="48dp"
android:layout_marginEnd="@dimen/avatar_margin"
android:layout_marginStart="@dimen/avatar_margin"/>
<com.fastaccess.ui.widgets.FontTextView
android:id="@+id/name"
@ -34,8 +35,7 @@
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginEnd="@dimen/spacing_xs_large"
android:layout_marginStart="@dimen/spacing_xs_large"
android:layout_marginEnd="@dimen/spacing_normal"
android:layout_weight="1"
android:ellipsize="end"
android:maxLines="1"
@ -58,6 +58,8 @@
android:id="@+id/description"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginEnd="@dimen/avatar_margin"
android:layout_marginStart="@dimen/avatar_margin"
android:layout_marginTop="@dimen/spacing_normal"/>
</LinearLayout>

View File

@ -10,7 +10,6 @@
android:foreground="?android:selectableItemBackground"
android:paddingBottom="@dimen/spacing_normal"
android:paddingEnd="@dimen/spacing_xs_large"
android:paddingStart="@dimen/spacing_xs_large"
android:paddingTop="@dimen/spacing_normal"
android:stateListAnimator="@animator/raise_selector">
@ -25,12 +24,13 @@
android:layout_width="48dp"
android:layout_height="48dp"
android:visibility="gone"
android:layout_marginEnd="@dimen/avatar_margin"
android:layout_marginStart="@dimen/avatar_margin"
tools:visibility="visible"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/spacing_xs_large"
android:orientation="vertical">
<com.fastaccess.ui.widgets.FontTextView

View File

@ -35,8 +35,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingBottom="@dimen/spacing_normal"
android:paddingEnd="@dimen/spacing_normal"
android:paddingStart="@dimen/spacing_normal"
android:paddingEnd="@dimen/spacing_xs_large"
android:paddingTop="@dimen/spacing_normal">
<com.fastaccess.ui.widgets.AvatarLayout
@ -44,7 +43,8 @@
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_gravity="top"
android:layout_marginEnd="@dimen/spacing_normal"/>
android:layout_marginEnd="@dimen/avatar_margin"
android:layout_marginStart="@dimen/avatar_margin"/>
<com.fastaccess.ui.widgets.FontTextView
android:id="@+id/stateText"

View File

@ -24,12 +24,14 @@
android:id="@+id/avatarLayout"
android:layout_width="48dp"
android:layout_height="48dp"
android:visibility="gone"/>
android:layout_marginEnd="@dimen/avatar_margin"
android:layout_marginStart="@dimen/avatar_margin"
android:visibility="gone"
tools:visibility="visible"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/spacing_xs_large"
android:orientation="vertical">
<com.fastaccess.ui.widgets.FontTextView
@ -37,8 +39,8 @@
style="@style/TextAppearance.AppCompat.Subhead"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:maxLines="3"
android:ellipsize="end"
android:maxLines="3"
android:textColor="@color/primary_text"
tools:text="When one acquires music and afterlife, one is able to capture heaven\nWhen one acquires music and afterlife, one is able to capture heaven\n."/>

View File

@ -9,7 +9,6 @@
android:foreground="?android:selectableItemBackground"
android:paddingBottom="@dimen/spacing_normal"
android:paddingEnd="@dimen/spacing_xs_large"
android:paddingStart="@dimen/spacing_xs_large"
android:paddingTop="@dimen/spacing_normal"
android:stateListAnimator="@animator/raise_selector">
@ -23,6 +22,8 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginEnd="@dimen/avatar_margin"
android:layout_marginStart="@dimen/avatar_margin"
android:background="?selectableItemBackgroundBorderless"
android:contentDescription="@string/file"
android:padding="@dimen/spacing_normal"
@ -32,6 +33,7 @@
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginEnd="@dimen/spacing_normal"
android:layout_weight="1"
android:orientation="vertical">
@ -40,8 +42,6 @@
style="@style/TextAppearance.AppCompat.Medium"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginEnd="@dimen/spacing_xs_large"
android:layout_marginStart="@dimen/spacing_xs_large"
android:ellipsize="end"
android:maxLines="3"
android:textColor="@color/primary_text"
@ -53,8 +53,6 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginEnd="@dimen/spacing_xs_large"
android:layout_marginStart="@dimen/spacing_xs_large"
tools:text="10KB"/>
</LinearLayout>

View File

@ -10,7 +10,6 @@
android:foreground="?android:selectableItemBackground"
android:paddingBottom="@dimen/spacing_normal"
android:paddingEnd="@dimen/spacing_xs_large"
android:paddingStart="@dimen/spacing_xs_large"
android:paddingTop="@dimen/spacing_normal"
android:stateListAnimator="@animator/raise_selector">
@ -24,12 +23,14 @@
android:id="@+id/avatarLayout"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_marginEnd="@dimen/avatar_margin"
android:layout_marginStart="@dimen/avatar_margin"
tools:visibility="visible"
android:visibility="gone"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/spacing_xs_large"
android:orientation="vertical">
<com.fastaccess.ui.widgets.FontTextView
@ -37,8 +38,8 @@
style="@style/TextAppearance.AppCompat.Medium"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:maxLines="3"
android:ellipsize="end"
android:maxLines="3"
android:textColor="@color/primary_text"
android:textStyle="bold"
tools:text="When one acquires music and afterlife, one is able to capture heaven."/>

View File

@ -26,4 +26,5 @@
<dimen name="dimen.bbn_elevation_tablet">0dp</dimen>
<dimen name="bbn_top_shadow_height">@dimen/spacing_micro</dimen>
<dimen name="divider_height">1px</dimen>
<dimen name="avatar_margin">12dp</dimen>
</resources>

View File

@ -198,23 +198,23 @@
<string name="commited">Committed</string>
<string name="downloaded">Downloaded</string>
<string name="followed">Followed</string>
<string name="created_gist">Created Gist</string>
<string name="gollum">Wiki created</string>
<string name="commented_on_issue">Commented on Issue</string>
<string name="created_issue">Issue Event</string>
<string name="created_gist">Gist</string>
<string name="gollum">Wiki</string>
<string name="commented_on_issue">Comment on Issue</string>
<string name="created_issue">Issue</string>
<string name="member">Member</string>
<string name="public_event">Open sourced</string>
<string name="pr_comment_review">PR comment preview</string>
<string name="pr_comment_review">PR comment</string>
<string name="pushed">Pushed</string>
<string name="team_event">Team Event</string>
<string name="team_event">Team</string>
<string name="deleted">Deleted</string>
<string name="unknown">Unknown</string>
<string name="commented_on_commit">Commented on commit</string>
<string name="organization_event">Organization Event</string>
<string name="card_event">Card event</string>
<string name="project_event">Project event</string>
<string name="pr_review_event">PR preview</string>
<string name="repo_event">Repo Event</string>
<string name="commented_on_commit">Comment on commit</string>
<string name="organization_event">Organization</string>
<string name="card_event">Card</string>
<string name="project_event">Project</string>
<string name="pr_review_event">PR</string>
<string name="repo_event">Repo</string>
<string name="switch_branch">Switch Branch</string>
<string name="assignees">Assignees</string>
<string name="edit">Edit</string>

View File

@ -102,4 +102,11 @@
<item name="android:windowContentTransitions">true</item>
</style>
<style name="LoginTheme" parent="Theme.AppCompat.Light.NoActionBar">
<item name="colorPrimary">@color/material_indigo_700</item>
<item name="colorPrimaryDark">@color/material_indigo_900</item>
<item name="colorAccent">@color/accent</item>
<item name="colorControlNormal">?colorAccent</item>
</style>
</resources>