Merge branch 'improve_login' into develop

This commit is contained in:
tom79 2019-06-15 13:53:00 +02:00
commit 8f80a6188b
16 changed files with 1292 additions and 2 deletions

View File

@ -128,6 +128,11 @@
<data android:pathPattern=".*\\.fedilab" />
</intent-filter>
</activity>
<activity android:name="app.fedilab.android.activities.MastodonRegisterActivity"
android:windowSoftInputMode="stateAlwaysHidden"
android:configChanges="orientation|screenSize"
android:label="@string/app_name"
/>
<activity android:name="app.fedilab.android.activities.LoginActivity"
android:windowSoftInputMode="stateAlwaysHidden"
android:configChanges="orientation|screenSize"

View File

@ -35,8 +35,10 @@ import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.widget.Toolbar;
import android.text.Editable;
import android.text.SpannableString;
import android.text.Spanned;
import android.text.TextWatcher;
import android.text.method.LinkMovementMethod;
import android.text.style.ForegroundColorSpan;
import android.text.style.UnderlineSpan;
import android.text.util.Linkify;
import android.view.LayoutInflater;
@ -62,6 +64,7 @@ import com.jaredrummler.materialspinner.MaterialSpinner;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.w3c.dom.Text;
import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
@ -213,6 +216,25 @@ public class LoginActivity extends BaseActivity {
changeDrawableColor(getApplicationContext(), R.drawable.mastodon_icon, R.color.mastodonC3);
}
TextView create_an_account_message = findViewById(R.id.create_an_account);
SpannableString content_create = new SpannableString(getString(R.string.join_mastodon));
content_create.setSpan(new UnderlineSpan(), 0, content_create.length(), 0);
if( theme == Helper.THEME_DARK)
content_create.setSpan(new ForegroundColorSpan(ContextCompat.getColor(LoginActivity.this, R.color.dark_link_toot)), 0, content_create.length(),
Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
else if( theme == Helper.THEME_BLACK)
content_create.setSpan(new ForegroundColorSpan(ContextCompat.getColor(LoginActivity.this, R.color.black_link_toot)), 0, content_create.length(),
Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
else if( theme == Helper.THEME_LIGHT)
content_create.setSpan(new ForegroundColorSpan(ContextCompat.getColor(LoginActivity.this, R.color.mastodonC4)), 0, content_create.length(),
Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
create_an_account_message.setText(content_create);
create_an_account_message.setOnClickListener(v -> {
Intent mainActivity = new Intent(LoginActivity.this, MastodonRegisterActivity.class);
startActivity(mainActivity);
});
login_instance = findViewById(R.id.login_instance);
login_uid = findViewById(R.id.login_uid);
login_passwd = findViewById(R.id.login_passwd);

View File

@ -0,0 +1,410 @@
/* Copyright 2017 Thomas Schneider
*
* This file is a part of Fedilab
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 3 of the
* License, or (at your option) any later version.
*
* Fedilab is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
* Public License for more details.
*
* You should have received a copy of the GNU General Public License along with Fedilab; if not,
* see <http://www.gnu.org/licenses>. */
package app.fedilab.android.activities;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.AsyncTask;
import android.os.Bundle;
import android.text.Html;
import android.text.SpannableString;
import android.text.Spanned;
import android.text.method.LinkMovementMethod;
import android.text.style.ForegroundColorSpan;
import android.text.style.UnderlineSpan;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.view.inputmethod.InputMethodManager;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.widget.Toolbar;
import androidx.core.content.ContextCompat;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.jaredrummler.materialspinner.MaterialSpinner;
import org.json.JSONObject;
import java.io.IOException;
import java.lang.ref.WeakReference;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.List;
import app.fedilab.android.R;
import app.fedilab.android.asynctasks.CreateMastodonAccountAsyncTask;
import app.fedilab.android.asynctasks.PostActionAsyncTask;
import app.fedilab.android.asynctasks.RetrieveInstanceRegAsyncTask;
import app.fedilab.android.client.API;
import app.fedilab.android.client.APIResponse;
import app.fedilab.android.client.Entities.AccountCreation;
import app.fedilab.android.client.Entities.InstanceReg;
import app.fedilab.android.drawers.InstanceRegAdapter;
import app.fedilab.android.helper.Helper;
import app.fedilab.android.interfaces.OnPostStatusActionInterface;
import app.fedilab.android.interfaces.OnRetrieveInstanceInterface;
import es.dmoral.toasty.Toasty;
import static android.os.AsyncTask.THREAD_POOL_EXECUTOR;
/**
* Created by Thomas on 13/06/2019.
* Register activity class
*/
public class MastodonRegisterActivity extends BaseActivity implements OnRetrieveInstanceInterface, OnPostStatusActionInterface {
private Button signup;
private String instance;
private TextView error_message;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
SharedPreferences sharedpreferences = getSharedPreferences(Helper.APP_PREFS, MODE_PRIVATE);
int theme = sharedpreferences.getInt(Helper.SET_THEME, Helper.THEME_DARK);
switch (theme){
case Helper.THEME_LIGHT:
setTheme(R.style.AppTheme);
break;
case Helper.THEME_DARK:
setTheme(R.style.AppThemeDark);
break;
case Helper.THEME_BLACK:
setTheme(R.style.AppThemeBlack);
break;
default:
setTheme(R.style.AppThemeDark);
}
setContentView(R.layout.activity_register);
ActionBar actionBar = getSupportActionBar();
if( actionBar != null ) {
LayoutInflater inflater = (LayoutInflater) this.getSystemService(LAYOUT_INFLATER_SERVICE);
assert inflater != null;
@SuppressLint("InflateParams") View view = inflater.inflate(R.layout.simple_bar, null);
actionBar.setCustomView(view, new ActionBar.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
actionBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM);
ImageView toolbar_close = actionBar.getCustomView().findViewById(R.id.toolbar_close);
TextView toolbar_title = actionBar.getCustomView().findViewById(R.id.toolbar_title);
toolbar_close.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
finish();
}
});
toolbar_title.setText(R.string.sign_up);
if (theme == Helper.THEME_LIGHT){
Toolbar toolbar = actionBar.getCustomView().findViewById(R.id.toolbar);
Helper.colorizeToolbar(toolbar, R.color.black, MastodonRegisterActivity.this);
}
}
MaterialSpinner reg_category = findViewById(R.id.reg_category);
Helper.changeMaterialSpinnerColor(MastodonRegisterActivity.this, reg_category);
String[] categoriesA = {
getString(R.string.category_general),
getString(R.string.category_regional),
getString(R.string.category_art),
getString(R.string.category_journalism),
getString(R.string.category_activism),
"LGBTQ+",
getString(R.string.category_games),
getString(R.string.category_tech),
getString(R.string.category_adult),
getString(R.string.category_furry),
getString(R.string.category_food)
};
String[] itemA = {
"general",
"regional",
"art",
"journalism",
"activism",
"lgbt",
"games",
"tech",
"adult",
"furry",
"food",
};
ArrayAdapter<String> adcategories = new ArrayAdapter<>(MastodonRegisterActivity.this,
android.R.layout.simple_spinner_dropdown_item, categoriesA);
reg_category.setAdapter(adcategories);
reg_category.setSelectedIndex(0);
//Manage privacies
reg_category.setOnItemSelectedListener(new MaterialSpinner.OnItemSelectedListener<String>() {
@Override
public void onItemSelected(MaterialSpinner view, int position, long id, String item) {
new RetrieveInstanceRegAsyncTask(MastodonRegisterActivity.this, itemA[position], MastodonRegisterActivity.this).executeOnExecutor(THREAD_POOL_EXECUTOR);
}
});
new RetrieveInstanceRegAsyncTask(MastodonRegisterActivity.this, "general", MastodonRegisterActivity.this).executeOnExecutor(THREAD_POOL_EXECUTOR);
signup = findViewById(R.id.signup);
EditText username = findViewById(R.id.username);
EditText email = findViewById(R.id.email);
EditText password = findViewById(R.id.password);
EditText password_confirm = findViewById(R.id.password_confirm);
CheckBox agreement = findViewById(R.id.agreement);
error_message = findViewById(R.id.error_message);
signup.setOnClickListener(view->{
error_message.setVisibility(View.GONE);
if( username.getText().toString().trim().length() == 0 || email.getText().toString().trim().length() == 0 ||
password.getText().toString().trim().length() == 0 || password_confirm.getText().toString().trim().length() == 0 || !agreement.isChecked()){
Toasty.error(MastodonRegisterActivity.this, getString(R.string.all_field_filled)).show();
return;
}
if(!password.getText().toString().trim().equals(password_confirm.getText().toString().trim())){
Toasty.error(MastodonRegisterActivity.this, getString(R.string.password_error)).show();
return;
}
if(!android.util.Patterns.EMAIL_ADDRESS.matcher(email.getText().toString().trim()).matches()){
Toasty.error(MastodonRegisterActivity.this, getString(R.string.email_error)).show();
return;
}
if(password.getText().toString().trim().length() < 8 ){
Toasty.error(MastodonRegisterActivity.this, getString(R.string.password_too_short)).show();
return;
}
if(username.getText().toString().matches("[a-zA-Z0-9_]")){
Toasty.error(MastodonRegisterActivity.this, getString(R.string.username_error)).show();
return;
}
signup.setEnabled(false);
AccountCreation accountCreation = new AccountCreation();
accountCreation.setEmail(email.getText().toString().trim());
accountCreation.setPassword(password.getText().toString().trim());
accountCreation.setPasswordConfirm(password_confirm.getText().toString().trim());
accountCreation.setUsername(username.getText().toString().trim());
new CreateMastodonAccountAsyncTask(MastodonRegisterActivity.this, accountCreation, instance, MastodonRegisterActivity.this).executeOnExecutor(THREAD_POOL_EXECUTOR);
});
}
@Override
protected void onResume(){
super.onResume();
}
@Override
public void onRetrieveInstance(APIResponse apiResponse) {
if( apiResponse.getError() != null ){
Toasty.error(MastodonRegisterActivity.this, getString(R.string.toast_error_instance_reg), Toast.LENGTH_LONG).show();
return;
}
List<InstanceReg> instanceRegs = apiResponse.getInstanceRegs();
RecyclerView lv_instances = findViewById(R.id.reg_category_view);
InstanceRegAdapter instanceRegAdapter = new InstanceRegAdapter(MastodonRegisterActivity.this, instanceRegs);
LinearLayoutManager mLayoutManager = new LinearLayoutManager(MastodonRegisterActivity.this);
lv_instances.setLayoutManager(mLayoutManager);
lv_instances.setNestedScrollingEnabled(false);
lv_instances.setAdapter(instanceRegAdapter);
}
public void pickupInstance(String instance){
checkInstance(MastodonRegisterActivity.this, instance);
LinearLayout form_container = findViewById(R.id.form_container);
LinearLayout drawer_layout = findViewById(R.id.drawer_layout);
TextView host_reg = findViewById(R.id.host_reg);
host_reg.setText(instance);
this.instance = instance;
drawer_layout.animate()
.translationY(0)
.alpha(0.0f)
.setListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
drawer_layout.setVisibility(View.GONE);
form_container.setVisibility(View.VISIBLE);
}
});
TextView change_instance = findViewById(R.id.change_instance);
final SpannableString change = new SpannableString(String.format("(%s)", getString(R.string.change)));
change.setSpan(new UnderlineSpan(), 0, change.length(), 0);
SharedPreferences sharedpreferences = getSharedPreferences(Helper.APP_PREFS, MODE_PRIVATE);
int theme = sharedpreferences.getInt(Helper.SET_THEME, Helper.THEME_DARK);
if( theme == Helper.THEME_DARK)
change.setSpan(new ForegroundColorSpan(ContextCompat.getColor(MastodonRegisterActivity.this, R.color.dark_link_toot)), 0, change.length(),
Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
else if( theme == Helper.THEME_BLACK)
change.setSpan(new ForegroundColorSpan(ContextCompat.getColor(MastodonRegisterActivity.this, R.color.black_link_toot)), 0, change.length(),
Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
else if( theme == Helper.THEME_LIGHT)
change.setSpan(new ForegroundColorSpan(ContextCompat.getColor(MastodonRegisterActivity.this, R.color.mastodonC4)), 0, change.length(),
Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
change_instance.setText(change);
change_instance.setOnClickListener(view -> {
drawer_layout.setVisibility(View.VISIBLE);
drawer_layout.animate()
.translationY(0)
.alpha(1.f)
.setListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
findViewById(R.id.invitation).setVisibility(View.GONE);
form_container.setVisibility(View.GONE);
}
});
});
TextView agreement_text = findViewById(R.id.agreement_text);
TextView username_indicator = findViewById(R.id.username_indicator);
username_indicator.setText(getString(R.string.username_indicator, instance));
String tos = getString(R.string.tos);
String serverrules = getString(R.string.server_rules);
String content_agreement = getString(R.string.agreement_check,
"<a href='https://" + instance + "/about/more' >"+serverrules +"</a>",
"<a href='https://" + instance + "/terms' >"+tos +"</a>"
);
agreement_text.setMovementMethod(LinkMovementMethod.getInstance());
agreement_text.setText(Html.fromHtml(content_agreement));
}
private void checkInstance(Context context, String instance){
new checkRegistration(context, instance).executeOnExecutor(THREAD_POOL_EXECUTOR);
}
@Override
public void onPostStatusAction(APIResponse apiResponse) {
if( apiResponse.getError() != null){
String errorMessage;
if( apiResponse.getError().getError() != null){
try{
String[] resp = apiResponse.getError().getError().split(":");
if( resp.length == 2)
errorMessage = apiResponse.getError().getError().split(":")[1];
else if( resp.length == 3)
errorMessage = apiResponse.getError().getError().split(":")[2];
else
errorMessage = getString(R.string.toast_error);
}catch (Exception e){
errorMessage = getString(R.string.toast_error);
}
}else {
errorMessage = getString(R.string.toast_error);
}
error_message.setText(errorMessage);
error_message.setVisibility(View.VISIBLE);
signup.setEnabled(true);
return;
}
SharedPreferences sharedpreferences = getSharedPreferences(Helper.APP_PREFS, android.content.Context.MODE_PRIVATE);
int theme = sharedpreferences.getInt(Helper.SET_THEME, Helper.THEME_DARK);
int style;
if (theme == Helper.THEME_DARK) {
style = R.style.DialogDark;
} else if (theme == Helper.THEME_BLACK){
style = R.style.DialogBlack;
}else {
style = R.style.Dialog;
}
AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(MastodonRegisterActivity.this, style);
dialogBuilder.setCancelable(false);
dialogBuilder.setPositiveButton(R.string.validate, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog,int which) {
dialog.dismiss();
Intent mainActivity = new Intent(MastodonRegisterActivity.this, MainActivity.class);
mainActivity.putExtra(Helper.INTENT_ACTION, Helper.ADD_USER_INTENT);
startActivity(mainActivity);
finish();
}
});
AlertDialog alertDialog = dialogBuilder.create();
alertDialog.setTitle(getString(R.string.account_created));
alertDialog.setMessage(getString(R.string.account_created_message, this.instance));
alertDialog.show();
}
@SuppressLint("StaticFieldLeak")
private class checkRegistration extends AsyncTask<Void, Void, String> {
private String instance;
private WeakReference<Context> weakReference;
checkRegistration(Context context, String instance){
this.instance = instance;
this.weakReference = new WeakReference<>(context);
}
@Override
protected String doInBackground(Void... params) {
String response = null;
try {
URL url = new URL("https://" + instance + "/auth/sign_up");
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
if (connection.getResponseCode() == HttpURLConnection.HTTP_OK) {
java.util.Scanner s = new java.util.Scanner(connection.getInputStream()).useDelimiter("\\A");
response = s.hasNext() ? s.next() : "";
}
} catch (IOException e) {
e.printStackTrace();
}
return response;
}
@Override
protected void onPostExecute(String result) {
if( result != null && result.contains("invite_request_attributes")){
TextView invitation = ((MastodonRegisterActivity)(weakReference.get())).findViewById(R.id.invitation);
if( invitation != null){
invitation.setVisibility(View.VISIBLE);
}
}
}
}
}

