Support openid

This commit is contained in:
Thomas 2020-12-14 14:42:28 +01:00
parent aa38514bcb
commit 9a2eb6cd49
10 changed files with 249 additions and 145 deletions

View File

@ -26,6 +26,8 @@ import android.util.Patterns;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.TextView;
import android.widget.Toast;
@ -35,6 +37,9 @@ import androidx.core.content.ContextCompat;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import app.fedilab.fedilabtube.client.RetrofitPeertubeAPI;
import app.fedilab.fedilabtube.client.entities.Error;
@ -56,6 +61,7 @@ public class LoginActivity extends AppCompatActivity {
private static String client_id;
private static String client_secret;
private ActivityLoginBinding binding;
private String acadInstance;
@SuppressLint("SetTextI18n")
@Override
@ -117,69 +123,59 @@ public class LoginActivity extends AppCompatActivity {
if (!hasFocus) {
if (binding.loginUid.getText() != null && android.util.Patterns.EMAIL_ADDRESS.matcher(binding.loginUid.getText().toString().trim()).matches()) {
String[] emailArray = binding.loginUid.getText().toString().split("@");
if (emailArray.length > 1 && Arrays.asList(HelperAcadInstance.openid).contains(emailArray[1])) {
if (emailArray.length > 1) {
binding.loginButton.callOnClick();
}
}
}
});
binding.loginUidContainer.setVisibility(View.GONE);
binding.loginPasswdContainer.setVisibility(View.GONE);
binding.loginInstanceContainer.setVisibility(View.GONE);
binding.createAnAccountPeertube.setVisibility(View.GONE);
binding.instancePickerTitle.setVisibility(View.VISIBLE);
binding.instancePicker.setVisibility(View.VISIBLE);
HashMap<String, String> instancesMap = new HashMap<>(HelperAcadInstance.instances_themes);
Iterator<Map.Entry<String, String>> it = instancesMap.entrySet().iterator();
String[] academiesKey = new String[HelperAcadInstance.instances_themes.size()];
String[] academiesValue = new String[HelperAcadInstance.instances_themes.size()];
String acad = HelperInstance.getLiveInstance(LoginActivity.this);
int position = 0;
int i = 0;
while (it.hasNext()) {
Map.Entry<String, String> pair = it.next();
academiesKey[i] = pair.getKey();
academiesValue[i] = pair.getValue();
if (pair.getValue().compareTo(acad) == 0) {
position = i;
}
it.remove();
i++;
}
binding.instancePicker.setSelection(position, true);
ArrayAdapter<String> adapterChannel = new ArrayAdapter<>(LoginActivity.this,
android.R.layout.simple_spinner_dropdown_item, academiesKey);
binding.instancePicker.setAdapter(adapterChannel);
binding.instancePicker.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
acadInstance = academiesValue[position];
}
@Override
public void onNothingSelected(AdapterView<?> parent) {
}
});
}
binding.loginButton.setOnClickListener(v -> {
if (binding.loginUid.getText() != null && binding.loginUid.getText().toString().contains("@") && !android.util.Patterns.EMAIL_ADDRESS.matcher(binding.loginUid.getText().toString().trim()).matches()) {
Toasty.error(LoginActivity.this, getString(R.string.email_error)).show();
return;
}
binding.loginButton.setEnabled(false);
String instance, host;
if (!BuildConfig.full_instances) {
String[] emailArray = binding.loginUid.getText().toString().split("@");
if (emailArray.length > 1 && !Arrays.asList(HelperAcadInstance.valideEmails).contains(emailArray[1])) {
Toasty.error(LoginActivity.this, getString(R.string.email_error_domain, emailArray[1])).show();
binding.loginButton.setEnabled(true);
return;
}
host = emailArray[1];
instance = HelperInstance.getPeertubeUrl(host);
} else {
if (binding.loginInstance.getText() == null || binding.loginInstance.getText().toString().trim().length() == 0) {
Toasty.error(LoginActivity.this, getString(R.string.not_valide_instance)).show();
binding.loginButton.setEnabled(true);
return;
}
instance = host = binding.loginInstance.getText().toString().trim().toLowerCase();
}
if (instance.startsWith("http")) {
try {
URL url = new URL(instance);
instance = url.getHost();
host = instance;
} catch (MalformedURLException e) {
e.printStackTrace();
}
} else if (instance.endsWith("/")) {
try {
URL url = new URL("https://" + instance);
instance = url.getHost();
host = instance;
} catch (MalformedURLException e) {
e.printStackTrace();
}
}
if (!Patterns.WEB_URL.matcher("https://" + instance).matches()) {
Toasty.error(LoginActivity.this, getString(R.string.not_valide_instance)).show();
binding.loginButton.setEnabled(true);
return;
}
String finalInstance = instance;
String finalHost = host;
if (Arrays.asList(HelperAcadInstance.openid).contains(host) && !BuildConfig.full_instances) {
new Thread(() -> {
try {
Oauth oauth = new RetrofitPeertubeAPI(LoginActivity.this, finalInstance, null).oauthClient(null, null, null, null);
Oauth oauth = new RetrofitPeertubeAPI(LoginActivity.this, acadInstance, null).oauthClient(null, null, null, null);
if (oauth == null) {
runOnUiThread(() -> {
binding.loginButton.setEnabled(true);
@ -196,7 +192,7 @@ public class LoginActivity extends AppCompatActivity {
editor.apply();
Intent intent = new Intent(LoginActivity.this, WebviewConnectActivity.class);
Bundle b = new Bundle();
b.putString("url", "https://" + HelperInstance.getPeertubeUrl(finalHost) + "/plugins/auth-openid-connect/0.0.1/auth/openid-connect");
b.putString("url", "https://" + acadInstance + "/plugins/auth-openid-connect/0.0.1/auth/openid-connect");
intent.putExtras(b);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
@ -210,50 +206,100 @@ public class LoginActivity extends AppCompatActivity {
}).start();
} else {
new Thread(() -> {
Oauth oauth = new RetrofitPeertubeAPI(LoginActivity.this, finalInstance, null).oauthClient(Helper.CLIENT_NAME_VALUE, Helper.WEBSITE_VALUE, Helper.OAUTH_SCOPES_PEERTUBE, Helper.WEBSITE_VALUE);
if (oauth == null) {
runOnUiThread(() -> {
binding.loginButton.setEnabled(true);
Toasty.error(LoginActivity.this, getString(R.string.client_error), Toast.LENGTH_LONG).show();
});
if (binding.loginUid.getText() != null && binding.loginUid.getText().toString().contains("@") && !android.util.Patterns.EMAIL_ADDRESS.matcher(binding.loginUid.getText().toString().trim()).matches()) {
Toasty.error(LoginActivity.this, getString(R.string.email_error)).show();
return;
}
binding.loginButton.setEnabled(false);
String instance, host;
if (!BuildConfig.full_instances) {
String[] emailArray = binding.loginUid.getText().toString().split("@");
if (emailArray.length > 1 && !Arrays.asList(HelperAcadInstance.valideEmails).contains(emailArray[1])) {
Toasty.error(LoginActivity.this, getString(R.string.email_error_domain, emailArray[1])).show();
binding.loginButton.setEnabled(true);
return;
}
client_id = oauth.getClient_id();
client_secret = oauth.getClient_secret();
SharedPreferences sharedpreferences = getSharedPreferences(Helper.APP_PREFS, MODE_PRIVATE);
SharedPreferences.Editor editor = sharedpreferences.edit();
editor.putString(Helper.CLIENT_ID, client_id);
editor.putString(Helper.CLIENT_SECRET, client_secret);
editor.apply();
OauthParams oauthParams = new OauthParams();
oauthParams.setClient_id(client_id);
oauthParams.setClient_secret(client_secret);
oauthParams.setGrant_type("password");
oauthParams.setScope("user");
oauthParams.setUsername(binding.loginUid.getText().toString().trim());
if (binding.loginPasswd.getText() != null) {
oauthParams.setPassword(binding.loginPasswd.getText().toString());
instance = HelperInstance.getLiveInstance(LoginActivity.this);
host = instance;
} else {
if (binding.loginInstance.getText() == null || binding.loginInstance.getText().toString().trim().length() == 0) {
Toasty.error(LoginActivity.this, getString(R.string.not_valide_instance)).show();
binding.loginButton.setEnabled(true);
return;
}
instance = host = binding.loginInstance.getText().toString().trim().toLowerCase();
}
if (instance.startsWith("http")) {
try {
Token token = new RetrofitPeertubeAPI(LoginActivity.this, finalInstance, null).manageToken(oauthParams);
proceedLogin(token, finalHost);
} catch (final Exception | Error e) {
oauthParams.setUsername(binding.loginUid.getText().toString().toLowerCase().trim());
URL url = new URL(instance);
instance = url.getHost();
host = instance;
} catch (MalformedURLException e) {
e.printStackTrace();
}
} else if (instance.endsWith("/")) {
try {
URL url = new URL("https://" + instance);
instance = url.getHost();
host = instance;
} catch (MalformedURLException e) {
e.printStackTrace();
}
}
if (!Patterns.WEB_URL.matcher("https://" + instance).matches()) {
Toasty.error(LoginActivity.this, getString(R.string.not_valide_instance)).show();
binding.loginButton.setEnabled(true);
return;
}
String finalInstance = instance;
String finalHost = host;
if (BuildConfig.full_instances) {
new Thread(() -> {
Oauth oauth = new RetrofitPeertubeAPI(LoginActivity.this, finalInstance, null).oauthClient(Helper.CLIENT_NAME_VALUE, Helper.WEBSITE_VALUE, Helper.OAUTH_SCOPES_PEERTUBE, Helper.WEBSITE_VALUE);
if (oauth == null) {
runOnUiThread(() -> {
binding.loginButton.setEnabled(true);
Toasty.error(LoginActivity.this, getString(R.string.client_error), Toast.LENGTH_LONG).show();
});
return;
}
client_id = oauth.getClient_id();
client_secret = oauth.getClient_secret();
SharedPreferences sharedpreferences = getSharedPreferences(Helper.APP_PREFS, MODE_PRIVATE);
SharedPreferences.Editor editor = sharedpreferences.edit();
editor.putString(Helper.CLIENT_ID, client_id);
editor.putString(Helper.CLIENT_SECRET, client_secret);
editor.apply();
OauthParams oauthParams = new OauthParams();
oauthParams.setClient_id(client_id);
oauthParams.setClient_secret(client_secret);
oauthParams.setGrant_type("password");
oauthParams.setScope("user");
oauthParams.setUsername(binding.loginUid.getText().toString().trim());
if (binding.loginPasswd.getText() != null) {
oauthParams.setPassword(binding.loginPasswd.getText().toString());
}
try {
Token token = new RetrofitPeertubeAPI(LoginActivity.this, finalInstance, null).manageToken(oauthParams);
proceedLogin(token, finalHost);
} catch (Error error) {
Error.displayError(LoginActivity.this, error);
error.printStackTrace();
runOnUiThread(() -> binding.loginButton.setEnabled(true));
} catch (final Exception | Error e) {
oauthParams.setUsername(binding.loginUid.getText().toString().toLowerCase().trim());
try {
Token token = new RetrofitPeertubeAPI(LoginActivity.this, finalInstance, null).manageToken(oauthParams);
proceedLogin(token, finalHost);
} catch (Error error) {
Error.displayError(LoginActivity.this, error);
error.printStackTrace();
runOnUiThread(() -> binding.loginButton.setEnabled(true));
}
}
}
}).start();
}).start();
}
}
});
}

View File

@ -58,8 +58,11 @@ import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import java.util.regex.Matcher;
@ -80,6 +83,7 @@ import app.fedilab.fedilabtube.databinding.ActivityMainBinding;
import app.fedilab.fedilabtube.fragment.DisplayOverviewFragment;
import app.fedilab.fedilabtube.fragment.DisplayVideosFragment;
import app.fedilab.fedilabtube.helper.Helper;
import app.fedilab.fedilabtube.helper.HelperAcadInstance;
import app.fedilab.fedilabtube.helper.HelperInstance;
import app.fedilab.fedilabtube.helper.PlaylistExportHelper;
import app.fedilab.fedilabtube.helper.SwitchAccountHelper;
@ -97,7 +101,7 @@ import su.litvak.chromecast.api.v2.MediaStatus;
import static app.fedilab.fedilabtube.MainActivity.TypeOfConnection.NORMAL;
import static app.fedilab.fedilabtube.MainActivity.TypeOfConnection.SURFING;
import static app.fedilab.fedilabtube.helper.Helper.peertubeInformation;
import static app.fedilab.fedilabtube.helper.HelperAcadInstance.academies;
public class MainActivity extends AppCompatActivity implements ChromeCastsListener {
@ -880,14 +884,23 @@ public class MainActivity extends AppCompatActivity implements ChromeCastsListen
final SharedPreferences sharedpreferences = getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE);
String acad = HelperInstance.getLiveInstance(MainActivity.this);
int i = 0;
for (String item : academies) {
if (HelperInstance.getPeertubeUrl(item).compareTo(acad) == 0) {
break;
HashMap<String, String> instancesMap = new HashMap<>(HelperAcadInstance.instances_themes);
Iterator<Map.Entry<String, String>> it = instancesMap.entrySet().iterator();
String[] academiesKey = new String[HelperAcadInstance.instances_themes.size()];
String[] academiesValue = new String[HelperAcadInstance.instances_themes.size()];
int position = 0;
while (it.hasNext()) {
Map.Entry<String, String> pair = it.next();
academiesKey[i] = pair.getKey();
academiesValue[i] = pair.getValue();
if (pair.getValue().compareTo(acad) == 0) {
position = i;
}
it.remove();
i++;
}
alt_bld.setSingleChoiceItems(academies, i, (dialog, item) -> {
String newInstance = academies[item];
alt_bld.setSingleChoiceItems(academiesKey, position, (dialog, item) -> {
String newInstance = academiesValue[item];
SharedPreferences.Editor editor = sharedpreferences.edit();
editor.putString(Helper.PREF_INSTANCE, newInstance);
editor.commit();

View File

@ -163,8 +163,7 @@ public class PeertubeRegisterActivity extends AppCompatActivity {
}
});
} else {
String host = emailArray[1];
instance = HelperInstance.getPeertubeUrl(host);
instance = HelperInstance.getLiveInstance(PeertubeRegisterActivity.this);
}
if (instance != null) {
instance = instance.toLowerCase().trim();

View File

@ -122,8 +122,11 @@ public class WebviewConnectActivity extends AppCompatActivity {
oauthParams.setClient_id(sharedpreferences.getString(Helper.CLIENT_ID, null));
oauthParams.setClient_secret(sharedpreferences.getString(Helper.CLIENT_SECRET, null));
oauthParams.setGrant_type("password");
oauthParams.setScope("upload");
oauthParams.setResponse_type("code");
oauthParams.setUsername(username);
oauthParams.setExternalAuthToken(externalAuthToken);
oauthParams.setPassword(externalAuthToken);
String instance = new URL(url).getHost();
Token token = null;
try {

View File

@ -103,6 +103,21 @@ public interface PeertubeService {
@GET("client/locales/{local}/server.json")
Call<Map<String, String>> getTranslations(@Path("local") String local);
//TOKEN
//Refresh
@FormUrlEncoded
@POST("users/token")
Call<Token> createOpenIdToken(
@Field("client_id") String client_id,
@Field("client_secret") String client_secret,
@Field("response_type") String response_type,
@Field("grant_type") String grant_type,
@Field("scope") String scope,
@Field("username") String username,
@Field("password") String password,
@Field("externalAuthToken") String externalAuthToken);
//TOKEN
//Refresh
@FormUrlEncoded

View File

@ -23,6 +23,7 @@ import android.net.Uri;
import android.os.Handler;
import android.os.Looper;
import android.webkit.MimeTypeMap;
import android.webkit.URLUtil;
import androidx.documentfile.provider.DocumentFile;
@ -121,12 +122,7 @@ public class RetrofitPeertubeAPI {
public static void updateCredential(Activity activity, String token, String client_id, String client_secret, String refresh_token, String host) {
new Thread(() -> {
AccountData.Account account;
String instance;
if (host.startsWith("tube") || BuildConfig.full_instances) {
instance = host;
} else {
instance = HelperInstance.getPeertubeUrl(host);
}
String instance = host;
try {
UserMe userMe = new RetrofitPeertubeAPI(activity, instance, token).verifyCredentials();
account = userMe.getAccount();
@ -181,6 +177,9 @@ public class RetrofitPeertubeAPI {
}
private PeertubeService init() {
if (!URLUtil.isValidUrl(finalUrl)) {
return null;
}
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(finalUrl)
.addConverterFactory(GsonConverterFactory.create())
@ -194,6 +193,9 @@ public class RetrofitPeertubeAPI {
}
private PeertubeService initTranslation() {
if (!URLUtil.isValidUrl("https://" + instance)) {
return null;
}
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://" + instance)
.addConverterFactory(GsonConverterFactory.create())
@ -208,7 +210,9 @@ public class RetrofitPeertubeAPI {
public Token manageToken(OauthParams oauthParams) throws Error {
PeertubeService peertubeService = init();
Call<Token> refreshTokenCall = null;
if (oauthParams.getGrant_type().compareTo("password") == 0) {
if (oauthParams.getExternalAuthToken() != null) {
refreshTokenCall = peertubeService.createOpenIdToken(oauthParams.getClient_id(), oauthParams.getClient_secret(), oauthParams.getResponse_type(), oauthParams.getGrant_type(), oauthParams.getScope(), oauthParams.getUsername(), oauthParams.getPassword(), oauthParams.getExternalAuthToken());
} else if (oauthParams.getGrant_type().compareTo("password") == 0) {
refreshTokenCall = peertubeService.createToken(oauthParams.getClient_id(), oauthParams.getClient_secret(), oauthParams.getGrant_type(), oauthParams.getUsername(), oauthParams.getPassword());
} else if (oauthParams.getGrant_type().compareTo("refresh_token") == 0) {
refreshTokenCall = peertubeService.refreshToken(oauthParams.getClient_id(), oauthParams.getClient_secret(), oauthParams.getRefresh_token(), oauthParams.getGrant_type());

View File

@ -38,7 +38,8 @@ public class OauthParams {
private String refresh_token;
@SerializedName("access_token")
private String access_token;
@SerializedName("response_type")
private String response_type;
public String getClient_secret() {
return client_secret;
@ -111,4 +112,12 @@ public class OauthParams {
public void setAccess_token(String access_token) {
this.access_token = access_token;
}
public String getResponse_type() {
return response_type;
}
public void setResponse_type(String response_type) {
this.response_type = response_type;
}
}

View File

@ -14,12 +14,34 @@ package app.fedilab.fedilabtube.helper;
* You should have received a copy of the GNU General Public License along with TubeLab; if not,
* see <http://www.gnu.org/licenses>. */
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
public class HelperAcadInstance {
public static Map<String, String> instances_themes;
static {
Map<String, String> is = new HashMap<>();
is.put("Normandie", "tube-normandie.beta.education.fr");
is.put("Enseignement professionnel", "tube-enseignement-professionnel.apps.education.fr");
is.put("Action éducative", "tube-action-educative.apps.education.fr");
is.put("Numérique éducatif", "tube-numerique-educatif.apps.education.fr");
is.put("Institutionnel", "tube-institutionnelle.apps.education.fr");
is.put("2d - langues vivantes", "tube-2d-langues-vivantes.apps.education.fr");
is.put("2d - éducation physique et sportive", "tube-2d-education-physique-et-sportive.apps.education.fr");
is.put("2d - arts, lettres et sciences humaines", "tube-2d-arts-lettres-sciences-humaines.apps.education.fr");
is.put("Maternelle", "tube-maternelle.apps.education.fr");
is.put("Cycle 2", "tube-cycle-2.apps.education.fr");
is.put("Cycle 3", "tube-cycle-3.apps.education.fr");
instances_themes = Collections.unmodifiableMap(is);
}
//List of available academies
public static String[] openid = {
/*public static String[] openid = {
"ac-normandie.fr",
"education.fr",
"education.gouv.fr"
@ -54,7 +76,7 @@ public class HelperAcadInstance {
"ac-strasbourg.fr",
"ac-toulouse.fr",
"ac-versailles.fr"
};
};*/
public static String[] valideEmails = {

View File

@ -23,39 +23,6 @@ import app.fedilab.fedilabtube.BuildConfig;
* see <http://www.gnu.org/licenses>. */
public class HelperInstance {
/**
* Returns the peertube URL depending of the academic domain name
*
* @param acad String academic domain name
* @return String the peertube URL
*/
public static String getPeertubeUrl(String acad) {
if (BuildConfig.full_instances) {
return acad;
}
if (acad.compareTo("education.gouv.fr") == 0 || acad.compareTo("igesr.gouv.fr") == 0) {
acad = "education.fr";
} else if (acad.compareTo("ac-nancy-metz.fr") == 0) {
acad = "ac-nancy.fr";
} else if (acad.compareTo("clermont.fr") == 0) {
acad = "clermont-ferrand.fr";
} else if (acad.compareTo("ac-wf.wf") == 0 || acad.compareTo("ac-mayotte.fr") == 0 || acad.compareTo("ac-noumea.nc") == 0
|| acad.compareTo("ac-guadeloupe.fr") == 0 || acad.compareTo("monvr.pf") == 0 || acad.compareTo("ac-reunion.fr") == 0 ||
acad.compareTo("ac-martinique.fr") == 0 || acad.compareTo("ac-guyane.fr") == 0
) {
acad = "outremer.fr";
}
if (!acad.contains("ac-lyon.fr")) {
//TODO: remove hack for test with openid
/*if( acad.contains("orleans-tours.fr")) {
return "tube-normandie.beta.education.fr";
}*/
return "tube-" + acad.replaceAll("ac-|\\.fr", "") + ".beta.education.fr";
} else {
return "tube.ac-lyon.fr";
}
}
/**
@ -75,15 +42,11 @@ public class HelperInstance {
} else if (BuildConfig.FLAVOR.compareTo("queermotion") == 0) {
return sharedpreferences.getString(Helper.PREF_INSTANCE, "queermotion.org");
} else {
acad = sharedpreferences.getString(Helper.PREF_INSTANCE, "tube.ac-lyon.fr");
acad = sharedpreferences.getString(Helper.PREF_INSTANCE, "tube-institutionnelle.apps.education.fr");
if (acad == null) {
acad = "tube.ac-lyon.fr";
}
if (acad.startsWith("tube-")) {
return acad;
} else {
return getPeertubeUrl(acad);
acad = "tube-institutionnelle.apps.education.fr";
}
return acad;
}
}

View File

@ -120,6 +120,36 @@
android:importantForAutofill="no" />
</com.google.android.material.textfield.TextInputLayout>
<TextView
android:id="@+id/instance_picker_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:text="@string/instances_picker"
android:visibility="gone"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Spinner
android:id="@+id/instance_picker"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:layout_marginBottom="20dp"
android:visibility="gone"
app:layout_constraintBottom_toTopOf="@+id/login_button"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/instance_picker_title" />
<androidx.constraintlayout.widget.Barrier
android:id="@+id/barrier"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:barrierDirection="bottom"
app:constraint_referenced_ids="login_passwd_container,instance_picker" />
<Button
android:id="@+id/login_button"
style="@style/Base.Widget.AppCompat.Button.Colored"
@ -139,7 +169,7 @@
android:textSize="20sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/login_passwd_container" />
app:layout_constraintTop_toBottomOf="@id/barrier" />
</androidx.constraintlayout.widget.ConstraintLayout>
</LinearLayout>