- URL cleanup

- WIP: Login activity
This commit is contained in:
Stefan Schueller 2018-11-13 22:16:30 +01:00
parent f8cfd7ea8c
commit c0dd67167a
10 changed files with 183 additions and 271 deletions

View File

@ -59,7 +59,6 @@
android:enabled="true" android:enabled="true"
android:exported="false" /> android:exported="false" />
<activity android:name=".activity.SelectServerActivity"></activity> <activity android:name=".activity.SelectServerActivity"/>
</application> </application>
</manifest> </manifest>

View File

@ -1,57 +1,43 @@
package net.schueller.peertube.activity; package net.schueller.peertube.activity;
import android.animation.Animator; import android.os.StrictMode;
import android.animation.AnimatorListenerAdapter;
import android.annotation.TargetApi;
import android.content.pm.PackageManager;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.support.design.widget.Snackbar;
import android.support.v7.app.AppCompatActivity; import android.support.v7.app.AppCompatActivity;
import android.app.LoaderManager.LoaderCallbacks;
import android.content.CursorLoader;
import android.content.Loader;
import android.database.Cursor;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.provider.ContactsContract; import android.util.Log;
import android.text.TextUtils;
import android.view.KeyEvent;
import android.view.View; import android.view.View;
import android.view.View.OnClickListener;
import android.view.inputmethod.EditorInfo;
import android.widget.ArrayAdapter;
import android.widget.AutoCompleteTextView; import android.widget.AutoCompleteTextView;
import android.widget.Button; import android.widget.Button;
import android.widget.EditText; import android.widget.EditText;
import android.widget.TextView; import android.widget.Toast;
import java.util.ArrayList;
import java.util.List;
import net.schueller.peertube.R; import net.schueller.peertube.R;
import net.schueller.peertube.helper.APIUrlHelper;
import net.schueller.peertube.model.OauthClient;
import net.schueller.peertube.model.Token;
import net.schueller.peertube.model.VideoList;
import net.schueller.peertube.network.AuthenticationService;
import net.schueller.peertube.network.GetVideoDataService;
import net.schueller.peertube.network.RetrofitInstance;
import static android.Manifest.permission.READ_CONTACTS; import java.io.IOException;
/** import okhttp3.FormBody;
* A login screen that offers login via email/password. import okhttp3.MediaType;
*/ import okhttp3.OkHttpClient;
public class LoginActivity extends AppCompatActivity implements LoaderCallbacks<Cursor> { import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import retrofit2.Call;
import retrofit2.Callback;
public class LoginActivity extends AppCompatActivity {
OkHttpClient client = new OkHttpClient();
public static final MediaType JSON = MediaType.parse("application/json; charset=utf-8");
private String TAG = "LoginActivity";
/**
* A dummy authentication store containing known user names and passwords.
* TODO: remove after connecting to a real authentication system.
*/
private static final String[] DUMMY_CREDENTIALS = new String[]{
"foo@example.com:hello", "bar@example.com:world"
};
/**
* Keep track of the login task to ensure we can cancel it if requested.
*/
private UserLoginTask mAuthTask = null;
// UI references. // UI references.
private AutoCompleteTextView mEmailView; private AutoCompleteTextView mEmailView;
@ -63,43 +49,25 @@ public class LoginActivity extends AppCompatActivity implements LoaderCallbacks<
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login); setContentView(R.layout.activity_login);
// Set up the login form.
mEmailView = (AutoCompleteTextView) findViewById(R.id.email);
mPasswordView = (EditText) findViewById(R.id.password); // bind button click
mPasswordView.setOnEditorActionListener(new TextView.OnEditorActionListener() { Button mEmailSignInButton = findViewById(R.id.email_sign_in_button);
@Override mEmailSignInButton.setOnClickListener(view -> attemptLogin());
public boolean onEditorAction(TextView textView, int id, KeyEvent keyEvent) {
if (id == EditorInfo.IME_ACTION_DONE || id == EditorInfo.IME_NULL) {
attemptLogin();
return true;
}
return false;
}
});
Button mEmailSignInButton = (Button) findViewById(R.id.email_sign_in_button); mEmailView = findViewById(R.id.email);
mEmailSignInButton.setOnClickListener(new OnClickListener() { mPasswordView = findViewById(R.id.password);
@Override
public void onClick(View view) { // if (android.os.Build.VERSION.SDK_INT > 9) {
attemptLogin(); // StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder().permitAll().build();
} // StrictMode.setThreadPolicy(policy);
}); // }
mLoginFormView = findViewById(R.id.login_form);
mProgressView = findViewById(R.id.login_progress);
} }
/**
* Attempts to sign in or register the account specified by the login form.
* If there are form errors (invalid email, missing fields, etc.), the
* errors are presented and no actual login attempt is made.
*/
private void attemptLogin() { private void attemptLogin() {
if (mAuthTask != null) {
return;
}
// Reset errors. // Reset errors.
mEmailView.setError(null); mEmailView.setError(null);
@ -109,195 +77,60 @@ public class LoginActivity extends AppCompatActivity implements LoaderCallbacks<
String email = mEmailView.getText().toString(); String email = mEmailView.getText().toString();
String password = mPasswordView.getText().toString(); String password = mPasswordView.getText().toString();
boolean cancel = false; // make http call to login and save access tokens
View focusView = null;
// Check for a valid password, if the user entered one. String apiBaseURL = APIUrlHelper.getUrlWithVersion(this);
if (!TextUtils.isEmpty(password) && !isPasswordValid(password)) {
mPasswordView.setError(getString(R.string.error_invalid_password));
focusView = mPasswordView;
cancel = true;
}
// Check for a valid email address. AuthenticationService service = RetrofitInstance.getRetrofitInstance(apiBaseURL).create(AuthenticationService.class);
if (TextUtils.isEmpty(email)) {
mEmailView.setError(getString(R.string.error_field_required));
focusView = mEmailView;
cancel = true;
} else if (!isEmailValid(email)) {
mEmailView.setError(getString(R.string.error_invalid_email));
focusView = mEmailView;
cancel = true;
}
if (cancel) { Call<OauthClient> call = service.getOauthClientLocal();
// There was an error; don't attempt login and focus the first call.enqueue(new Callback<OauthClient>() {
// form field with an error. @Override
focusView.requestFocus(); public void onResponse(@NonNull Call<OauthClient> call, @NonNull retrofit2.Response<OauthClient> response) {
} else {
// Show a progress spinner, and kick off a background task to
// perform the user login attempt.
showProgress(true);
mAuthTask = new UserLoginTask(email, password);
mAuthTask.execute((Void) null);
}
}
private boolean isEmailValid(String email) { if (response.body() != null) {
//TODO: Replace this with your own logic
return email.contains("@");
}
private boolean isPasswordValid(String password) { Call<Token> call2 = service.getAuthenticationToken(
//TODO: Replace this with your own logic response.body().getClientId(),
return password.length() > 4; response.body().getClientSecret(),
} "code",
"password",
"upload",
email,
password
);
call2.enqueue(new Callback<Token>() {
@Override
public void onResponse(@NonNull Call<Token> call2, @NonNull retrofit2.Response<Token> response2) {
/** if (response2.body() != null) {
* Shows the progress UI and hides the login form. Log.wtf(TAG, response2.body().getAccessToken());
*/ Log.wtf(TAG, response2.body().getExpiresIn());
@TargetApi(Build.VERSION_CODES.HONEYCOMB_MR2) Log.wtf(TAG, response2.body().getRefreshToken());
private void showProgress(final boolean show) { Log.wtf(TAG, response2.body().getTokenType());
// On Honeycomb MR2 we have the ViewPropertyAnimator APIs, which allow } else {
// for very easy animations. If available, use these APIs to fade-in Log.wtf(TAG, response2.toString());
// the progress spinner. }
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR2) { }
int shortAnimTime = getResources().getInteger(android.R.integer.config_shortAnimTime);
mLoginFormView.setVisibility(show ? View.GONE : View.VISIBLE); @Override
mLoginFormView.animate().setDuration(shortAnimTime).alpha( public void onFailure(@NonNull Call<Token> call2, @NonNull Throwable t2) {
show ? 0 : 1).setListener(new AnimatorListenerAdapter() { Log.wtf("err", t2.fillInStackTrace());
@Override }
public void onAnimationEnd(Animator animation) { });
mLoginFormView.setVisibility(show ? View.GONE : View.VISIBLE);
}
});
mProgressView.setVisibility(show ? View.VISIBLE : View.GONE); } else {
mProgressView.animate().setDuration(shortAnimTime).alpha( Log.wtf(TAG, response.toString());
show ? 1 : 0).setListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
mProgressView.setVisibility(show ? View.VISIBLE : View.GONE);
}
});
} else {
// The ViewPropertyAnimator APIs are not available, so simply show
// and hide the relevant UI components.
mProgressView.setVisibility(show ? View.VISIBLE : View.GONE);
mLoginFormView.setVisibility(show ? View.GONE : View.VISIBLE);
}
}
@Override
public Loader<Cursor> onCreateLoader(int i, Bundle bundle) {
return new CursorLoader(this,
// Retrieve data rows for the device user's 'profile' contact.
Uri.withAppendedPath(ContactsContract.Profile.CONTENT_URI,
ContactsContract.Contacts.Data.CONTENT_DIRECTORY), ProfileQuery.PROJECTION,
// Select only email addresses.
ContactsContract.Contacts.Data.MIMETYPE +
" = ?", new String[]{ContactsContract.CommonDataKinds.Email
.CONTENT_ITEM_TYPE},
// Show primary email addresses first. Note that there won't be
// a primary email address if the user hasn't specified one.
ContactsContract.Contacts.Data.IS_PRIMARY + " DESC");
}
@Override
public void onLoadFinished(Loader<Cursor> cursorLoader, Cursor cursor) {
List<String> emails = new ArrayList<>();
cursor.moveToFirst();
while (!cursor.isAfterLast()) {
emails.add(cursor.getString(ProfileQuery.ADDRESS));
cursor.moveToNext();
}
addEmailsToAutoComplete(emails);
}
@Override
public void onLoaderReset(Loader<Cursor> cursorLoader) {
}
private void addEmailsToAutoComplete(List<String> emailAddressCollection) {
//Create adapter to tell the AutoCompleteTextView what to show in its dropdown list.
ArrayAdapter<String> adapter =
new ArrayAdapter<>(LoginActivity.this,
android.R.layout.simple_dropdown_item_1line, emailAddressCollection);
mEmailView.setAdapter(adapter);
}
private interface ProfileQuery {
String[] PROJECTION = {
ContactsContract.CommonDataKinds.Email.ADDRESS,
ContactsContract.CommonDataKinds.Email.IS_PRIMARY,
};
int ADDRESS = 0;
int IS_PRIMARY = 1;
}
/**
* Represents an asynchronous login/registration task used to authenticate
* the user.
*/
public class UserLoginTask extends AsyncTask<Void, Void, Boolean> {
private final String mEmail;
private final String mPassword;
UserLoginTask(String email, String password) {
mEmail = email;
mPassword = password;
}
@Override
protected Boolean doInBackground(Void... params) {
// TODO: attempt authentication against a network service.
try {
// Simulate network access.
Thread.sleep(2000);
} catch (InterruptedException e) {
return false;
}
for (String credential : DUMMY_CREDENTIALS) {
String[] pieces = credential.split(":");
if (pieces[0].equals(mEmail)) {
// Account exists, return true if the password matches.
return pieces[1].equals(mPassword);
} }
} }
// TODO: register the new account here. @Override
return true; public void onFailure(@NonNull Call<OauthClient> call, @NonNull Throwable t) {
} Log.wtf("err", t.fillInStackTrace());
@Override
protected void onPostExecute(final Boolean success) {
mAuthTask = null;
showProgress(false);
if (success) {
finish();
} else {
mPasswordView.setError(getString(R.string.error_incorrect_password));
mPasswordView.requestFocus();
} }
} });
@Override
protected void onCancelled() {
mAuthTask = null;
showProgress(false);
}
} }
} }