View File

@ -51,7 +51,6 @@ import android.text.Html;
import android.text.InputFilter;
import android.text.InputType;
import android.text.TextWatcher;
import android.util.Log;
import android.util.Patterns;
import android.view.LayoutInflater;
import android.view.Menu;

View File

@ -0,0 +1,60 @@
/* Copyright 2017 Thomas Schneider
*
* This file is a part of Fedilab
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 3 of the
* License, or (at your option) any later version.
*
* Fedilab is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
* Public License for more details.
*
* You should have received a copy of the GNU General Public License along with Fedilab; if not,
* see <http://www.gnu.org/licenses>. */
package app.fedilab.android.asynctasks;
import android.content.Context;
import android.os.AsyncTask;
import java.lang.ref.WeakReference;
import app.fedilab.android.client.API;
import app.fedilab.android.client.APIResponse;
import app.fedilab.android.client.Entities.AccountCreation;
import app.fedilab.android.interfaces.OnPostStatusActionInterface;
/**
* Created by Thomas on 15/06/2019.
* Create a Mastodon account
*/
public class CreateMastodonAccountAsyncTask extends AsyncTask<Void, Void, Void> {
private OnPostStatusActionInterface listener;
private APIResponse apiResponse;
private app.fedilab.android.client.Entities.Status status;
private AccountCreation accountCreation;
private WeakReference<Context> contextReference;
private String instance;
public CreateMastodonAccountAsyncTask(Context context, AccountCreation accountCreation, String instance, OnPostStatusActionInterface onPostStatusActionInterface){
this.contextReference = new WeakReference<>(context);
this.listener = onPostStatusActionInterface;
this.accountCreation = accountCreation;
this.instance = instance;
}
@Override
protected Void doInBackground(Void... params) {
apiResponse = new API(contextReference.get(), instance, null).createAccount(accountCreation);
return null;
}
@Override
protected void onPostExecute(Void result) {
listener.onPostStatusAction(apiResponse);
}
}

