@@ -46,6 +46,8 @@ | |||||
</activity> | </activity> | ||||
<activity android:name=".activity.LoginActivity" | <activity android:name=".activity.LoginActivity" | ||||
android:screenOrientation="portrait"/> | android:screenOrientation="portrait"/> | ||||
<activity android:name=".activity.SetLauncherActivity" | |||||
android:screenOrientation="portrait"/> | |||||
<activity | <activity | ||||
android:name=".activity.MFAVerifyActivity" | android:name=".activity.MFAVerifyActivity" | ||||
android:screenOrientation="portrait" /> | android:screenOrientation="portrait" /> | ||||
@@ -37,6 +37,7 @@ public class LoginActivity extends BaseActivityBubble { | |||||
private EditText password; | private EditText password; | ||||
private AppCompatButton sign; | private AppCompatButton sign; | ||||
private TextView signUpButton; | private TextView signUpButton; | ||||
private TextView setLauncherButton; | |||||
private String resource; | private String resource; | ||||
private boolean userNameStateFlag; | private boolean userNameStateFlag; | ||||
@@ -69,6 +70,12 @@ public class LoginActivity extends BaseActivityBubble { | |||||
passwordStateListener(); | passwordStateListener(); | ||||
sendListener(); | sendListener(); | ||||
signUpButton.setOnClickListener(v -> signUp()); | signUpButton.setOnClickListener(v -> signUp()); | ||||
setLauncherButton.setOnClickListener(v -> setLauncher()); | |||||
} | |||||
private void setLauncher() { | |||||
final Intent intent = new Intent(this, SetLauncherActivity.class); | |||||
startActivity(intent); | |||||
} | } | ||||
private void signUp() { | private void signUp() { | ||||
@@ -106,6 +113,7 @@ public class LoginActivity extends BaseActivityBubble { | |||||
password = findViewById(R.id.password); | password = findViewById(R.id.password); | ||||
sign = findViewById(R.id.signButton); | sign = findViewById(R.id.signButton); | ||||
signUpButton = findViewById(R.id.signUpButton); | signUpButton = findViewById(R.id.signUpButton); | ||||
setLauncherButton = findViewById(R.id.setLauncherButton); | |||||
} | } | ||||
@@ -103,6 +103,11 @@ public class MainActivity extends BaseActivityBubble { | |||||
}); | }); | ||||
} | } | ||||
@Override protected void onStop() { | |||||
super.onStop(); | |||||
mainViewModel.disposeProgress(); | |||||
} | |||||
private void startBubbleSession() { | private void startBubbleSession() { | ||||
if (mainViewModel.isHaveSageURL(this)) { | if (mainViewModel.isHaveSageURL(this)) { | ||||
if (mainViewModel.isHaveHostName(this)) { | if (mainViewModel.isHaveHostName(this)) { | ||||
@@ -463,6 +468,7 @@ public class MainActivity extends BaseActivityBubble { | |||||
mainViewModel.deleteTunnel(); | mainViewModel.deleteTunnel(); | ||||
mainViewModel.removeSharedPreferences(this); | mainViewModel.removeSharedPreferences(this); | ||||
mainViewModel.clearCredentials(getApplicationContext()); | mainViewModel.clearCredentials(getApplicationContext()); | ||||
mainViewModel.disposeBubbleCheck(); | |||||
startActivity(intent); | startActivity(intent); | ||||
} | } | ||||
@@ -0,0 +1,98 @@ | |||||
package com.getbubblenow.android.activity; | |||||
import android.os.Bundle; | |||||
import android.view.KeyEvent; | |||||
import android.view.inputmethod.EditorInfo; | |||||
import android.widget.EditText; | |||||
import com.getbubblenow.android.R; | |||||
import com.getbubblenow.android.viewmodel.SetLauncherViewModel; | |||||
import com.jakewharton.rxbinding4.widget.RxTextView; | |||||
import androidx.appcompat.widget.AppCompatButton; | |||||
import androidx.lifecycle.ViewModelProvider; | |||||
import io.reactivex.rxjava3.disposables.CompositeDisposable; | |||||
import io.reactivex.rxjava3.disposables.Disposable; | |||||
public class SetLauncherActivity extends BaseActivityBubble { | |||||
private static final String PREFIX = "https://"; | |||||
private SetLauncherViewModel setLauncherViewModel; | |||||
private EditText launcherUrl; | |||||
private AppCompatButton setLauncherButton; | |||||
private final CompositeDisposable compositeSubscription = new CompositeDisposable(); | |||||
@Override | |||||
protected void onCreate(final Bundle savedInstanceState) { | |||||
super.onCreate(savedInstanceState); | |||||
setContentView(R.layout.activity_login_sage); | |||||
initUI(); | |||||
setLauncherViewModel = new ViewModelProvider(this).get(SetLauncherViewModel.class); | |||||
} | |||||
@Override protected void onDestroy() { | |||||
super.onDestroy(); | |||||
compositeSubscription.dispose(); | |||||
} | |||||
private void initUI() { | |||||
initViews(); | |||||
initListeners(); | |||||
} | |||||
private void initListeners() { | |||||
setLauncherButton.setOnClickListener(v -> setLauncher()); | |||||
sendListener(); | |||||
launcherUrlStateListener(); | |||||
} | |||||
private void initViews() { | |||||
launcherUrl = findViewById(R.id.launcher_url); | |||||
setLauncherButton = findViewById(R.id.setLauncherButton); | |||||
} | |||||
private void sendListener() { | |||||
launcherUrl.setOnEditorActionListener((v, actionId, event) -> { | |||||
if ((event != null && event.getKeyCode() == KeyEvent.KEYCODE_ENTER && event.getAction() == KeyEvent.ACTION_UP) | |||||
|| (actionId == EditorInfo.IME_ACTION_SEND)) { | |||||
setLauncher(); | |||||
return true; | |||||
} | |||||
return false; | |||||
}); | |||||
} | |||||
private void setLauncher() { | |||||
String launcherInput = launcherUrl.getText().toString().trim(); | |||||
if (!launcherInput.isEmpty()) { | |||||
if (!launcherInput.startsWith(PREFIX)) { | |||||
launcherInput = PREFIX + launcherInput; | |||||
} | |||||
} | |||||
setLauncherViewModel.setSageHost(this, launcherInput); | |||||
finish(); | |||||
} | |||||
private void launcherUrlStateListener() { | |||||
final Disposable subscribe = RxTextView | |||||
.afterTextChangeEvents(launcherUrl) | |||||
.subscribe(view -> setButtonState(!launcherUrl.getText().toString().trim().isEmpty())); | |||||
compositeSubscription.add(subscribe); | |||||
} | |||||
private void setButtonState(final boolean launcherUrlStateFlag) { | |||||
if (launcherUrlStateFlag) { | |||||
setLauncherButton.setBackgroundDrawable(getDrawable(R.drawable.sign_in_enable)); | |||||
setLauncherButton.setEnabled(true); | |||||
} else { | |||||
setLauncherButton.setBackgroundDrawable(getDrawable(R.drawable.sign_in_disable)); | |||||
setLauncherButton.setEnabled(false); | |||||
} | |||||
} | |||||
} |
@@ -11,12 +11,10 @@ import android.widget.Toast; | |||||
import com.getbubblenow.android.Application; | import com.getbubblenow.android.Application; | ||||
import com.getbubblenow.android.R; | import com.getbubblenow.android.R; | ||||
import com.getbubblenow.android.api.enums.MFAuthType; | import com.getbubblenow.android.api.enums.MFAuthType; | ||||
import com.getbubblenow.android.configStore.FileConfigStore; | |||||
import com.getbubblenow.android.model.LoginErrBody; | import com.getbubblenow.android.model.LoginErrBody; | ||||
import com.getbubblenow.android.model.Network; | import com.getbubblenow.android.model.Network; | ||||
import com.getbubblenow.android.model.NetworkStatus; | import com.getbubblenow.android.model.NetworkStatus; | ||||
import com.getbubblenow.android.model.ObservableTunnel; | import com.getbubblenow.android.model.ObservableTunnel; | ||||
import com.getbubblenow.android.model.TunnelManager; | |||||
import com.getbubblenow.android.activity.MainActivity; | import com.getbubblenow.android.activity.MainActivity; | ||||
import com.getbubblenow.android.api.ApiConstants; | import com.getbubblenow.android.api.ApiConstants; | ||||
import com.getbubblenow.android.api.network.ClientApi; | import com.getbubblenow.android.api.network.ClientApi; | ||||
@@ -39,6 +37,7 @@ import java.io.IOException; | |||||
import java.io.InputStream; | import java.io.InputStream; | ||||
import java.util.ArrayList; | import java.util.ArrayList; | ||||
import java.util.Arrays; | import java.util.Arrays; | ||||
import java.util.Collections; | |||||
import java.util.HashMap; | import java.util.HashMap; | ||||
import java.util.Iterator; | import java.util.Iterator; | ||||
import java.util.List; | import java.util.List; | ||||
@@ -56,6 +55,7 @@ import io.reactivex.observers.DisposableSingleObserver; | |||||
import io.reactivex.schedulers.Schedulers; | import io.reactivex.schedulers.Schedulers; | ||||
import io.reactivex.subscribers.DisposableSubscriber; | import io.reactivex.subscribers.DisposableSubscriber; | ||||
import okhttp3.Request; | import okhttp3.Request; | ||||
import okhttp3.RequestBody; | |||||
import okhttp3.ResponseBody; | import okhttp3.ResponseBody; | ||||
import okio.Buffer; | import okio.Buffer; | ||||
import retrofit2.Call; | import retrofit2.Call; | ||||
@@ -97,6 +97,8 @@ public final class DataRepository { | |||||
private static volatile DataRepository instance; | private static volatile DataRepository instance; | ||||
private String baseUrl = ""; | private String baseUrl = ""; | ||||
private Disposable bubbleCheckDisposable; | |||||
private Disposable progressDisposable; | |||||
private String deviceName; | private String deviceName; | ||||
private String deviceID; | private String deviceID; | ||||
@@ -135,6 +137,24 @@ public final class DataRepository { | |||||
return instance; | return instance; | ||||
} | } | ||||
public void disposeBubbleCheck() { | |||||
if (bubbleCheckDisposable == null) { | |||||
return; | |||||
} | |||||
if (!bubbleCheckDisposable.isDisposed()) { | |||||
bubbleCheckDisposable.dispose(); | |||||
} | |||||
} | |||||
public void disposeProgress() { | |||||
if (progressDisposable == null) { | |||||
return; | |||||
} | |||||
if (!progressDisposable.isDisposed()) { | |||||
progressDisposable.dispose(); | |||||
} | |||||
} | |||||
public MutableLiveData<StatusResource<List<Network>>> checkBubbleOnce(final Context context) { | public MutableLiveData<StatusResource<List<Network>>> checkBubbleOnce(final Context context) { | ||||
final MutableLiveData<StatusResource<List<Network>>> data = new MutableLiveData<>(); | final MutableLiveData<StatusResource<List<Network>>> data = new MutableLiveData<>(); | ||||
final HashMap<String, String> header = new HashMap<>(); | final HashMap<String, String> header = new HashMap<>(); | ||||
@@ -161,12 +181,13 @@ public final class DataRepository { | |||||
final MutableLiveData<StatusResource<List<Network>>> data = new MutableLiveData<>(); | final MutableLiveData<StatusResource<List<Network>>> data = new MutableLiveData<>(); | ||||
final HashMap<String, String> header = new HashMap<>(); | final HashMap<String, String> header = new HashMap<>(); | ||||
header.put(ApiConstants.AUTHORIZATION_HEADER, UserStore.getInstance(context).getSageToken()); | header.put(ApiConstants.AUTHORIZATION_HEADER, UserStore.getInstance(context).getSageToken()); | ||||
clientApi.getNodeBaseURI(header) | |||||
bubbleCheckDisposable = clientApi.getNodeBaseURI(header) | |||||
.subscribeOn(Schedulers.io()) | .subscribeOn(Schedulers.io()) | ||||
.observeOn(AndroidSchedulers.mainThread()) | .observeOn(AndroidSchedulers.mainThread()) | ||||
.repeatWhen(success -> success.delay(DELAY_VALUE, TimeUnit.SECONDS)) | .repeatWhen(success -> success.delay(DELAY_VALUE, TimeUnit.SECONDS)) | ||||
.retryWhen(error -> error.delay(NET_UNAVAILABLE_DELAY_VALUE, TimeUnit.SECONDS)) | .retryWhen(error -> error.delay(NET_UNAVAILABLE_DELAY_VALUE, TimeUnit.SECONDS)) | ||||
.subscribe(new DisposableSubscriber<List<Network>>() { | |||||
.subscribeWith(new DisposableSubscriber<List<Network>>() { | |||||
@Override public void onNext(final List<Network> networks) { | @Override public void onNext(final List<Network> networks) { | ||||
final List<Network> alive = getAliveBubbles(networks); | final List<Network> alive = getAliveBubbles(networks); | ||||
if (!alive.isEmpty()) { | if (!alive.isEmpty()) { | ||||
@@ -366,7 +387,7 @@ public final class DataRepository { | |||||
final HashMap<String, String> header = new HashMap<>(); | final HashMap<String, String> header = new HashMap<>(); | ||||
header.put(ApiConstants.AUTHORIZATION_HEADER, UserStore.getInstance(context).getSageToken()); | header.put(ApiConstants.AUTHORIZATION_HEADER, UserStore.getInstance(context).getSageToken()); | ||||
final Disposable getNetworkStateDisposable = clientApi.getNetworkState( | |||||
progressDisposable = clientApi.getNetworkState( | |||||
CredentialRepository.getInstance(context).getUsername(), network.getUuid(), header | CredentialRepository.getInstance(context).getUsername(), network.getUuid(), header | ||||
) | ) | ||||
.subscribeOn(Schedulers.newThread()) | .subscribeOn(Schedulers.newThread()) | ||||
@@ -393,7 +414,6 @@ public final class DataRepository { | |||||
stopNetworkStatusLiveData = true; | stopNetworkStatusLiveData = true; | ||||
setErrorMessage(throwable, getMutableLiveData()); | setErrorMessage(throwable, getMutableLiveData()); | ||||
}); | }); | ||||
compositeDisposable.add(getNetworkStateDisposable); | |||||
} | } | ||||
private void loginToNetworkWithCheck() { | private void loginToNetworkWithCheck() { | ||||
@@ -492,6 +512,13 @@ public final class DataRepository { | |||||
return new NetworkBoundStatusResource<byte[]>() { | return new NetworkBoundStatusResource<byte[]>() { | ||||
@Override protected void createCall() { | @Override protected void createCall() { | ||||
final String sageHostName = UserStore.getInstance(context).getSageHostname(); | |||||
if (!sageHostName.isEmpty()) { | |||||
nodes = Collections.singletonList(sageHostName); | |||||
getNodeIndex(0, username, password, context, this); | |||||
return; | |||||
} | |||||
final Disposable sagesDisposable = clientApi.getSages() | final Disposable sagesDisposable = clientApi.getSages() | ||||
.subscribeOn(Schedulers.newThread()) | .subscribeOn(Schedulers.newThread()) | ||||
.observeOn(AndroidSchedulers.mainThread()) | .observeOn(AndroidSchedulers.mainThread()) | ||||
@@ -985,6 +1012,7 @@ public final class DataRepository { | |||||
liveData.postValue(StatusResource.error(LOGIN_FAILED)); | liveData.postValue(StatusResource.error(LOGIN_FAILED)); | ||||
} | } | ||||
} else { | } else { | ||||
Log.e("ERR", "Unhandled http error.", throwable); | |||||
liveData.postValue(StatusResource.error(LOGIN_FAILED)); | liveData.postValue(StatusResource.error(LOGIN_FAILED)); | ||||
} | } | ||||
} | } | ||||
@@ -995,27 +1023,29 @@ public final class DataRepository { | |||||
try { | try { | ||||
final Request copy = request.newBuilder().build(); | final Request copy = request.newBuilder().build(); | ||||
final Buffer buffer = new Buffer(); | final Buffer buffer = new Buffer(); | ||||
copy.body().writeTo(buffer); | |||||
final RequestBody body = copy.body(); | |||||
if (body != null) { | |||||
body.writeTo(buffer); | |||||
} | |||||
return buffer.readUtf8(); | return buffer.readUtf8(); | ||||
} catch (final IOException e) { | } catch (final IOException e) { | ||||
return "did not work"; | return "did not work"; | ||||
} | } | ||||
} | } | ||||
public void removeSharedPreferences(Context context) { | |||||
public void removeSharedPreferences(final Context context) { | |||||
context.getSharedPreferences(UserStore.USER_SHARED_PREF, 0).edit().clear().apply(); | context.getSharedPreferences(UserStore.USER_SHARED_PREF, 0).edit().clear().apply(); | ||||
context.getSharedPreferences(TunnelStore.TUNNEL_SHARED_PREF, 0).edit().clear().apply(); | context.getSharedPreferences(TunnelStore.TUNNEL_SHARED_PREF, 0).edit().clear().apply(); | ||||
} | } | ||||
public void deleteTunnel() { | public void deleteTunnel() { | ||||
ArrayList<ObservableTunnel> tunnels = new ArrayList<>(); | |||||
if(pendingTunnel != null) { | |||||
tunnels.add(pendingTunnel); | |||||
if (pendingTunnel != null) { | |||||
Application.getTunnelManager().delete(pendingTunnel); | Application.getTunnelManager().delete(pendingTunnel); | ||||
pendingTunnel = null; | |||||
} | } | ||||
} | } | ||||
public MutableLiveData<StatusResource<Boolean>> isHaveTunnel(Context context) { | |||||
public MutableLiveData<StatusResource<Boolean>> isHaveTunnel(final Context context) { | |||||
return new NetworkBoundStatusResource<Boolean>(){ | return new NetworkBoundStatusResource<Boolean>(){ | ||||
@Override protected void createCall() { | @Override protected void createCall() { | ||||
final byte[] configBytes = UserStore.getInstance(context).getConfig().getBytes(); | final byte[] configBytes = UserStore.getInstance(context).getConfig().getBytes(); | ||||
@@ -5,7 +5,6 @@ import android.content.Context; | |||||
import com.getbubblenow.android.model.Network; | import com.getbubblenow.android.model.Network; | ||||
import com.getbubblenow.android.repository.CredentialRepository; | import com.getbubblenow.android.repository.CredentialRepository; | ||||
import com.getbubblenow.android.repository.DataRepository; | import com.getbubblenow.android.repository.DataRepository; | ||||
import com.getbubblenow.android.model.ObservableTunnel; | |||||
import com.getbubblenow.android.repository.RestringMessageRepository; | import com.getbubblenow.android.repository.RestringMessageRepository; | ||||
import com.getbubblenow.android.resource.StatusResource; | import com.getbubblenow.android.resource.StatusResource; | ||||
import com.getbubblenow.android.util.UserStore; | import com.getbubblenow.android.util.UserStore; | ||||
@@ -22,6 +21,14 @@ public class MainViewModel extends BaseViewModel { | |||||
CredentialRepository.getInstance(context).clear(); | CredentialRepository.getInstance(context).clear(); | ||||
} | } | ||||
public void disposeBubbleCheck() { | |||||
DataRepository.getRepositoryInstance().disposeBubbleCheck(); | |||||
} | |||||
public void disposeProgress() { | |||||
DataRepository.getRepositoryInstance().disposeProgress(); | |||||
} | |||||
public boolean isUserLoggedInToSage(final Context context) { | public boolean isUserLoggedInToSage(final Context context) { | ||||
return !UserStore.SAGE_TOKEN_DEFAULT_VALUE.equals(UserStore.getInstance(context).getSageToken()); | return !UserStore.SAGE_TOKEN_DEFAULT_VALUE.equals(UserStore.getInstance(context).getSageToken()); | ||||
} | } | ||||
@@ -0,0 +1,13 @@ | |||||
package com.getbubblenow.android.viewmodel; | |||||
import android.content.Context; | |||||
import com.getbubblenow.android.util.UserStore; | |||||
public class SetLauncherViewModel extends BaseViewModel { | |||||
public void setSageHost(final Context context, final String sageHost) { | |||||
UserStore.getInstance(context).setSageHostname(sageHost); | |||||
} | |||||
} |
@@ -177,5 +177,17 @@ | |||||
android:textStyle="normal" | android:textStyle="normal" | ||||
android:clickable="true" | android:clickable="true" | ||||
android:focusable="true" /> | android:focusable="true" /> | ||||
<TextView | |||||
android:id="@+id/setLauncherButton" | |||||
android:layout_width="match_parent" | |||||
android:layout_height="wrap_content" | |||||
android:layout_marginTop="@dimen/margin_start_and_end" | |||||
android:text="@string/set_launcher" | |||||
android:textAlignment="center" | |||||
android:textSize="14sp" | |||||
android:textStyle="normal" | |||||
android:clickable="true" | |||||
android:focusable="true" /> | |||||
</LinearLayout> | </LinearLayout> | ||||
</androidx.constraintlayout.widget.ConstraintLayout> | </androidx.constraintlayout.widget.ConstraintLayout> |
@@ -0,0 +1,110 @@ | |||||
<?xml version="1.0" encoding="utf-8"?> | |||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" | |||||
xmlns:app="http://schemas.android.com/apk/res-auto" | |||||
xmlns:tools="http://schemas.android.com/tools" | |||||
android:layout_width="match_parent" | |||||
android:layout_height="match_parent" | |||||
android:background="@color/white" | |||||
tools:context="com.getbubblenow.android.activity.LoginActivity"> | |||||
<androidx.constraintlayout.widget.ConstraintLayout | |||||
android:id="@+id/imageConstraint" | |||||
android:layout_width="match_parent" | |||||
android:layout_height="wrap_content" | |||||
app:layout_constraintEnd_toEndOf="parent" | |||||
app:layout_constraintStart_toStartOf="parent" | |||||
app:layout_constraintTop_toTopOf="parent"> | |||||
<ImageView | |||||
android:id="@+id/color_curve" | |||||
android:layout_width="match_parent" | |||||
android:layout_height="wrap_content" | |||||
android:layout_alignParentStart="true" | |||||
android:layout_alignParentTop="true" | |||||
android:background="@color/white" | |||||
android:scaleType="fitXY" | |||||
android:src="@drawable/color_curves" | |||||
app:layout_constraintBottom_toBottomOf="parent" | |||||
app:layout_constraintEnd_toEndOf="parent" | |||||
app:layout_constraintHorizontal_bias="0.0" | |||||
app:layout_constraintStart_toStartOf="parent" | |||||
app:layout_constraintTop_toTopOf="parent" | |||||
app:layout_constraintVertical_bias="1.0" /> | |||||
<ImageView | |||||
android:layout_width="wrap_content" | |||||
android:layout_height="wrap_content" | |||||
android:layout_marginStart="@dimen/margin_start_and_end" | |||||
android:layout_marginTop="24dp" | |||||
android:layout_marginEnd="@dimen/margin_start_and_end" | |||||
android:src="@drawable/bubble_top" | |||||
app:layout_constraintBottom_toBottomOf="parent" | |||||
app:layout_constraintEnd_toEndOf="parent" | |||||
app:layout_constraintHorizontal_bias="0.51" | |||||
app:layout_constraintStart_toStartOf="parent" | |||||
app:layout_constraintTop_toTopOf="parent" | |||||
app:layout_constraintVertical_bias="0.0" /> | |||||
</androidx.constraintlayout.widget.ConstraintLayout> | |||||
<TextView | |||||
android:id="@+id/set_launcher_title" | |||||
android:layout_width="wrap_content" | |||||
android:layout_height="wrap_content" | |||||
android:layout_marginStart="@dimen/margin_start_and_end" | |||||
android:layout_marginTop="50dp" | |||||
android:layout_marginEnd="@dimen/margin_start_and_end" | |||||
android:fontFamily="@font/josefin_sans_bold" | |||||
android:text="@string/set_launcher_title" | |||||
android:textColor="@color/black" | |||||
android:textSize="@dimen/title" | |||||
app:layout_constraintEnd_toEndOf="parent" | |||||
app:layout_constraintStart_toStartOf="parent" | |||||
app:layout_constraintTop_toBottomOf="@+id/imageConstraint" /> | |||||
<EditText | |||||
android:id="@+id/launcher_url" | |||||
android:layout_width="@dimen/edit_text_width" | |||||
android:layout_height="@dimen/edit_text_height" | |||||
android:layout_marginStart="@dimen/margin_start_and_end" | |||||
android:layout_marginTop="64dp" | |||||
android:layout_marginEnd="@dimen/margin_start_and_end" | |||||
android:background="@drawable/rectangle" | |||||
android:hint="@string/launcher_url_to_sign_in" | |||||
android:inputType="textUri" | |||||
android:textAlignment="center" | |||||
android:textColor="@color/black" | |||||
android:textSize="@dimen/text_title" | |||||
app:layout_constraintEnd_toEndOf="parent" | |||||
app:layout_constraintStart_toStartOf="parent" | |||||
app:layout_constraintTop_toBottomOf="@+id/set_launcher_title" | |||||
android:importantForAutofill="no" /> | |||||
<LinearLayout | |||||
android:id="@+id/linearLayout" | |||||
android:layout_width="@dimen/edit_text_width" | |||||
android:layout_height="@dimen/edit_text_height" | |||||
android:layout_marginStart="@dimen/margin_start_and_end" | |||||
android:layout_marginTop="50dp" | |||||
android:layout_marginEnd="@dimen/margin_start_and_end" | |||||
android:background="@color/white" | |||||
android:orientation="vertical" | |||||
app:layout_constraintEnd_toEndOf="parent" | |||||
app:layout_constraintStart_toStartOf="parent" | |||||
app:layout_constraintTop_toBottomOf="@+id/launcher_url"> | |||||
<androidx.appcompat.widget.AppCompatButton | |||||
android:id="@+id/setLauncherButton" | |||||
android:layout_width="match_parent" | |||||
android:layout_height="match_parent" | |||||
android:background="@drawable/sign_in_disable" | |||||
android:enabled="false" | |||||
android:text="@string/set_launcher_title" | |||||
android:textAlignment="center" | |||||
android:textAllCaps="false" | |||||
android:textColor="@color/white" | |||||
android:textSize="@dimen/text_title" /> | |||||
</LinearLayout> | |||||
</androidx.constraintlayout.widget.ConstraintLayout> |
@@ -269,6 +269,7 @@ | |||||
<string name="building_bubble_title">We Are Building</string> | <string name="building_bubble_title">We Are Building</string> | ||||
<string name="private_bubble">Your Private Bubble!</string> | <string name="private_bubble">Your Private Bubble!</string> | ||||
<string name="no_account_yet">No account yet? <u>Create one.</u></string> | <string name="no_account_yet">No account yet? <u>Create one.</u></string> | ||||
<string name="set_launcher">Running your own Bubble? <u>Set Launcher.</u></string> | |||||
<string name="launch_new_bubble">LAUNCH NEW BUBBLE</string> | <string name="launch_new_bubble">LAUNCH NEW BUBBLE</string> | ||||
<string name="select_your_bubble">Select your Bubble</string> | <string name="select_your_bubble">Select your Bubble</string> | ||||
<string name="select_your_bubble_ok">OK</string> | <string name="select_your_bubble_ok">OK</string> | ||||
@@ -277,4 +278,6 @@ | |||||
<string name="enter_verification_code">Enter Verification Code</string> | <string name="enter_verification_code">Enter Verification Code</string> | ||||
<string name="enter_6_digit_code">Enter 6-digit Code</string> | <string name="enter_6_digit_code">Enter 6-digit Code</string> | ||||
<string name="invalid_mfa_token_format">Invalid MFA token format</string> | <string name="invalid_mfa_token_format">Invalid MFA token format</string> | ||||
<string name="set_launcher_title">Set launcher</string> | |||||
<string name="launcher_url_to_sign_in">Launcher URL to Sign In</string> | |||||
</resources> | </resources> |