View File

@ -117,9 +117,9 @@ public class SearchActivity extends AppCompatActivity {
SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(this); SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(this);
String nsfw = sharedPref.getBoolean("pref_show_nsfw", false) ? "both" : "false"; String nsfw = sharedPref.getBoolean("pref_show_nsfw", false) ? "both" : "false";
String apiBaseURL = APIUrlHelper.getUrl(this); String apiBaseURL = APIUrlHelper.getUrlWithVersion(this);
GetVideoDataService service = RetrofitInstance.getRetrofitInstance(apiBaseURL + "/api/v1/").create(GetVideoDataService.class); GetVideoDataService service = RetrofitInstance.getRetrofitInstance(apiBaseURL).create(GetVideoDataService.class);
Call<VideoList> call = service.searchVideosData(start, count, sort, nsfw, search); Call<VideoList> call = service.searchVideosData(start, count, sort, nsfw, search);

View File

@ -242,9 +242,9 @@ public class VideoListActivity extends AppCompatActivity {
SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(this); SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(this);
String nsfw = sharedPref.getBoolean("pref_show_nsfw", false) ? "both" : "false"; String nsfw = sharedPref.getBoolean("pref_show_nsfw", false) ? "both" : "false";
String apiBaseURL = APIUrlHelper.getUrl(this); String apiBaseURL = APIUrlHelper.getUrlWithVersion(this);
GetVideoDataService service = RetrofitInstance.getRetrofitInstance(apiBaseURL + "/api/v1/").create(GetVideoDataService.class); GetVideoDataService service = RetrofitInstance.getRetrofitInstance(apiBaseURL).create(GetVideoDataService.class);
Call<VideoList> call = service.getVideosData(start, count, sort, nsfw); Call<VideoList> call = service.getVideosData(start, count, sort, nsfw);

View File

@ -92,8 +92,8 @@ public class VideoPlayActivity extends AppCompatActivity implements VideoRendere
simpleExoPlayerView.setPlayer(player); simpleExoPlayerView.setPlayer(player);
// get video details from api // get video details from api
String apiBaseURL = APIUrlHelper.getUrl(this); String apiBaseURL = APIUrlHelper.getUrlWithVersion(this);
GetVideoDataService service = RetrofitInstance.getRetrofitInstance(apiBaseURL + "/api/v1/").create(GetVideoDataService.class); GetVideoDataService service = RetrofitInstance.getRetrofitInstance(apiBaseURL).create(GetVideoDataService.class);
Call<Video> call = service.getVideoData(videoID); Call<Video> call = service.getVideoData(videoID);

View File

@ -12,4 +12,8 @@ public class APIUrlHelper{
SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(context); SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(context);
return sharedPref.getString("pref_api_base", context.getResources().getString(R.string.pref_default_api_base_url)); return sharedPref.getString("pref_api_base", context.getResources().getString(R.string.pref_default_api_base_url));
} }
public static String getUrlWithVersion(Context context) {
return APIUrlHelper.getUrl(context) + "/api/v1/";
}
} }