View File

@ -0,0 +1,56 @@
/* Copyright 2019 Thomas Schneider
*
* This file is a part of Fedilab
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 3 of the
* License, or (at your option) any later version.
*
* Fedilab is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
* Public License for more details.
*
* You should have received a copy of the GNU General Public License along with Fedilab; if not,
* see <http://www.gnu.org/licenses>. */
package app.fedilab.android.asynctasks;
import android.content.Context;
import android.os.AsyncTask;
import java.lang.ref.WeakReference;
import app.fedilab.android.client.API;
import app.fedilab.android.client.APIResponse;
import app.fedilab.android.interfaces.OnRetrieveInstanceInterface;
/**
* Created by Thomas on 14/06/2019.
* Retrieves instances for registration
*/
public class RetrieveInstanceRegAsyncTask extends AsyncTask<Void, Void, Void> {
private OnRetrieveInstanceInterface listener;
private APIResponse apiResponse;
private WeakReference<Context> contextReference;
private String category;
public RetrieveInstanceRegAsyncTask(Context context, String category, OnRetrieveInstanceInterface onRetrieveInstanceInterface){
this.contextReference = new WeakReference<>(context);
this.listener = onRetrieveInstanceInterface;
this.category = category;
}
@Override
protected Void doInBackground(Void... params) {
apiResponse = new API(this.contextReference.get()).getInstanceReg(category);
return null;
}
@Override
protected void onPostExecute(Void result) {
listener.onRetrieveInstance(apiResponse);
}
}

