this commit fixed #174 and add optional login via browser for those who don’t trust FastHub login.

This commit is contained in:
Kosh 2017-03-19 20:38:08 +08:00
parent 23c4745183
commit 247a803e68
16 changed files with 159 additions and 23 deletions

View File

@ -14,9 +14,11 @@ import lombok.Setter;
@Getter @Setter @NoArgsConstructor
public class AccessTokenModel implements Parcelable {
long id;
private long id;
private String token;
private String hashedToken;
private String accessToken;
private String tokenType;
@Override public int describeContents() { return 0; }
@ -24,15 +26,19 @@ public class AccessTokenModel implements Parcelable {
dest.writeLong(this.id);
dest.writeString(this.token);
dest.writeString(this.hashedToken);
dest.writeString(this.accessToken);
dest.writeString(this.tokenType);
}
protected AccessTokenModel(Parcel in) {
this.id = in.readLong();
this.token = in.readString();
this.hashedToken = in.readString();
this.accessToken = in.readString();
this.tokenType = in.readString();
}
public static final Parcelable.Creator<AccessTokenModel> CREATOR = new Parcelable.Creator<AccessTokenModel>() {
public static final Creator<AccessTokenModel> CREATOR = new Creator<AccessTokenModel>() {
@Override public AccessTokenModel createFromParcel(Parcel source) {return new AccessTokenModel(source);}
@Override public AccessTokenModel[] newArray(int size) {return new AccessTokenModel[size];}

View File

@ -3,6 +3,7 @@ package com.fastaccess.data.dao;
import android.os.Parcel;
import android.os.Parcelable;
import com.fastaccess.data.dao.model.Comment;
import com.fastaccess.data.dao.model.Issue;
import com.fastaccess.data.dao.model.PullRequest;
import com.fastaccess.data.dao.model.Repo;
@ -26,6 +27,7 @@ public class PayloadModel implements Parcelable {
private PullRequest pullRequest;
private String ref;
private String refType;
private Comment comment;
@Override public int describeContents() { return 0; }

View File

@ -6,10 +6,21 @@ import com.fastaccess.data.dao.AccessTokenModel;
import com.fastaccess.data.dao.AuthModel;
import retrofit2.http.Body;
import retrofit2.http.Field;
import retrofit2.http.FormUrlEncoded;
import retrofit2.http.Headers;
import retrofit2.http.POST;
import rx.Observable;
public interface LoginRestService {
@POST("authorizations") Observable<AccessTokenModel> login(@NonNull @Body AuthModel authModel);
@FormUrlEncoded @POST("access_token")
@Headers("Accept: application/json")
Observable<AccessTokenModel> getAccessToken(@NonNull @Field("code") String code,
@NonNull @Field("client_id") String clientId,
@NonNull @Field("client_secret") String clientSecret,
@NonNull @Field("state") String state,
@NonNull @Field("redirect_uri") String redirectUrl);
}

View File

@ -31,7 +31,7 @@ public class LoginProvider {
.setPrettyPrinting()
.create();
private static OkHttpClient provideOkHttpClient(@NonNull String authToken, @Nullable String otp) {
private static OkHttpClient provideOkHttpClient(@Nullable String authToken, @Nullable String otp) {
OkHttpClient.Builder client = new OkHttpClient.Builder();
if (BuildConfig.DEBUG) {
client.addInterceptor(new HttpLoggingInterceptor()
@ -41,7 +41,7 @@ public class LoginProvider {
return client.build();
}
private static Retrofit provideRetrofit(@NonNull String authToken, @Nullable String otp) {
private static Retrofit provideRetrofit(@Nullable String authToken, @Nullable String otp) {
return new Retrofit.Builder()
.baseUrl(BuildConfig.REST_URL)
.client(provideOkHttpClient(authToken, otp))
@ -50,6 +50,16 @@ public class LoginProvider {
.build();
}
public static LoginRestService getLoginRestService() {
return new Retrofit.Builder()
.baseUrl("https://github.com/login/oauth/")
.client(provideOkHttpClient(null, null))
.addConverterFactory(new GithubResponseConverter(gson))
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.build()
.create(LoginRestService.class);
}
@NonNull public static LoginRestService getLoginRestService(@NonNull String authToken, @Nullable String otp) {
return provideRetrofit(authToken, otp).create(LoginRestService.class);
}

View File

@ -17,7 +17,10 @@ public class AuthenticationInterceptor implements Interceptor {
@Override public Response intercept(Chain chain) throws IOException {
Request original = chain.request();
Request.Builder builder = original.newBuilder().header("Authorization", authToken);
Request.Builder builder = original.newBuilder();
if (!InputHelper.isEmpty(authToken)) {
builder.header("Authorization", authToken);
}
if (!InputHelper.isEmpty(otp)) {
builder.addHeader("X-GitHub-OTP", otp.trim());
}

View File

@ -8,6 +8,7 @@ import android.view.View;
import com.annimon.stream.Collectors;
import com.annimon.stream.Stream;
import com.fastaccess.data.dao.NameParser;
import com.fastaccess.data.dao.PayloadModel;
import com.fastaccess.data.dao.SimpleUrlsModel;
import com.fastaccess.data.dao.model.Event;
import com.fastaccess.data.dao.model.Login;
@ -105,13 +106,18 @@ class FeedsPresenter extends BasePresenter<FeedsMvp.View> implements FeedsMvp.Pr
NameParser parser = new NameParser(item.getPayload().getForkee().getHtmlUrl());
RepoPagerView.startRepoPager(v.getContext(), parser);
} else {
if (item.getPayload() != null && item.getPayload().getIssue() != null) {
SchemeParser.launchUri(v.getContext(), Uri.parse(item.getPayload().getIssue().getHtmlUrl()), true);
} else if (item.getPayload() != null && item.getPayload().getPullRequest() != null) {
SchemeParser.launchUri(v.getContext(), Uri.parse(item.getPayload().getPullRequest().getHtmlUrl()), true);
} else {
Repo repoModel = item.getRepo();
if (item.getRepo() != null) SchemeParser.launchUri(v.getContext(), Uri.parse(repoModel.getName()), true);
PayloadModel payloadModel = item.getPayload();
if (payloadModel != null) {
if (item.getPayload().getIssue() != null) {
SchemeParser.launchUri(v.getContext(), Uri.parse(item.getPayload().getIssue().getHtmlUrl()), true);
} else if (item.getPayload().getPullRequest() != null) {
SchemeParser.launchUri(v.getContext(), Uri.parse(item.getPayload().getPullRequest().getHtmlUrl()), true);
} else if (item.getPayload().getComment() != null) {
SchemeParser.launchUri(v.getContext(), Uri.parse(item.getPayload().getComment().getHtmlUrl()), true);
} else {
Repo repoModel = item.getRepo();
if (item.getRepo() != null) SchemeParser.launchUri(v.getContext(), Uri.parse(repoModel.getName()), true);
}
}
}
}

View File

@ -1,5 +1,7 @@
package com.fastaccess.ui.modules.login;
import android.content.Intent;
import android.net.Uri;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
@ -26,6 +28,10 @@ interface LoginMvp {
interface Presenter extends BaseMvp.FAPresenter {
@NonNull Uri getAuthorizationUrl();
void onHandleAuthIntent(@Nullable Intent intent);
void onTokenResponse(@Nullable AccessTokenModel response);
void onUserResponse(@Nullable Login response);

View File

@ -1,5 +1,7 @@
package com.fastaccess.ui.modules.login;
import android.content.Intent;
import android.net.Uri;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
@ -9,6 +11,7 @@ import com.fastaccess.data.dao.AccessTokenModel;
import com.fastaccess.data.dao.AuthModel;
import com.fastaccess.data.dao.model.Login;
import com.fastaccess.helper.InputHelper;
import com.fastaccess.helper.Logger;
import com.fastaccess.helper.PrefGetter;
import com.fastaccess.provider.rest.LoginProvider;
import com.fastaccess.provider.rest.RestProvider;
@ -44,7 +47,7 @@ class LoginPresenter extends BasePresenter<LoginMvp.View> implements LoginMvp.Pr
@Override public void onTokenResponse(@Nullable AccessTokenModel modelResponse) {
if (modelResponse != null) {
String token = modelResponse.getToken();
String token = modelResponse.getToken() != null ? modelResponse.getToken() : modelResponse.getAccessToken();
if (!InputHelper.isEmpty(token)) {
PrefGetter.setToken(token);
makeRestCall(RestProvider.getUserService().getUser(), this::onUserResponse);
@ -54,6 +57,37 @@ class LoginPresenter extends BasePresenter<LoginMvp.View> implements LoginMvp.Pr
sendToView(view -> view.showMessage(R.string.error, R.string.failed_login));
}
@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 onHandleAuthIntent(@Nullable Intent intent) {
Logger.e(intent);
if (intent != null && intent.getData() != null) {
Uri uri = intent.getData();
Logger.e(uri.toString());
if (uri.toString().startsWith(BuildConfig.REDIRECT_URL)) {
String tokenCode = uri.getQueryParameter("code");
if (!InputHelper.isEmpty(tokenCode)) {
makeRestCall(LoginProvider.getLoginRestService().getAccessToken(tokenCode, BuildConfig.GITHUB_CLIENT_ID,
BuildConfig.GITHUB_SECRET, BuildConfig.APPLICATION_ID, BuildConfig.REDIRECT_URL), this::onTokenResponse);
} else {
sendToView(view -> view.showMessage(R.string.error, R.string.error));
}
}
}
}
@Override public void onUserResponse(@Nullable Login userModel) {
if (userModel != null) {
userModel.setToken(PrefGetter.getToken());

View File

@ -1,6 +1,7 @@
package com.fastaccess.ui.modules.login;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.StringRes;
@ -11,6 +12,7 @@ import android.view.View;
import android.widget.ProgressBar;
import com.fastaccess.R;
import com.fastaccess.helper.ActivityHelper;
import com.fastaccess.helper.AnimHelper;
import com.fastaccess.helper.InputHelper;
import com.fastaccess.ui.base.BaseActivity;
@ -34,6 +36,11 @@ public class LoginView extends BaseActivity<LoginMvp.View, LoginPresenter> imple
@BindView(R.id.login) FloatingActionButton login;
@BindView(R.id.progress) ProgressBar progress;
@OnClick(R.id.browserLogin) void onOpenBrowser() {
Uri uri = getPresenter().getAuthorizationUrl();
ActivityHelper.forceOpenInBrowser(this, uri);
}
@OnClick(R.id.login) public void onClick() {
getPresenter().login(InputHelper.toString(username),
InputHelper.toString(password), InputHelper.toString(twoFactor));
@ -75,8 +82,10 @@ public class LoginView extends BaseActivity<LoginMvp.View, LoginPresenter> imple
@Override public void onSuccessfullyLoggedIn() {
hideProgress();
startActivity(new Intent(this, MainView.class));
finish();
Intent intent = new Intent(this, MainView.class);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
finishAffinity();
}
@Override protected void onCreate(Bundle savedInstanceState) {
@ -107,4 +116,16 @@ public class LoginView extends BaseActivity<LoginMvp.View, LoginPresenter> imple
progress.setVisibility(View.GONE);
login.show();
}
@Override protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
getPresenter().onHandleAuthIntent(intent);
setIntent(null);
}
@Override protected void onResume() {
super.onResume();
getPresenter().onHandleAuthIntent(getIntent());
setIntent(null);
}
}

View File

@ -15,6 +15,7 @@ import android.view.View;
import com.fastaccess.R;
import com.fastaccess.data.dao.FragmentPagerAdapterModel;
import com.fastaccess.data.dao.NameParser;
import com.fastaccess.data.dao.model.Commit;
import com.fastaccess.helper.ActivityHelper;
import com.fastaccess.helper.BundleConstant;
@ -25,6 +26,7 @@ import com.fastaccess.helper.ViewHelper;
import com.fastaccess.provider.scheme.SchemeParser;
import com.fastaccess.ui.adapter.FragmentsPagerAdapter;
import com.fastaccess.ui.base.BaseActivity;
import com.fastaccess.ui.modules.repos.RepoPagerView;
import com.fastaccess.ui.modules.repos.code.commit.details.comments.CommitCommentsView;
import com.fastaccess.ui.widgets.AvatarLayout;
import com.fastaccess.ui.widgets.FontTextView;
@ -127,6 +129,13 @@ public class CommitPagerView extends BaseActivity<CommitPagerMvp.View, CommitPag
if (item.getItemId() == R.id.share) {
if (getPresenter().getCommit() != null) ActivityHelper.shareUrl(this, getPresenter().getCommit().getHtmlUrl());
return true;
} else if (item.getItemId() == R.id.toRepo) {
NameParser nameParser = new NameParser("");
nameParser.setName(getPresenter().getRepoId());
nameParser.setUsername(getPresenter().getLogin());
RepoPagerView.startRepoPager(this, nameParser);
finish();
return true;
}
return super.onOptionsItemSelected(item);
}

View File

@ -74,6 +74,11 @@ class RepoContributorsPresenter extends BasePresenter<RepoContributorsMvp.View>
}
}
@Override public void onError(@NonNull Throwable throwable) {
onWorkOffline();
super.onError(throwable);
}
@Override public void onWorkOffline() {
if (users.isEmpty()) {
manageSubscription(RxHelper.getObserver(User.getUserContributorList(repoId))

View File

@ -30,7 +30,6 @@ public class RepoClosedIssuesView extends BaseFragment<RepoIssuesMvp.View, RepoI
@BindView(R.id.stateLayout) StateLayout stateLayout;
private OnLoadMore<IssueState> onLoadMore;
private IssuesAdapter adapter;
private final IssueState issueState = IssueState.closed;
public static RepoClosedIssuesView newInstance(@NonNull String repoId, @NonNull String login) {
RepoClosedIssuesView view = new RepoClosedIssuesView();
@ -64,7 +63,7 @@ public class RepoClosedIssuesView extends BaseFragment<RepoIssuesMvp.View, RepoI
recycler.setAdapter(adapter);
recycler.addOnScrollListener(getLoadMore());
if (savedInstanceState == null) {
getPresenter().onFragmentCreated(getArguments(), issueState);
getPresenter().onFragmentCreated(getArguments(), IssueState.closed);
} else if (getPresenter().getIssues().isEmpty() && !getPresenter().isApiCalled()) {
onRefresh();
}
@ -93,7 +92,7 @@ public class RepoClosedIssuesView extends BaseFragment<RepoIssuesMvp.View, RepoI
if (onLoadMore == null) {
onLoadMore = new OnLoadMore<>(getPresenter());
}
onLoadMore.setParameter(issueState);
onLoadMore.setParameter(IssueState.closed);
return onLoadMore;
}

View File

@ -30,7 +30,7 @@ interface RepoIssuesMvp {
BaseViewHolder.OnItemClickListener<Issue>,
BaseMvp.PaginationListener<IssueState> {
void onFragmentCreated(@NonNull Bundle bundle, IssueState issueState);
void onFragmentCreated(@NonNull Bundle bundle, @NonNull IssueState issueState);
void onWorkOffline();

View File

@ -35,7 +35,6 @@ public class RepoOpenedIssuesView extends BaseFragment<RepoIssuesMvp.View, RepoI
@BindView(R.id.stateLayout) StateLayout stateLayout;
private OnLoadMore<IssueState> onLoadMore;
private IssuesAdapter adapter;
private final IssueState issueState = IssueState.open;
private RepoIssuesPagerMvp.View pagerCallback;
public static RepoOpenedIssuesView newInstance(@NonNull String repoId, @NonNull String login) {
@ -84,7 +83,7 @@ public class RepoOpenedIssuesView extends BaseFragment<RepoIssuesMvp.View, RepoI
recycler.setAdapter(adapter);
recycler.addOnScrollListener(getLoadMore());
if (savedInstanceState == null) {
getPresenter().onFragmentCreated(getArguments(), issueState);
getPresenter().onFragmentCreated(getArguments(), IssueState.open);
} else if (getPresenter().getIssues().isEmpty() && !getPresenter().isApiCalled()) {
onRefresh();
}
@ -122,7 +121,7 @@ public class RepoOpenedIssuesView extends BaseFragment<RepoIssuesMvp.View, RepoI
if (onLoadMore == null) {
onLoadMore = new OnLoadMore<>(getPresenter());
}
onLoadMore.setParameter(issueState);
onLoadMore.setParameter(IssueState.open);
return onLoadMore;
}

View File

@ -125,6 +125,31 @@
android:layout_margin="@dimen/fab_margin"
android:visibility="gone"/>
</FrameLayout>
<com.fastaccess.ui.widgets.FontTextView
style="@style/TextAppearance.AppCompat.Small"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingBottom="@dimen/spacing_normal"
android:background="@drawable/bottom_border"
android:gravity="center"
android:text="@string/or_character"/>
<com.fastaccess.ui.widgets.FontTextView
style="@style/TextAppearance.AppCompat.Small"
android:layout_width="match_parent"
android:layout_marginTop="@dimen/spacing_normal"
android:layout_height="wrap_content"
android:gravity="center"
android:text="@string/login_using_your_default_browser"/>
<com.fastaccess.ui.widgets.FontButton
android:id="@+id/browserLogin"
style="@style/Widget.AppCompat.Button.Borderless.Colored"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/open_in_browser"/>
</LinearLayout>
</LinearLayout>

@ -1 +1 @@
Subproject commit c1d32068b64305248f05fb67785b013ccd2ef641
Subproject commit c3f8d88974b74c91665bbf7efaa0dcadac498d44