View File

@ -0,0 +1,23 @@
package net.schueller.peertube.model;
public class OauthClient {
private String clientId;
private String clientSecret;
public String getClientId() {
return clientId;
}
public void setClientId(String clientId) {
this.clientId = clientId;
}
public String getClientSecret() {
return clientSecret;
}
public void setClientSecret(String clientSecret) {
this.clientSecret = clientSecret;
}
}

View File

@ -0,0 +1,41 @@
package net.schueller.peertube.model;
public class Token {
private String accessToken;
private String expiresIn;
private String refreshToken;
private String tokenType;
public String getAccessToken() {
return accessToken;
}
public void setAccessToken(String accessToken) {
this.accessToken = accessToken;
}
public String getExpiresIn() {
return expiresIn;
}
public void setExpiresIn(String expiresIn) {
this.expiresIn = expiresIn;
}
public String getRefreshToken() {
return refreshToken;
}
public void setRefreshToken(String refreshToken) {
this.refreshToken = refreshToken;
}
public String getTokenType() {
return tokenType;
}
public void setTokenType(String tokenType) {
this.tokenType = tokenType;
}
}

View File

@ -0,0 +1,28 @@
package net.schueller.peertube.network;
import net.schueller.peertube.model.OauthClient;
import net.schueller.peertube.model.Token;
import retrofit2.Call;
import retrofit2.http.Field;
import retrofit2.http.FormUrlEncoded;
import retrofit2.http.GET;
import retrofit2.http.POST;
public interface AuthenticationService {
@GET("oauth-clients/local")
Call<OauthClient> getOauthClientLocal();
@FormUrlEncoded
@POST("users/token")
Call<Token> getAuthenticationToken(
@Field("client_id") String clientId,
@Field("client_secret") String clientSecret,
@Field("response_type") String responseType,
@Field("grant_type") String grantType,
@Field("scope") String scope,
@Field("username") String username,
@Field("password") String password
);
}

View File

@ -30,21 +30,6 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="vertical"> android:orientation="vertical">
<android.support.design.widget.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<AutoCompleteTextView
android:id="@+id/server"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/prompt_server"
android:inputType="textUri"
android:maxLines="1"
android:singleLine="true" />
</android.support.design.widget.TextInputLayout>
<android.support.design.widget.TextInputLayout <android.support.design.widget.TextInputLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content"> android:layout_height="wrap_content">
@ -86,7 +71,6 @@
android:layout_marginTop="16dp" android:layout_marginTop="16dp"
android:text="@string/action_sign_in" android:text="@string/action_sign_in"
android:textStyle="bold" /> android:textStyle="bold" />
</LinearLayout> </LinearLayout>
</ScrollView> </ScrollView>
</LinearLayout> </LinearLayout>