View File

@ -16,7 +16,6 @@ package app.fedilab.android.asynctasks;
import android.content.Context;
import android.os.AsyncTask;
import android.util.Log;
import java.lang.ref.WeakReference;

View File

@ -19,6 +19,7 @@ import android.content.Intent;
import android.content.SharedPreferences;
import android.database.sqlite.SQLiteDatabase;
import android.os.Bundle;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import com.google.gson.JsonArray;
@ -41,13 +42,16 @@ import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import app.fedilab.android.R;
import app.fedilab.android.activities.LoginActivity;
import app.fedilab.android.activities.MainActivity;
import app.fedilab.android.asynctasks.RetrieveOpenCollectiveAsyncTask;
import app.fedilab.android.asynctasks.UpdateAccountInfoAsyncTask;
import app.fedilab.android.client.Entities.Account;
import app.fedilab.android.client.Entities.AccountCreation;
import app.fedilab.android.client.Entities.Application;
import app.fedilab.android.client.Entities.Attachment;
import app.fedilab.android.client.Entities.Card;
@ -58,6 +62,7 @@ import app.fedilab.android.client.Entities.Filters;
import app.fedilab.android.client.Entities.HowToVideo;
import app.fedilab.android.client.Entities.Instance;
import app.fedilab.android.client.Entities.InstanceNodeInfo;
import app.fedilab.android.client.Entities.InstanceReg;
import app.fedilab.android.client.Entities.InstanceSocial;
import app.fedilab.android.client.Entities.Mention;
import app.fedilab.android.client.Entities.NodeInfo;
@ -346,6 +351,31 @@ public class API {
}
/***
* Get instance for registering an account *synchronously*
* @return APIResponse
*/
public APIResponse getInstanceReg(String category) {
apiResponse = new APIResponse();
try {
String response = new HttpsConnection(context, null).get(String.format("https://api.joinmastodon.org/servers?category=%s", category));
List<InstanceReg> instanceRegs = parseInstanceReg(new JSONArray(response));
apiResponse.setInstanceRegs(instanceRegs);
} catch (HttpsConnection.HttpsConnectionException e) {
setError(e.getStatusCode(), e);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (KeyManagementException e) {
e.printStackTrace();
} catch (JSONException e) {
e.printStackTrace();
}
return apiResponse;
}
/***
* Update credential of the authenticated user *synchronously*
@ -502,6 +532,73 @@ public class API {
return newValues;
}
public APIResponse createAccount(AccountCreation accountCreation){
apiResponse = new APIResponse();
try {
HashMap<String, String> params = new HashMap<>();
params.put(Helper.CLIENT_NAME, Helper.CLIENT_NAME_VALUE);
params.put(Helper.REDIRECT_URIS, Helper.REDIRECT_CONTENT);
params.put(Helper.SCOPES, Helper.OAUTH_SCOPES);
params.put(Helper.WEBSITE, Helper.WEBSITE_VALUE);
String response = new HttpsConnection(context, this.instance).post(getAbsoluteUrl("/apps"), 30, params, null);
JSONObject resobj = new JSONObject(response);
String client_id = resobj.getString("client_id");
String client_secret = resobj.getString("client_secret");
params = new HashMap<>();
params.put("grant_type", "client_credentials");
params.put("client_id", client_id);
params.put("client_secret", client_secret);
params.put("scope", "read write");
response = new HttpsConnection(context, this.instance).post("https://" + this.instance + "/oauth/token", 30, params, null);
JSONObject res = new JSONObject(response);
String app_token = res.getString("access_token");
params = new HashMap<>();
params.put("username", accountCreation.getUsername());
params.put("email", accountCreation.getEmail());
params.put("password", accountCreation.getPassword());
params.put("agreement", "true");
params.put("locale", Locale.getDefault().getLanguage());
response = new HttpsConnection(context, this.instance).post(getAbsoluteUrl("/accounts"), 60, params, app_token);
res = new JSONObject(response);
String access_token = res.getString("access_token");
SharedPreferences sharedpreferences = context.getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE);
account.setToken(access_token);
account.setClient_id(client_id);
account.setClient_secret(client_secret);
account.setRefresh_token(null);
account.setInstance(instance);
SQLiteDatabase db = Sqlite.getInstance(context, Sqlite.DB_NAME, null, Sqlite.DB_VERSION).open();
boolean userExists = new AccountDAO(context, db).userExist(account);
SharedPreferences.Editor editor = sharedpreferences.edit();
editor.putString(Helper.PREF_KEY_ID, account.getId());
editor.putBoolean(Helper.PREF_IS_MODERATOR, account.isModerator());
editor.putBoolean(Helper.PREF_IS_ADMINISTRATOR, account.isAdmin());
editor.putString(Helper.PREF_INSTANCE, instance);
editor.apply();
if( userExists)
new AccountDAO(context, db).updateAccountCredential(account);
else {
if( account.getUsername() != null && account.getCreated_at() != null)
new AccountDAO(context, db).insertAccount(account);
}
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (KeyManagementException e) {
e.printStackTrace();
} catch (HttpsConnection.HttpsConnectionException e) {
setError(e.getStatusCode(), e);
e.printStackTrace();
} catch (JSONException e) {
e.printStackTrace();
}
return apiResponse;
}
/**
* Returns an account
@ -4403,6 +4500,54 @@ public class API {
return instance;
}
/**
* Parse json response for several instance reg
* @param jsonArray JSONArray
* @return List<Status>
*/
public List<InstanceReg> parseInstanceReg(JSONArray jsonArray){
List<InstanceReg> instanceRegs = new ArrayList<>();
try {
int i = 0;
while (i < jsonArray.length() ){
JSONObject resobj = jsonArray.getJSONObject(i);
InstanceReg instanceReg = parseInstanceReg(resobj);
i++;
instanceRegs.add(instanceReg);
}
} catch (JSONException e) {
e.printStackTrace();
}
return instanceRegs;
}
/**
* Parse json response an unique instance for registering
* @param resobj JSONObject
* @return InstanceReg
*/
private InstanceReg parseInstanceReg(JSONObject resobj){
InstanceReg instanceReg = new InstanceReg();
try {
instanceReg.setDomain(resobj.getString("domain"));
instanceReg.setVersion(resobj.getString("version"));
instanceReg.setDescription(resobj.getString("description"));
instanceReg.setLanguage(resobj.getString("language"));
instanceReg.setCategory(resobj.getString("category"));
instanceReg.setProxied_thumbnail(resobj.getString("proxied_thumbnail"));
instanceReg.setTotal_users(resobj.getInt("total_users"));
instanceReg.setLast_week_users(resobj.getInt("last_week_users"));
} catch (JSONException e) {
e.printStackTrace();
}
return instanceReg;
}
/**
* Parse Pleroma emojis
* @param jsonObject JSONObject

View File

@ -25,6 +25,7 @@ import app.fedilab.android.client.Entities.Error;
import app.fedilab.android.client.Entities.Filters;
import app.fedilab.android.client.Entities.HowToVideo;
import app.fedilab.android.client.Entities.Instance;
import app.fedilab.android.client.Entities.InstanceReg;
import app.fedilab.android.client.Entities.Notification;
import app.fedilab.android.client.Entities.Peertube;
import app.fedilab.android.client.Entities.PeertubeNotification;
@ -62,6 +63,8 @@ public class APIResponse {
private List<StoredStatus> storedStatuses;
private boolean fetchmore = false;
private List<String> playlistForVideos;
private List<InstanceReg> instanceRegs = null;
public List<Account> getAccounts() {
return accounts;
@ -238,4 +241,12 @@ public class APIResponse {
public void setPlaylistForVideos(List<String> playlistForVideos) {
this.playlistForVideos = playlistForVideos;
}
public List<InstanceReg> getInstanceRegs() {
return instanceRegs;
}
public void setInstanceRegs(List<InstanceReg> instanceRegs) {
this.instanceRegs = instanceRegs;
}
}

View File

@ -0,0 +1,41 @@
package app.fedilab.android.client.Entities;
public class AccountCreation {
private String username;
private String email;
private String password;
private String passwordConfirm;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getPasswordConfirm() {
return passwordConfirm;
}
public void setPasswordConfirm(String passwordConfirm) {
this.passwordConfirm = passwordConfirm;
}
}

View File

@ -0,0 +1,100 @@
package app.fedilab.android.client.Entities;
/* Copyright 2019 Thomas Schneider
*
* This file is a part of Fedilab
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 3 of the
* License, or (at your option) any later version.
*
* Fedilab is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
* Public License for more details.
*
* You should have received a copy of the GNU General Public License along with Fedilab; if not,
* see <http://www.gnu.org/licenses>. */
public class InstanceReg {
private String domain;
private String version;
private String description;
private String language;
private String category;
private String proxied_thumbnail;
private int total_users;
private int last_week_users;
private boolean selected = false;
public String getDomain() {
return domain;
}
public void setDomain(String domain) {
this.domain = domain;
}
public String getVersion() {
return version;
}
public void setVersion(String version) {
this.version = version;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public String getLanguage() {
return language;
}
public void setLanguage(String language) {
this.language = language;
}
public String getCategory() {
return category;
}
public void setCategory(String category) {
this.category = category;
}
public String getProxied_thumbnail() {
return proxied_thumbnail;
}
public void setProxied_thumbnail(String proxied_thumbnail) {
this.proxied_thumbnail = proxied_thumbnail;
}
public int getTotal_users() {
return total_users;
}
public void setTotal_users(int total_users) {
this.total_users = total_users;
}
public int getLast_week_users() {
return last_week_users;
}
public void setLast_week_users(int last_week_users) {
this.last_week_users = last_week_users;
}
public boolean isSelected() {
return selected;
}
public void setSelected(boolean selected) {
this.selected = selected;
}
}

View File

@ -0,0 +1,116 @@
package app.fedilab.android.drawers;
/* Copyright 2019 Thomas Schneider
*
* This file is a part of Fedilab
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 3 of the
* License, or (at your option) any later version.
*
* Fedilab is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
* Public License for more details.
*
* You should have received a copy of the GNU General Public License along with Fedilab; if not,
* see <http://www.gnu.org/licenses>. */
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import com.bumptech.glide.Glide;
import com.bumptech.glide.load.resource.bitmap.FitCenter;
import com.bumptech.glide.load.resource.bitmap.RoundedCorners;
import com.bumptech.glide.request.RequestOptions;
import java.util.List;
import app.fedilab.android.R;
import app.fedilab.android.activities.MastodonRegisterActivity;
import app.fedilab.android.client.Entities.InstanceReg;
import app.fedilab.android.helper.Helper;
/**
* Created by Thomas on 14/06/2019.
* Adapter to display instances
*/
public class InstanceRegAdapter extends RecyclerView.Adapter {
private Context context;
private List<InstanceReg> instanceRegs;
private LayoutInflater layoutInflater;
public InstanceRegAdapter(Context context, List<InstanceReg> instanceRegs) {
this.context = context;
this.instanceRegs = instanceRegs;
this.layoutInflater = LayoutInflater.from(this.context);
}
public int getCount() {
return instanceRegs.size();
}
public InstanceReg getItem(int position) {
return instanceRegs.get(position);
}
@NonNull
@Override
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
return new ViewHolder(layoutInflater.inflate(R.layout.drawer_instance_reg, parent, false));
}
@Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder viewHolder, int position) {
InstanceReg instanceReg = instanceRegs.get(position);
final InstanceRegAdapter.ViewHolder holder = (InstanceRegAdapter.ViewHolder) viewHolder;
holder.instance_choose.setOnClickListener(v -> {
((MastodonRegisterActivity)context).pickupInstance(instanceReg.getDomain());
});
holder.instance_count_user.setText(context.getString(R.string.users,Helper.withSuffix(instanceReg.getTotal_users())));
holder.instance_description.setText(instanceReg.getDescription());
holder.instance_host.setText(instanceReg.getDomain());
holder.instance_version.setText(String.format("%s - %s", instanceReg.getCategory(),instanceReg.getVersion()));
Glide.with(context)
.load(instanceReg.getProxied_thumbnail())
.apply(new RequestOptions().transforms(new FitCenter(), new RoundedCorners(10)))
.into(holder.instance_pp);
}
public long getItemId(int position) {
return position;
}
@Override
public int getItemCount() {
return instanceRegs.size();
}
class ViewHolder extends RecyclerView.ViewHolder{
ImageView instance_pp;
TextView instance_host, instance_version, instance_description, instance_count_user;
ImageButton instance_choose;
public ViewHolder(View itemView) {
super(itemView);
instance_pp = itemView.findViewById(R.id.instance_pp);
instance_host = itemView.findViewById(R.id.instance_host);
instance_version = itemView.findViewById(R.id.instance_version);
instance_description = itemView.findViewById(R.id.instance_description);
instance_count_user = itemView.findViewById(R.id.instance_count_user);
instance_choose = itemView.findViewById(R.id.instance_choose);
}
}
}

View File

@ -104,6 +104,18 @@
android:layout_height="wrap_content"
android:textSize="20sp"
android:text="@string/connect_instance" />
<TextView
android:id="@+id/create_an_account"
android:gravity="center"
android:textSize="16sp"
android:text="@string/join_mastodon"
android:layout_gravity="center"
android:layout_marginTop="20dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
<LinearLayout
android:visibility="gone"

View File

@ -0,0 +1,189 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2019 Thomas Schneider
This file is a part of Fedilab
This program is free software; you can redistribute it and/or modify it under the terms of the
GNU General Public License as published by the Free Software Foundation; either version 3 of the
License, or (at your option) any later version.
Fedilab is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
Public License for more details.
You should have received a copy of the GNU General Public License along with Fedilab; if not,
see <http://www.gnu.org/licenses>.
-->
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_margin="@dimen/fab_margin"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/parent"
android:orientation="vertical">
<LinearLayout
android:animateLayoutChanges="true"
android:id="@+id/drawer_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<!-- category -->
<TextView
android:gravity="center"
android:textSize="16sp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/pickup_instance_category"/>
<com.jaredrummler.materialspinner.MaterialSpinner
android:textSize="16sp"
android:id="@+id/reg_category"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/reg_category_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</LinearLayout>
<LinearLayout
android:visibility="gone"
android:id="@+id/form_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:gravity="center"
android:orientation="horizontal">
<TextView
android:layout_gravity="center"
android:gravity="center"
android:id="@+id/host_reg"
android:textSize="18sp"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<TextView
android:layout_gravity="center"
android:gravity="center"
android:layout_marginStart="10dp"
android:id="@+id/change_instance"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
<TextView
android:visibility="gone"
android:id="@+id/invitation"
android:textColor="@color/red_1"
android:layout_gravity="center"
android:gravity="center"
android:text="@string/validation_needed"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<TextView
android:visibility="gone"
android:layout_gravity="center"
android:gravity="center"
android:layout_margin="10dp"
android:padding="5dp"
android:id="@+id/error_message"
android:textColor="@color/red_1"
android:background="@drawable/red_border"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<TextView
android:layout_marginTop="20dp"
android:labelFor="@+id/username"
android:text="@string/username"
android:layout_width="150dp"
android:layout_height="wrap_content" />
<EditText
android:id="@+id/username"
android:inputType="text"
android:singleLine="true"
android:maxLength="30"
android:layout_width="match_parent"
android:layout_height="wrap_content"
/>
<TextView
android:id="@+id/username_indicator"
android:layout_width="match_parent"
android:textSize="12sp"
android:layout_height="wrap_content" />
<TextView
android:layout_marginTop="10dp"
android:labelFor="@+id/email"
android:text="@string/email"
android:layout_width="150dp"
android:layout_height="wrap_content" />
<EditText
android:id="@+id/email"
android:inputType="textEmailAddress"
android:singleLine="true"
android:layout_width="match_parent"
android:layout_height="wrap_content"
/>
<TextView
android:id="@+id/email_indicator"
android:layout_width="match_parent"
android:text="@string/email_indicator"
android:textSize="12sp"
android:layout_height="wrap_content" />
<TextView
android:layout_marginTop="10dp"
android:labelFor="@+id/password"
android:text="@string/password"
android:layout_width="150dp"
android:layout_height="wrap_content" />
<EditText
android:id="@+id/password"
android:inputType="textPassword"
android:singleLine="true"
android:layout_width="match_parent"
android:layout_height="wrap_content"
/>
<TextView
android:id="@+id/password_indicator"
android:layout_width="match_parent"
android:text="@string/password_indicator"
android:textSize="12sp"
android:layout_height="wrap_content" />
<TextView
android:layout_marginTop="10dp"
android:labelFor="@+id/password_confirm"
android:text="@string/password_confirm"
android:layout_width="150dp"
android:layout_height="wrap_content" />
<EditText
android:id="@+id/password_confirm"
android:inputType="textPassword"
android:singleLine="true"
android:layout_width="match_parent"
android:layout_height="wrap_content"
/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<CheckBox
android:id="@+id/agreement"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<TextView
android:id="@+id/agreement_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
<Button
android:id="@+id/signup"
android:text="@string/sign_up"
style="@style/Base.Widget.AppCompat.Button.Colored"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
</LinearLayout>

View File

@ -0,0 +1,89 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2019 Thomas Schneider
This file is a part of Fedilab
This program is free software; you can redistribute it and/or modify it under the terms of the
GNU General Public License as published by the Free Software Foundation; either version 3 of the
License, or (at your option) any later version.
Fedilab is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
Public License for more details.
You should have received a copy of the GNU General Public License along with Fedilab; if not,
see <http://www.gnu.org/licenses>.
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/main_container"
android:orientation="horizontal"
android:background="@drawable/border_bottom"
android:divider="@null"
android:layout_margin="10dp"
android:paddingBottom="10dp"
android:baselineAligned="false">
<LinearLayout
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="wrap_content"
android:orientation="vertical">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal">
<ImageView
android:id="@+id/instance_pp"
android:layout_width="80dp"
android:layout_height="80dp"
android:scaleType="centerCrop"
android:contentDescription="@string/instance_logo" />
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="10dp"
android:orientation="vertical">
<TextView
android:id="@+id/instance_host"
android:textSize="16sp"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<TextView
android:id="@+id/instance_version"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
</LinearLayout>
<TextView
android:id="@+id/instance_description"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
<LinearLayout
android:layout_width="60dp"
android:layout_marginStart="20dp"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:orientation="vertical">
<ImageButton
android:layout_gravity="center"
android:gravity="center"
android:id="@+id/instance_choose"
style="@style/Widget.AppCompat.Button.Colored"
android:layout_width="35dp"
android:layout_height="35dp"
android:layout_margin="5dp"
android:background="@color/mastodonC4"
android:contentDescription="@string/validate"
android:src="@drawable/ic_check"
android:tooltipText="@string/validate" />
<TextView
android:layout_gravity="center"
android:gravity="center"
android:id="@+id/instance_count_user"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
</LinearLayout>

View File

@ -991,6 +991,42 @@
<string name="mention_status">Mention the status</string>
<string name="action_news">News</string>
<string name="set_display_news_from_fedilab">Display news from Fedilab\'s account</string>
<string name="category_general">General</string>
<string name="category_regional">Regional</string>
<string name="category_art">Art</string>
<string name="category_journalism">Journalism</string>
<string name="category_activism">Activism</string>
<string name="category_games">Gaming</string>
<string name="category_tech">Technology</string>
<string name="category_adult">Adult content</string>
<string name="category_furry">Furry</string>
<string name="category_food">Food</string>
<string name="instance_logo">Logo of the instance</string>
<string name="toast_error_instance_reg">Something went wrong when checking available instances!</string>
<string name="join_mastodon">Join Mastodon</string>
<string name="pickup_instance_category">Choose an instance by picking up a category, then click on a check button.</string>
<string name="users">%1$s users</string>
<string name="password_confirm">Confirm password</string>
<string name="agreement_check">I agree to %1$s and %2$s</string>
<string name="server_rules">server rules</string>
<string name="tos">terms of service</string>
<string name="sign_up">Sign up</string>
<string name="validation_needed">This instance works with invitations. Your account will need to be manually approved by an administrator before being usable.</string>
<string name="all_field_filled">Please, fill all the fields!</string>
<string name="password_error">Passwords don\'t match!</string>
<string name="email_error">The email doesn\'t seem to be valid!</string>
<string name="username_indicator">Your username will be unique on %1$s</string>
<string name="email_indicator">You will be sent a confirmation e-mail</string>
<string name="password_indicator">Use at least 8 characters</string>
<string name="password_too_short">Password should contain at least 8 characters</string>
<string name="username_error">Username should only contain letters, numbers and underscores</string>
<string name="account_created">Account created!</string>
<string name="account_created_message">
Your account has been created!\n\n
Think to validate your email within the 48 next hours.\n\n
You can now connect your account by writing <b>%1$s</b> in the first field and click on <b>Connect</b>.\n\n
<b>Important</b>: If your instance required validation, you will receive an email once it is validated!
</string>
<plurals name="number_of_vote">
<item quantity="one">%d vote</item>
<item quantity="other">%d votes</item>