Browse Source

Added messages localization

dev
Denys Podymskyy 4 years ago
parent
commit
7a9a783e54
16 changed files with 716 additions and 165 deletions
  1. +7
    -0
      ui/build.gradle
  2. +19
    -4
      ui/src/main/java/com/getbubblenow/android/Application.kt
  3. +20
    -0
      ui/src/main/java/com/getbubblenow/android/activity/BaseActivityBubble.java
  4. +1
    -1
      ui/src/main/java/com/getbubblenow/android/activity/LoginActivity.java
  5. +66
    -47
      ui/src/main/java/com/getbubblenow/android/activity/MainActivity.java
  6. +1
    -0
      ui/src/main/java/com/getbubblenow/android/api/ApiConstants.java
  7. +9
    -0
      ui/src/main/java/com/getbubblenow/android/api/enums/LocalizationDataType.java
  8. +7
    -0
      ui/src/main/java/com/getbubblenow/android/api/network/ClientApi.java
  9. +69
    -50
      ui/src/main/java/com/getbubblenow/android/repository/DataRepository.java
  10. +73
    -0
      ui/src/main/java/com/getbubblenow/android/repository/MessageLocalLoader.java
  11. +100
    -0
      ui/src/main/java/com/getbubblenow/android/repository/MessageRemoteLoader.java
  12. +247
    -0
      ui/src/main/java/com/getbubblenow/android/repository/RestringMessageRepository.java
  13. +59
    -40
      ui/src/main/java/com/getbubblenow/android/util/UserStore.java
  14. +2
    -2
      ui/src/main/java/com/getbubblenow/android/viewmodel/MFAVerifyViewModel.java
  15. +20
    -8
      ui/src/main/java/com/getbubblenow/android/viewmodel/MainViewModel.java
  16. +16
    -13
      ui/src/main/res/values/strings.xml

+ 7
- 0
ui/build.gradle View File

@@ -96,6 +96,13 @@ dependencies {
implementation "com.squareup.retrofit2:adapter-rxjava2:$adapterrxjava2Version"
implementation "com.airbnb.android:lottie:$lottieVersion"
implementation "com.jakewharton.rxbinding4:rxbinding:$rxbindingVersion"

// Replace bundled strings dynamically
implementation 'dev.b3nedikt.restring:restring:4.0.5'
// Intercept view inflation
implementation 'io.github.inflationx:viewpump:2.0.3'
// Allows to update the text of views at runtime without recreating the activity
implementation 'dev.b3nedikt.reword:reword:1.1.0'
}

tasks.withType(JavaCompile) {


+ 19
- 4
ui/src/main/java/com/getbubblenow/android/Application.kt View File

@@ -13,17 +13,21 @@ import android.os.StrictMode.VmPolicy
import android.util.Log
import androidx.appcompat.app.AppCompatDelegate
import androidx.preference.PreferenceManager
import com.wireguard.android.backend.Backend
import com.wireguard.android.backend.GoBackend
import com.wireguard.android.backend.WgQuickBackend
import com.getbubblenow.android.configStore.FileConfigStore
import com.getbubblenow.android.model.TunnelManager
import com.getbubblenow.android.repository.DataRepository
import com.getbubblenow.android.repository.RestringMessageRepository
import com.getbubblenow.android.util.AsyncWorker
import com.getbubblenow.android.util.ExceptionLoggers
import com.wireguard.android.backend.Backend
import com.wireguard.android.backend.GoBackend
import com.wireguard.android.backend.WgQuickBackend
import com.wireguard.android.util.ModuleLoader
import com.wireguard.android.util.RootShell
import com.wireguard.android.util.ToolsInstaller
import dev.b3nedikt.restring.Restring
import dev.b3nedikt.restring.RestringConfig
import dev.b3nedikt.reword.RewordInterceptor
import io.github.inflationx.viewpump.ViewPump
import java9.util.concurrent.CompletableFuture
import java.lang.ref.WeakReference
import java.util.Locale
@@ -157,5 +161,16 @@ class Application : android.app.Application(), OnSharedPreferenceChangeListener

init {
weakSelf = WeakReference(this)

Restring.init(this,
RestringConfig.Builder()
.stringRepository(RestringMessageRepository.getInstance())
.build()
)

ViewPump.init(ViewPump.builder()
.addInterceptor(RewordInterceptor)
.build()
)
}
}

+ 20
- 0
ui/src/main/java/com/getbubblenow/android/activity/BaseActivityBubble.java View File

@@ -1,5 +1,7 @@
package com.getbubblenow.android.activity;

import android.content.Context;
import android.content.res.Resources;
import android.os.Bundle;
import android.os.Handler;
import android.view.Gravity;
@@ -16,6 +18,9 @@ import com.getbubblenow.android.repository.DataRepository;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.lifecycle.Lifecycle;
import dev.b3nedikt.restring.Restring;
import dev.b3nedikt.reword.Reword;
import io.github.inflationx.viewpump.ViewPumpContextWrapper;

public class BaseActivityBubble extends AppCompatActivity{

@@ -24,6 +29,7 @@ public class BaseActivityBubble extends AppCompatActivity{
public static final String ERROR_TAG = "error_tag";
public static final String PROGRESS_TAG = "progress_tag";
public static final String RATE_TAG = "rate tag";
public static final String LOCALIZATION_TAG = "localization_tag";
private final long LOADER_DELAY = 1000;

private LoadingDialogFragment loadingDialog;
@@ -38,6 +44,20 @@ public class BaseActivityBubble extends AppCompatActivity{
} else {
DataRepository.getRepositoryInstance().buildClientService(ApiConstants.BOOTSTRAP_URL);
}

// The layout containing the views you want to localize
final View rootView = getWindow().getDecorView().findViewById(android.R.id.content);
Reword.reword(rootView);
}

@Override
protected void attachBaseContext(final Context newBase) {
super.attachBaseContext(ViewPumpContextWrapper.wrap(Restring.wrapContext(newBase)));
}

@Override
public Resources getResources() {
return Restring.wrapContext(getBaseContext()).getResources();
}




+ 1
- 1
ui/src/main/java/com/getbubblenow/android/activity/LoginActivity.java View File

@@ -169,7 +169,7 @@ public class LoginActivity extends BaseActivityBubble {
if (statusResource.message.equals(NO_INTERNET_CONNECTION)) {
showNetworkNotAvailableMessage();
} else if (statusResource.message.equals(LOGIN_FAILED)) {
Toast.makeText(this, LOGIN_FAILED, Toast.LENGTH_LONG).show();
Toast.makeText(this, getString(R.string.login_failed), Toast.LENGTH_LONG).show();
} else {
showErrorDialog(statusResource.message);
}


+ 66
- 47
ui/src/main/java/com/getbubblenow/android/activity/MainActivity.java View File

@@ -30,6 +30,8 @@ import com.getbubblenow.android.util.UserStore;
import com.getbubblenow.android.viewmodel.MainViewModel;
import com.getbubblenow.android.R;

import java.util.Locale;

public class MainActivity extends BaseActivityBubble {

private static final int PROGRESS_ANIMATION_DURATION = 2000;
@@ -54,7 +56,6 @@ public class MainActivity extends BaseActivityBubble {
private static final String NO_INTERNET_CONNECTION = "no internet connection";
private static final String LOGIN_FAILED = "Login Failed";
private static final int REQUEST_CODE = 1555;
private static final String CERTIFICATE_NAME = "Bubble Certificate";
private static final String SUPPORT_URL = "https://support.getbubblenow.com";
private static final String AUTH_TYPE_KEY = "authType";

@@ -89,18 +90,40 @@ public class MainActivity extends BaseActivityBubble {
super.onCreate(savedInstanceState);
mainViewModel = new ViewModelProvider(this).get(MainViewModel.class);
showLoadingDialog();

final MutableLiveData<Boolean> data = (mainViewModel.isUserLoggedInToSage(this)) ?
mainViewModel.updatePostAuthLocalization(this, Locale.getDefault())
: mainViewModel.updatePreAuthLocalization(this, Locale.getDefault());

data.observe(this, isLocalizationDataUpdated -> {
if (isLocalizationDataUpdated) {
Log.d(LOCALIZATION_TAG, "Success");
} else {
Log.d(LOCALIZATION_TAG, "Fail");
}
startBubbleSession();
});
}

private void startBubbleSession() {
if (mainViewModel.isHaveSageURL(this)) {
setContentView(R.layout.activity_main);
initUI();
if (mainViewModel.isHaveHostName(this)) {
closeLoadingDialog();
setContentView(R.layout.activity_main);
mainViewModel.buildRepositoryInstance(this, mainViewModel.getSageURL(this));
initUI();
logout.setEnabled(true);
connectButton.setEnabled(true);
connectButton.setVisibility(View.VISIBLE);

if (mainViewModel.isVPNConnected(this, connectionStateFlag)) {
connectionStateFlag = false;
setConnectionStateUI(false);
} else {
connectionStateFlag = true;
setConnectionStateUI(true);
}
} else {
setContentView(R.layout.activity_main);
initUI();
logout.setEnabled(true);
if (mainViewModel.checkRepositoryInstance()) {
mainViewModel.buildRepositoryInstance(this, mainViewModel.getSageURL(this));
@@ -109,8 +132,7 @@ public class MainActivity extends BaseActivityBubble {
}
checkBubbleCurrentStatus();
}
}
else{
} else {
closeLoadingDialog();
final Intent intent = new Intent(this, LoginActivity.class);
startActivity(intent);
@@ -119,35 +141,48 @@ public class MainActivity extends BaseActivityBubble {
}

private void checkBubbleCurrentStatus() {
mainViewModel.checkBubble(this);
mainViewModel.getBubbleCheckLiveData(this).observe(this, availableBubbleList -> {
mainViewModel.getBubbleCheckLiveData(this).removeObservers(this);
mainViewModel.checkBubbleOnce(this).observe(this, statusResource -> {
closeLoadingDialog();
if (availableBubbleList.isEmpty()) {
setNonAvailableBubbleUI();
requestBubbleStatusContinuously();
} else {
if (availableBubbleList.size() == 1) {
getCertificateData(availableBubbleList.get(0).getUuid());
} else {
networkListDialogFragment = new NetworkListDialogFragment(
availableBubbleList, (view, bubble) -> {
switch (statusResource.status) {
case SUCCESS:
if (statusResource.data.isEmpty()) {
setNonAvailableBubbleUI();
requestBubbleStatusContinuously();
} else {
if (statusResource.data.size() == 1) {
getCertificateData(statusResource.data.get(0).getUuid());
} else {
networkListDialogFragment = new NetworkListDialogFragment(
statusResource.data, (view, bubble) -> {
getCertificateData(bubble.getUuid());
networkListDialogFragment.dismiss();
}
);
networkListDialogFragment.show(getSupportFragmentManager(), "networkListDialog");
}
);
networkListDialogFragment.show(getSupportFragmentManager(), "networkListDialog");
}
}
break;
case ERROR:
setNonAvailableBubbleUI();
if (NO_INTERNET_CONNECTION.equals(statusResource.message)) {
showNetworkNotAvailableMessage();
requestBubbleStatusContinuously();
} else {
showErrorDialog(statusResource.message);
}
break;
}
});
}

private void requestBubbleStatusContinuously() {
mainViewModel.getBubbleCheckLiveData(this).observe(this, availableBubbleList -> {
if (!availableBubbleList.isEmpty()) {
setConnectedBubbleUI();
mainViewModel.getBubbleCheckLiveData(this).removeObservers(MainActivity.this);
getCertificateData(availableBubbleList.get(0).getUuid());
mainViewModel.checkBubbleContinuously(this).observe(this, statusResource -> {
switch (statusResource.status) {
case SUCCESS:
if (!statusResource.data.isEmpty()) {
setConnectedBubbleUI();
getCertificateData(statusResource.data.get(0).getUuid());
}
}
});
}
@@ -221,7 +256,7 @@ public class MainActivity extends BaseActivityBubble {
} else {
final Intent intent = KeyChain.createInstallIntent();
intent.putExtra(KeyChain.EXTRA_CERTIFICATE, certificateEncode);
intent.putExtra(KeyChain.EXTRA_NAME, CERTIFICATE_NAME);
intent.putExtra(KeyChain.EXTRA_NAME, getString(R.string.certificate_name));
closeLoadingDialog();
isNeedConnection = false;
startActivityForResult(intent, REQUEST_CODE);
@@ -244,21 +279,6 @@ public class MainActivity extends BaseActivityBubble {
}
}

@Override protected void onResume() {
super.onResume();
if (mainViewModel.isHaveSageURL(this)) {
if (mainViewModel.isHaveHostName(this)) {
if (mainViewModel.isVPNConnected(this, connectionStateFlag)) {
connectionStateFlag = false;
setConnectionStateUI(false);
} else {
connectionStateFlag = true;
setConnectionStateUI(true);
}
}
}
}

private void initUI() {
initViews();
initListeners();
@@ -351,7 +371,7 @@ public class MainActivity extends BaseActivityBubble {
closeLoadingDialog();
final Intent intent = KeyChain.createInstallIntent();
intent.putExtra(KeyChain.EXTRA_CERTIFICATE, certificateEncode);
intent.putExtra(KeyChain.EXTRA_NAME, CERTIFICATE_NAME);
intent.putExtra(KeyChain.EXTRA_NAME, getString(R.string.certificate_name));
isNeedConnection = true;
startActivityForResult(intent, REQUEST_CODE);
}
@@ -380,7 +400,7 @@ public class MainActivity extends BaseActivityBubble {
if (NO_INTERNET_CONNECTION.equals(objectStatusResource.message)) {
showNetworkNotAvailableMessage();
} else if (LOGIN_FAILED.equals(objectStatusResource.message)) {
Toast.makeText(this, LOGIN_FAILED, Toast.LENGTH_LONG).show();
Toast.makeText(this, getString(R.string.login_failed), Toast.LENGTH_LONG).show();
} else {
showErrorDialog(objectStatusResource.message);
}
@@ -409,7 +429,7 @@ public class MainActivity extends BaseActivityBubble {
} else {
logout.setEnabled(true);
connectButton.setEnabled(true);
Toast.makeText(this, getString(R.string.cerificate_install), Toast.LENGTH_LONG).show();
Toast.makeText(this, getString(R.string.certificate_install), Toast.LENGTH_LONG).show();
}
}
}
@@ -439,7 +459,6 @@ public class MainActivity extends BaseActivityBubble {
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
mainViewModel.deleteTunnel();
mainViewModel.removeSharedPreferences(this);
mainViewModel.logout(this);
startActivity(intent);
}



+ 1
- 0
ui/src/main/java/com/getbubblenow/android/api/ApiConstants.java View File

@@ -19,4 +19,5 @@ public class ApiConstants {
public static final String DEVICE_TYPE = "deviceType";
public static final String CERTIFICATE_URL = "auth/cacert?deviceType=android";
public static final String NETWORK_STATUS = "users/{userId}/networks/{networkId}/actions/status";
public static final String LOCALIZATION = "messages/{locale}/{bundle}";
}

+ 9
- 0
ui/src/main/java/com/getbubblenow/android/api/enums/LocalizationDataType.java View File

@@ -0,0 +1,9 @@
package com.getbubblenow.android.api.enums;

/**
* Enum for localization data type. It can be before user logged in and after.
**/
public enum LocalizationDataType {
PRE_AUTH,
POST_AUTH
}

+ 7
- 0
ui/src/main/java/com/getbubblenow/android/api/network/ClientApi.java View File

@@ -9,6 +9,7 @@ import com.getbubblenow.android.api.ApiConstants;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

import io.reactivex.Single;
import okhttp3.ResponseBody;
@@ -51,4 +52,10 @@ public interface ClientApi {

@GET(ApiConstants.NETWORK_STATUS)
Single<List<NetworkStatus>> getNetworkState(@Path("userId") String userId , @Path("networkId") String networkId , @HeaderMap HashMap<String,String> header);

@GET(ApiConstants.LOCALIZATION)
Single<Map<String, String>> getMessageBundle(@Path("locale") String locale, @Path("bundle") String bundle, @HeaderMap HashMap<String,String> header);

@GET(ApiConstants.LOCALIZATION)
Single<Map<String, String>> getMessageBundle(@Path("locale") String locale, @Path("bundle") String bundle);
}

+ 69
- 50
ui/src/main/java/com/getbubblenow/android/repository/DataRepository.java View File

@@ -3,6 +3,7 @@ package com.getbubblenow.android.repository;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.os.Handler;
import android.provider.Settings;
import android.provider.Settings.Secure;
import android.util.Log;
@@ -60,7 +61,9 @@ import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.disposables.Disposable;
import io.reactivex.functions.Predicate;
import io.reactivex.observers.DisposableSingleObserver;
import io.reactivex.schedulers.Schedulers;
import io.reactivex.subscribers.DisposableSubscriber;
import okhttp3.Request;
import okhttp3.ResponseBody;
import okio.Buffer;
@@ -69,8 +72,12 @@ import retrofit2.HttpException;
import retrofit2.Response;

public final class DataRepository {

private static final String TAG = "DataRepository";

private static final int DELAY_VALUE = 10;
private static final int PROGRESS_DELAY_VALUE = 5;
private static final int NET_UNAVAILABLE_DELAY_VALUE = 10;
private static final int HTTP_422_ERR_CODE = 422;
private static final int HTTP_500_ERR_CODE = 500;
private static final Long ZERO_PROGRESS = 0L;
@@ -107,9 +114,6 @@ public final class DataRepository {
private final MutableLiveData<StatusResource<Network>> networkStatusLiveData = new MutableLiveData<>();
private final MutableLiveData<Long> progressLiveData = new MutableLiveData<>();

private final MutableLiveData<List<Network>> bubbleCheckLiveData = new MutableLiveData<>();
private Disposable bubbleCheckDisposable;

private DataRepository(final Context context, final String url) {
baseUrl = url;
clientApi = ClientService.getInstance().createClientApi(url);
@@ -127,15 +131,7 @@ public final class DataRepository {
}
}

public void logout(final Context context) {
if (bubbleCheckDisposable != null) {
bubbleCheckDisposable.dispose();
bubbleCheckDisposable = null;
bubbleCheckLiveData.removeObservers((LifecycleOwner) context);
}
}

public void buildClientService(final String url) {
public void buildClientService(String url) {
baseUrl = url;
clientApi = ClientService.getInstance().createClientApi(url);
}
@@ -144,32 +140,55 @@ public final class DataRepository {
return instance;
}

public MutableLiveData<List<Network>> getBubbleCheckLiveData() {
return bubbleCheckLiveData;
public MutableLiveData<StatusResource<List<Network>>> checkBubbleOnce(final Context context) {
final MutableLiveData<StatusResource<List<Network>>> data = new MutableLiveData<>();
final HashMap<String, String> header = new HashMap<>();
header.put(ApiConstants.AUTHORIZATION_HEADER, UserStore.getInstance(context).getSageToken());
clientApi.getNodeBaseURI(header)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new DisposableSingleObserver<List<Network>>() {
@Override public void onSuccess(final List<Network> networks) {
data.postValue(StatusResource.success(getAliveBubbles(networks)));
}
@Override public void onError(final Throwable throwable) {
setErrorMessage(throwable, data);
final String message = throwable.getMessage() == null ? "" : throwable.getMessage();
Log.e(TAG, "Failed to acquire list of bubble networks from the remote server. " + message);
Log.e(TAG, Arrays.toString(throwable.getStackTrace()));
}
});
return data;
}

public void checkBubble(final Context context) {

if (bubbleCheckDisposable != null) {
return;
}

public MutableLiveData<StatusResource<List<Network>>> checkBubbleContinuously(final Context context) {
final MutableLiveData<StatusResource<List<Network>>> data = new MutableLiveData<>();
final HashMap<String, String> header = new HashMap<>();
header.put(ApiConstants.AUTHORIZATION_HEADER, UserStore.getInstance(context).getSageToken());

bubbleCheckDisposable = clientApi.getNodeBaseURI(header)
clientApi.getNodeBaseURI(header)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.retry((attempts, error) -> attempts < 5 && error instanceof IOException)
.repeatWhen(completed -> completed.delay(DELAY_VALUE, TimeUnit.SECONDS))
.subscribe(networks -> {
final List<Network> alive = getAliveBubbles(networks);
bubbleCheckLiveData.postValue(alive);
if (!alive.isEmpty()) {
bubbleCheckDisposable.dispose();
bubbleCheckDisposable = null;
.repeatWhen(success -> success.delay(DELAY_VALUE, TimeUnit.SECONDS))
.retryWhen(error -> error.delay(NET_UNAVAILABLE_DELAY_VALUE, TimeUnit.SECONDS))
.subscribe(new DisposableSubscriber<List<Network>>() {
@Override public void onNext(final List<Network> networks) {
final List<Network> alive = getAliveBubbles(networks);
if (!alive.isEmpty()) {
data.postValue(StatusResource.success(alive));
onComplete();
}
}

@Override public void onError(final Throwable throwable) {
Log.e(TAG, "Failed to acquire list of bubble networks from the remote server. "
+ throwable.getMessage(), throwable);
}

@Override public void onComplete() {
dispose();
}
});
return data;
}

private static List<Network> getAliveBubbles(final List<Network> networkList) {
@@ -303,7 +322,7 @@ public final class DataRepository {
if (message != null) {
Log.e("ERR", message);
}
setErrorMessage(throwable, liveData);
setErrorMessage(throwable, liveData.getMutableLiveData());
});
compositeDisposable.add(getNodeBaseURIDisposable);
}
@@ -333,7 +352,7 @@ public final class DataRepository {
}
}, throwable -> {
Log.e("ERR", "getNetworkState");
setErrorMessage(throwable, this);
setErrorMessage(throwable, this.getMutableLiveData());
});
compositeDisposable.add(getNetworkStateDisposable);
}
@@ -360,7 +379,7 @@ public final class DataRepository {
}
}, throwable -> {
Log.d("ERR", "getNodeBaseURI");
setErrorMessage(throwable, this);
setErrorMessage(throwable, this.getMutableLiveData());
});
compositeDisposable.add(getNodeBaseURIDisposable);
}
@@ -417,7 +436,7 @@ public final class DataRepository {
}
}, throwable -> {
Log.e("ERR", "Bootstrap URL cannot be reached.");
setErrorMessage(throwable, this);
setErrorMessage(throwable, this.getMutableLiveData());
});
compositeDisposable.add(sagesDisposable);
}
@@ -438,7 +457,7 @@ public final class DataRepository {
getNodeIndex(nodeIndex, username, password, context, this);
}, throwable -> {
Log.d("ERR", "getSages");
setErrorMessage(throwable, this);
setErrorMessage(throwable, this.getMutableLiveData());
});
compositeDisposable.add(sagesDisposable);
}
@@ -543,7 +562,7 @@ public final class DataRepository {
liveData.postMutableLiveData(StatusResource.auth(MFAuthType.TOTP));
} else {
Log.d("ERR", "login");
setErrorMessage(throwable, liveData);
setErrorMessage(throwable, liveData.getMutableLiveData());
}
});
}
@@ -572,7 +591,7 @@ public final class DataRepository {
}
}, throwable -> {
Log.d("ERR", "getAllDevices");
setErrorMessage(throwable, liveData);
setErrorMessage(throwable, liveData.getMutableLiveData());
});
compositeDisposable.add(disposableAllDevices);
}
@@ -654,7 +673,7 @@ public final class DataRepository {
});
// getConfig(context);
}, throwable -> {
setErrorMessage(throwable, liveData);
setErrorMessage(throwable, liveData.getMutableLiveData());
Log.d("ERR", "addDevice5");
// setMutableLiveData(StatusResource.error(throwable.getMessage()));
});
@@ -711,7 +730,7 @@ public final class DataRepository {
// getConfig(context);
}, throwable -> {
Log.d("ERR", "addDevice10");
setErrorMessage(throwable, liveData);
setErrorMessage(throwable, liveData.getMutableLiveData());
// setMutableLiveData(StatusResource.error(throwable.getMessage()));
});
compositeDisposable.add(disposableAddDevice);
@@ -719,7 +738,7 @@ public final class DataRepository {
}
}
}, throwable -> {
setErrorMessage(throwable, liveData);
setErrorMessage(throwable, liveData.getMutableLiveData());
Log.d("ERR", "getAllDevices2");
// setMutableLiveData(StatusResource.error(NO_INTERNET_CONNECTION));
});
@@ -748,7 +767,7 @@ public final class DataRepository {
postMutableLiveData(StatusResource.success(data));
}, throwable -> {
Log.d("ERR", "getConfig");
setErrorMessage(throwable, this);
setErrorMessage(throwable, this.getMutableLiveData());
});
compositeDisposable.add(configDisposable);
}
@@ -772,12 +791,12 @@ public final class DataRepository {
postMutableLiveData(StatusResource.success(null));
} else {
Log.d("ERR", "createTunnel11");
setErrorMessage(throwable, this);
setErrorMessage(throwable, this.getMutableLiveData());
}
});
} catch (Exception e) {
Log.d("ERR", "createTunnel12");
setErrorMessage(e, this);
setErrorMessage(e, this.getMutableLiveData());
}
}
}.getMutableLiveData();
@@ -899,7 +918,7 @@ public final class DataRepository {
x509Certificate = X509Certificate.getInstance(cert);
} catch (final CertificateException e) {
Log.d("ERR", "getCertificate12");
setErrorMessage(e, this);
setErrorMessage(e, this.getMutableLiveData());
}
try {
if (x509Certificate != null) {
@@ -908,11 +927,11 @@ public final class DataRepository {
}
} catch (final CertificateEncodingException e) {
Log.d("ERR", "getCertificate13");
setErrorMessage(e, this);
setErrorMessage(e, this.getMutableLiveData());
}
}, throwable -> {
Log.d("ERR", "getCertificate14");
setErrorMessage(throwable, this);
setErrorMessage(throwable, this.getMutableLiveData());
});
compositeDisposable.add(certificateDisposable);
}
@@ -983,9 +1002,9 @@ public final class DataRepository {
return UserStore.getInstance(context).getHostname();
}

private <T> void setErrorMessage(Throwable throwable, NetworkBoundStatusResource<T> liveData) {
private <T> void setErrorMessage(Throwable throwable, MutableLiveData<StatusResource<T>> liveData) {
if (throwable instanceof IOException) {
liveData.postMutableLiveData(StatusResource.error(NO_INTERNET_CONNECTION));
liveData.postValue(StatusResource.error(NO_INTERNET_CONNECTION));
}
if (throwable instanceof HttpException) {
if (((HttpException) throwable).code() == HTTP_500_ERR_CODE) {
@@ -997,9 +1016,9 @@ public final class DataRepository {
"BODY:" + requestBody + '\n' +
"METHOD:" + requestMethod + '\n' +
"STACK_TRACE:" + stackTrace;
liveData.postMutableLiveData(StatusResource.error(message));
liveData.postValue(StatusResource.error(message));
} else {
liveData.postMutableLiveData(StatusResource.error(LOGIN_FAILED));
liveData.postValue(StatusResource.error(LOGIN_FAILED));
}
}
}


+ 73
- 0
ui/src/main/java/com/getbubblenow/android/repository/MessageLocalLoader.java View File

@@ -0,0 +1,73 @@
package com.getbubblenow.android.repository;

import android.content.Context;
import android.util.Log;

import com.getbubblenow.android.api.enums.LocalizationDataType;
import com.getbubblenow.android.util.UserStore;
import com.google.gson.Gson;
import com.google.gson.JsonSyntaxException;

import java.util.Arrays;
import java.util.Locale;
import java.util.Map;

import androidx.lifecycle.MutableLiveData;
import io.reactivex.Completable;

/*package*/ class MessageLocalLoader {

private static final String TAG = "MessageLocalLoader";

/*package*/ MutableLiveData<Map<String, String>> updatePreAuth(final Context context, final Locale locale) {
return update(UserStore.getInstance(context).getLocalizationPre());
}

/*package*/ MutableLiveData<Map<String, String>> updatePostAuth(final Context context, final Locale locale) {
return update(UserStore.getInstance(context).getLocalizationPost());
}

private MutableLiveData<Map<String, String>> update(final String data) {
final MutableLiveData<Map<String, String>> mutableLiveData = new MutableLiveData<>();
final Gson gson = new Gson();
try {
final Map<String, String> map = gson.fromJson(data, Map.class);
mutableLiveData.postValue(map);
} catch (final JsonSyntaxException ex) {
final String message = ex.getMessage() == null ? "" : ex.getMessage();
Log.e(TAG, "Failed to deserialize localization data from the local storage. " + message);
Log.e(TAG, Arrays.toString(ex.getStackTrace()));
mutableLiveData.postValue(null);
}
return mutableLiveData;
}


/*package*/ Completable savePreAuth(final Map<String, String> map, final Context context, final Locale locale) {
return Completable.fromRunnable(() -> saveToSharedPrefs(map, context, locale, LocalizationDataType.PRE_AUTH));
}

/*package*/ Completable savePostAuth(final Map<String, String> map, final Context context, final Locale locale) {
return Completable.fromRunnable(() -> saveToSharedPrefs(map, context, locale, LocalizationDataType.POST_AUTH));
}

private void saveToSharedPrefs(
final Map<String, String> map,
final Context context,
final Locale locale,
final LocalizationDataType localizationDataType) {
final Gson gson = new Gson();
final String data = gson.toJson(map);

switch (localizationDataType) {
case PRE_AUTH:
UserStore.getInstance(context).setLocalizationPre(data);
break;
case POST_AUTH:
UserStore.getInstance(context).setLocalizationPost(data);
break;
default:
throw new IllegalStateException("Message localization data type is not defined.");
}
}
}

+ 100
- 0
ui/src/main/java/com/getbubblenow/android/repository/MessageRemoteLoader.java View File

@@ -0,0 +1,100 @@
package com.getbubblenow.android.repository;

import android.content.Context;
import android.util.Log;

import com.getbubblenow.android.api.ApiConstants;
import com.getbubblenow.android.api.network.ClientApi;
import com.getbubblenow.android.api.network.ClientService;
import com.getbubblenow.android.model.Sages;
import com.getbubblenow.android.util.UserStore;

import java.util.Arrays;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;

import androidx.lifecycle.MutableLiveData;
import io.reactivex.Single;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.observers.DisposableSingleObserver;
import io.reactivex.schedulers.Schedulers;

/*package*/ class MessageRemoteLoader {

private static final String TAG = "MessageRemoteLoader";

private static final String PRE_AUTH_BUNDLE = "pre_auth";
private static final String POST_AUTH_BUNDLE = "post_auth";
private static final String API_SUFFIX = "/api/";

private ClientApi clientApi;

/*package*/ MessageRemoteLoader() {
clientApi = ClientService.getInstance().createClientApi(ApiConstants.BOOTSTRAP_URL);
}

/*package*/ MutableLiveData<Map<String, String>> updatePreAuth(final Context context, final Locale locale) {
final MutableLiveData<Map<String, String>> mutableLiveData = new MutableLiveData<>();
clientApi.getSages()
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new DisposableSingleObserver<Sages>() {
@Override public void onSuccess(final Sages sages) {
// just get the first available
final String url = sages.getSages().get(0) + API_SUFFIX;
update(getLocaleBundle(locale), url, null, mutableLiveData);
dispose();
}

@Override public void onError(final Throwable e) {
final String message = e.getMessage() == null ? "" : e.getMessage();
Log.e(TAG, "Failed to connect to the remote server. " + message);
Log.e(TAG, Arrays.toString(e.getStackTrace()));
mutableLiveData.postValue(null);
dispose();
}
});
return mutableLiveData;
}

/*package*/ MutableLiveData<Map<String, String>> updatePostAuth(final Context context, final Locale locale) {
final MutableLiveData<Map<String, String>> mutableLiveData = new MutableLiveData<>();
final HashMap<String, String> header = new HashMap<>();
header.put(ApiConstants.AUTHORIZATION_HEADER, UserStore.getInstance(context).getSageToken());
final String url = UserStore.getInstance(context).getSageURL();
update(getLocaleBundle(locale), url, header, mutableLiveData);
return mutableLiveData;
}

private void update(
final String localeBundle,
final String url,
final HashMap<String, String> header ,
final MutableLiveData<Map<String, String>> mutableLiveData) {
clientApi = ClientService.getInstance().createClientApi(url);
final Single<Map<String, String>> getMessageBundle = (header == null) ? clientApi.getMessageBundle(localeBundle, PRE_AUTH_BUNDLE)
: clientApi.getMessageBundle(localeBundle, POST_AUTH_BUNDLE, header);
getMessageBundle
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new DisposableSingleObserver<Map<String, String>>() {
@Override public void onSuccess(final Map<String, String> map) {
mutableLiveData.postValue(map);
dispose();
}

@Override public void onError(final Throwable e) {
final String message = e.getMessage() == null ? "" : e.getMessage();
Log.e(TAG, "Failed to get localization data from the remote server. " + message);
Log.e(TAG, Arrays.toString(e.getStackTrace()));
mutableLiveData.postValue(null);
dispose();
}
});
}

private String getLocaleBundle(final Locale locale) {
return locale.getLanguage() + '_' + locale.getCountry();
}
}

+ 247
- 0
ui/src/main/java/com/getbubblenow/android/repository/RestringMessageRepository.java View File

@@ -0,0 +1,247 @@
package com.getbubblenow.android.repository;

import android.content.Context;
import android.util.Log;

import com.getbubblenow.android.api.enums.LocalizationDataType;

import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import androidx.lifecycle.LifecycleOwner;
import androidx.lifecycle.MutableLiveData;
import dev.b3nedikt.restring.PluralKeyword;
import dev.b3nedikt.restring.StringRepository;
import io.reactivex.Completable;
import io.reactivex.observers.DisposableCompletableObserver;


public final class RestringMessageRepository implements StringRepository {

private static final String PLURALS_PREFIX = "plurals_";
private static final String PLURAL_ITEM_DELIMITER = "\\|";
private static final String PLURAL_ITEM_KEY_VALUE_DELIMITER = "=";
private static final String ONE = "one";
private static final String OTHER = "other";
private static final String TAG = "RestringMessageRepository";

private static final List<Locale> APP_LOCALES = Arrays.asList(Locale.ENGLISH, Locale.US);

private static volatile RestringMessageRepository instance;
private final MessageRemoteLoader remoteLoader = new MessageRemoteLoader();
private final MessageLocalLoader localLoader = new MessageLocalLoader();

private final Map<String, CharSequence> strings = new HashMap<>();
private final Map<String, CharSequence[]> stringArrays = new HashMap<>();
private final Map<String, Map<PluralKeyword, CharSequence>> quantityStrings = new HashMap<>();


public static RestringMessageRepository getInstance() {
if (instance == null) {
synchronized (RestringMessageRepository.class) {
if (instance == null) {
instance = new RestringMessageRepository();
}
}
}
return instance;
}

public MutableLiveData<Boolean> updatePreAuthLocalization(final Context context, final Locale locale) {
return update(context, locale, LocalizationDataType.PRE_AUTH);
}

public MutableLiveData<Boolean> updatePostAuthLocalization(final Context context, final Locale locale) {
return update(context, locale, LocalizationDataType.POST_AUTH);
}

private MutableLiveData<Boolean> update(
final Context context,
final Locale locale,
final LocalizationDataType localizationDataType) {
final MutableLiveData<Boolean> mutableLiveData = new MutableLiveData<>();

final MutableLiveData<Map<String, String>> data;
switch (localizationDataType) {
case PRE_AUTH:
data = remoteLoader.updatePreAuth(context, locale);
break;
case POST_AUTH:
data = remoteLoader.updatePostAuth(context, locale);
break;
default:
throw new IllegalStateException("Message localization data type is not defined.");
}

data.observe((LifecycleOwner) context, map -> {
if (map == null || map.isEmpty()) {
updateFromLocal(context, locale, localizationDataType, mutableLiveData);
} else {
processStrings(map, locale);

saveToLocal(map, context, locale, localizationDataType).subscribe(new DisposableCompletableObserver() {
@Override
public void onComplete() {
Log.d(TAG, "Data is saved to the local storage.");
}
@Override
public void onError(final Throwable e) {
final String message = e.getMessage() == null ? "" : e.getMessage();
Log.e(TAG, "Failed to save data to the local storage. " + message);
Log.e(TAG, Arrays.toString(e.getStackTrace()));
}
});

mutableLiveData.postValue(true);
}
});
return mutableLiveData;
}

private void updateFromLocal(
final Context context,
final Locale locale,
final LocalizationDataType localizationDataType,
final MutableLiveData<Boolean> mutableLiveData) {

final MutableLiveData<Map<String, String>> data;
switch (localizationDataType) {
case PRE_AUTH:
data = localLoader.updatePreAuth(context, locale);
break;
case POST_AUTH:
data = localLoader.updatePostAuth(context, locale);
break;
default:
throw new IllegalStateException("Message localization data type is not defined.");
}

data.observe((LifecycleOwner) context, map -> {
if (map == null || map.isEmpty()) {
mutableLiveData.postValue(false);
} else {
processStrings(map, locale);
mutableLiveData.postValue(true);
}
});
}

private Completable saveToLocal(
final Map<String, String> map,
final Context context,
final Locale locale,
final LocalizationDataType localizationDataType) {
final Completable saveCompletable;
switch (localizationDataType) {
case PRE_AUTH:
saveCompletable = localLoader.savePreAuth(map, context, locale);
break;
case POST_AUTH:
saveCompletable = localLoader.savePostAuth(map, context, locale);
break;
default:
throw new IllegalStateException("Message localization data type is not defined.");
}
return saveCompletable;
}

private void processStrings(final Map<String, String> map, final Locale locale) {
for (final Entry<String, String> entry : map.entrySet()) {
final String key = entry.getKey();
if (key.startsWith(PLURALS_PREFIX)) {
processEntryValue(key.substring(PLURALS_PREFIX.length()), entry.getValue(), locale);
} else {
setString(locale, key, entry.getValue());
}
}
}

private void processEntryValue(final String key, final String value, final Locale locale) {
final Map<PluralKeyword, CharSequence> map = new HashMap<>();
final String[] pluralItems = value.split(PLURAL_ITEM_DELIMITER);

for (final String pluralItem : pluralItems) {
final int idx = pluralItem.indexOf(PLURAL_ITEM_KEY_VALUE_DELIMITER);
final String pluralKey = pluralItem.substring(0, idx);
final String pluralValue = pluralItem.substring(idx + 1);

switch (pluralKey) {
case ONE:
map.put(PluralKeyword.ONE, pluralValue);
break;
case OTHER:
map.put(PluralKeyword.OTHER, pluralValue);
break;
}
}
setQuantityString(locale, key, map);
}


@Nullable @Override public Map<PluralKeyword, CharSequence> getQuantityString(@NotNull final Locale locale, @NotNull final String s) {
return quantityStrings.get(s);
}

@NotNull @Override public Map<String, Map<PluralKeyword, CharSequence>> getQuantityStrings(@NotNull final Locale locale) {
return quantityStrings;
}

@Nullable @Override public CharSequence getString(@NotNull final Locale locale, @NotNull final String s) {
return strings.get(s);
}

@Nullable @Override public CharSequence[] getStringArray(@NotNull final Locale locale, @NotNull final String s) {
return stringArrays.get(s);
}

@NotNull @Override public Map<String, CharSequence[]> getStringArrays(@NotNull final Locale locale) {
return stringArrays;
}

@NotNull @Override public Map<String, CharSequence> getStrings(@NotNull final Locale locale) {
return strings;
}

@NotNull @Override public Set<Locale> getSupportedLocales() {
return new HashSet<>(APP_LOCALES);
}

@Override public void setQuantityString(@NotNull final Locale locale, @NotNull final String s, @NotNull final Map<PluralKeyword, ? extends CharSequence> map) {
quantityStrings.put(s, (Map<PluralKeyword, CharSequence>) map);
}

@Override public void setQuantityStrings(@NotNull final Locale locale, @NotNull final Map<String, ? extends Map<PluralKeyword, ? extends CharSequence>> map) {
for (final Entry<String, ? extends Map<PluralKeyword, ? extends CharSequence>> entry : map.entrySet()) {
quantityStrings.put(entry.getKey(), (Map<PluralKeyword, CharSequence>) entry.getValue());
}
}

@Override public void setString(@NotNull final Locale locale, @NotNull final String s, @NotNull final CharSequence charSequence) {
strings.put(s, charSequence);
}

@Override public void setStringArray(@NotNull final Locale locale, @NotNull final String s, @NotNull final CharSequence[] charSequences) {
stringArrays.put(s, charSequences);
}

@Override public void setStringArrays(@NotNull final Locale locale, @NotNull final Map<String, CharSequence[]> map) {
for (final Entry<String, CharSequence[]> entry : map.entrySet()) {
stringArrays.put(entry.getKey(), entry.getValue());
}
}

@Override public void setStrings(@NotNull final Locale locale, @NotNull final Map<String, ? extends CharSequence> map) {
for (final Entry<String, ? extends CharSequence> entry : map.entrySet()) {
strings.put(entry.getKey(), entry.getValue());
}
}
}

+ 59
- 40
ui/src/main/java/com/getbubblenow/android/util/UserStore.java View File

@@ -3,9 +3,9 @@ package com.getbubblenow.android.util;
import android.content.Context;
import android.content.SharedPreferences;

public class UserStore {
public final class UserStore {
private static UserStore instance;
private SharedPreferences sharedPreferences;
private final SharedPreferences sharedPreferences;

public static final String USER_SHARED_PREF = "com.wireguard.android.util.bubbleUserSharedPref";
private static final String USER_DATA_KEY = "com.wireguard.android.util.bubbleUserResponse";
@@ -20,21 +20,25 @@ public class UserStore {
private static final String SAGE_KEY = "com.wireguard.android.util.bubbleSageResponse";
private static final String HOSTNAME_FOR_ACCOUNT_KEY = "com.wireguard.android.util.bubbleHostNameForAccountResponse";
private static final String MFA_TOKEN_KEY = "MFA_TOKEN_KEY";
private static final String LOCALIZATION_PRE_KEY = "LOCALIZATION_PRE_KEY";
private static final String LOCALIZATION_POST_KEY = "LOCALIZATION_POST_KEY";

public static final String USER_TOKEN_DEFAULT_VALUE = "";
public static final String USER_TOKEN_DEFAULT_VALUE = "";
public static final String DEVICE_DEFAULT_VALUE = "";
public static final String DEVICE_ID_DEFAULT_VALUE = "";
private static final String DEVICE_ID_DEFAULT_VALUE = "";
private static final String HOSTNAME_DEFAULT_VALUE = "";
public static final String USERNAME_DEFAULT_VALUE = "";
private static final String USERNAME_DEFAULT_VALUE = "";
private static final String PASSWORD_DEFAULT_VALUE = "";
private static final String SAGE_TOKEN_DEFAULT_VALUE = "";
public static final String SAGE_TOKEN_DEFAULT_VALUE = "";
private static final String CONFIG_DEFAULT_VALUE = "";
private static final String SAGE_HOSTNAME_DEFAULT_VALUE = "";
private static final String SAGE_DEFAULT_VALUE = "";
private static final String HOSTNAME_FOR_ACCOUNT_DEFAULT_VALUE = "";
private static final String MFA_TOKEN_DEFAULT_VALUE = "";
private static final String LOCALIZATION_PRE_DEFAULT_VALUE = "";
private static final String LOCALIZATION_POST_DEFAULT_VALUE = "";

public static UserStore getInstance(Context context) {
public static UserStore getInstance(final Context context) {
if (instance == null) {
synchronized (UserStore.class) {
if (instance == null) {
@@ -42,15 +46,14 @@ public class UserStore {
}
}
}

return instance;
}

private UserStore(Context context) {
private UserStore(final Context context) {
sharedPreferences = context.getSharedPreferences(USER_SHARED_PREF, Context.MODE_PRIVATE);
}

public void setToken(String response) {
public void setToken(final String response) {
sharedPreferences.edit().putString(USER_DATA_KEY, response).apply();
}

@@ -58,25 +61,25 @@ public class UserStore {
return sharedPreferences.getString(USER_DATA_KEY, USER_TOKEN_DEFAULT_VALUE);
}

public void setDevice(String deviceName, String deviceID){
public void setDevice(final String deviceName, final String deviceID) {
sharedPreferences.edit().putString(DEVICE_DATA_KEY, deviceName).apply();
sharedPreferences.edit().putString(DEVICE_ID_KEY,deviceID).apply();
sharedPreferences.edit().putString(DEVICE_ID_KEY, deviceID).apply();
}

public String getDeviceName(){
public String getDeviceName() {
return sharedPreferences.getString(DEVICE_DATA_KEY, DEVICE_DEFAULT_VALUE);
}

public String getDeviceID(){
return sharedPreferences.getString(DEVICE_ID_KEY,DEVICE_ID_DEFAULT_VALUE);
public String getDeviceID() {
return sharedPreferences.getString(DEVICE_ID_KEY, DEVICE_ID_DEFAULT_VALUE);
}

public void setHostname(String hostName){
sharedPreferences.edit().putString(HOSTNAME_KEY,hostName).apply();
public void setHostname(final String hostName) {
sharedPreferences.edit().putString(HOSTNAME_KEY, hostName).apply();
}

public String getHostname(){
return sharedPreferences.getString(HOSTNAME_KEY,HOSTNAME_DEFAULT_VALUE);
public String getHostname() {
return sharedPreferences.getString(HOSTNAME_KEY, HOSTNAME_DEFAULT_VALUE);
}

public void setMfaToken(final String mfaToken) {
@@ -87,56 +90,72 @@ public class UserStore {
return sharedPreferences.getString(MFA_TOKEN_KEY, MFA_TOKEN_DEFAULT_VALUE);
}

public void setUserData(String username , String password){
public void setUserData(final String username, final String password) {
sharedPreferences.edit().putString(USERNAME_KEY, username).apply();
sharedPreferences.edit().putString(PASSWORD_KEY,password).apply();
sharedPreferences.edit().putString(PASSWORD_KEY, password).apply();
}

public String getUsername(){
return sharedPreferences.getString(USERNAME_KEY,USERNAME_DEFAULT_VALUE);
public String getUsername() {
return sharedPreferences.getString(USERNAME_KEY, USERNAME_DEFAULT_VALUE);
}

public String getPassword(){
return sharedPreferences.getString(PASSWORD_KEY,PASSWORD_DEFAULT_VALUE);
public String getPassword() {
return sharedPreferences.getString(PASSWORD_KEY, PASSWORD_DEFAULT_VALUE);
}

public void setSageURL(String sage){
public void setSageURL(final String sage) {
sharedPreferences.edit().putString(SAGE_KEY, sage).apply();
}

public String getSageURL(){
return sharedPreferences.getString(SAGE_KEY,SAGE_DEFAULT_VALUE);
public String getSageURL() {
return sharedPreferences.getString(SAGE_KEY, SAGE_DEFAULT_VALUE);
}

public void setConfig(String config){
public void setConfig(final String config) {
sharedPreferences.edit().putString(CONFIG_KEY, config).apply();
}

public String getConfig(){
return sharedPreferences.getString(CONFIG_KEY,CONFIG_DEFAULT_VALUE);
public String getConfig() {
return sharedPreferences.getString(CONFIG_KEY, CONFIG_DEFAULT_VALUE);
}

public void setSageHostname(String sageHostname){
public void setSageHostname(final String sageHostname) {
sharedPreferences.edit().putString(SAGE_HOSTNAME_KEY, sageHostname).apply();
}

public String getSageHostname(){
return sharedPreferences.getString(SAGE_HOSTNAME_KEY,SAGE_HOSTNAME_DEFAULT_VALUE);
public String getSageHostname() {
return sharedPreferences.getString(SAGE_HOSTNAME_KEY, SAGE_HOSTNAME_DEFAULT_VALUE);
}

public void setSageToken(String sageToken){
public void setSageToken(final String sageToken) {
sharedPreferences.edit().putString(SAGE_TOKEN_KEY, sageToken).apply();
}

public String getSageToken(){
return sharedPreferences.getString(SAGE_TOKEN_KEY,SAGE_TOKEN_DEFAULT_VALUE);
public String getSageToken() {
return sharedPreferences.getString(SAGE_TOKEN_KEY, SAGE_TOKEN_DEFAULT_VALUE);
}

public void setHostNameForAccount(String hostNameForAccount){
public void setHostNameForAccount(final String hostNameForAccount) {
sharedPreferences.edit().putString(HOSTNAME_FOR_ACCOUNT_KEY, hostNameForAccount).apply();
}

public String getHostNameForAccount(){
return sharedPreferences.getString(HOSTNAME_FOR_ACCOUNT_KEY,HOSTNAME_FOR_ACCOUNT_DEFAULT_VALUE);
public String getHostNameForAccount() {
return sharedPreferences.getString(HOSTNAME_FOR_ACCOUNT_KEY, HOSTNAME_FOR_ACCOUNT_DEFAULT_VALUE);
}

public void setLocalizationPre(final String data) {
sharedPreferences.edit().putString(LOCALIZATION_PRE_KEY, data).apply();
}

public String getLocalizationPre() {
return sharedPreferences.getString(LOCALIZATION_PRE_KEY, LOCALIZATION_PRE_DEFAULT_VALUE);
}

public void setLocalizationPost(final String data) {
sharedPreferences.edit().putString(LOCALIZATION_POST_KEY, data).apply();
}

public String getLocalizationPost() {
return sharedPreferences.getString(LOCALIZATION_POST_KEY, LOCALIZATION_POST_DEFAULT_VALUE);
}
}

+ 2
- 2
ui/src/main/java/com/getbubblenow/android/viewmodel/MFAVerifyViewModel.java View File

@@ -8,7 +8,7 @@ import com.getbubblenow.android.resource.StatusResource;

import androidx.lifecycle.MutableLiveData;

public class MFAVerifyViewModel extends BaseViewModel {
public class MFAVerifyViewModel extends BaseViewModel {

private MFAuthType authType = MFAuthType.NONE;

@@ -25,4 +25,4 @@ public class MFAVerifyViewModel extends BaseViewModel {
public MFAuthType getAuthType() {
return authType;
}
}
}

+ 20
- 8
ui/src/main/java/com/getbubblenow/android/viewmodel/MainViewModel.java View File

@@ -5,14 +5,30 @@ import android.content.Context;
import com.getbubblenow.android.model.Network;
import com.getbubblenow.android.repository.DataRepository;
import com.getbubblenow.android.model.ObservableTunnel;
import com.getbubblenow.android.repository.RestringMessageRepository;
import com.getbubblenow.android.resource.StatusResource;
import com.getbubblenow.android.util.UserStore;

import java.util.Locale;

import java.util.List;

import androidx.lifecycle.MutableLiveData;

public class MainViewModel extends BaseViewModel {

public boolean isUserLoggedInToSage(final Context context) {
return !UserStore.SAGE_TOKEN_DEFAULT_VALUE.equals(UserStore.getInstance(context).getSageToken());
}

public MutableLiveData<Boolean> updatePreAuthLocalization(final Context context, final Locale locale) {
return RestringMessageRepository.getInstance().updatePreAuthLocalization(context, locale);
}

public MutableLiveData<Boolean> updatePostAuthLocalization(final Context context, final Locale locale) {
return RestringMessageRepository.getInstance().updatePostAuthLocalization(context, locale);
}

public boolean isUserLoggedIn(Context context){
return !UserStore.USER_TOKEN_DEFAULT_VALUE.equals(UserStore.getInstance(context).getToken());
}
@@ -37,10 +53,6 @@ public class MainViewModel extends BaseViewModel {
return DataRepository.getRepositoryInstance().getHostname(context);
}

public void logout(final Context context) {
DataRepository.getRepositoryInstance().logout(context);
}

public void removeSharedPreferences(Context context){
DataRepository.getRepositoryInstance().removeSharedPreferences(context);
}
@@ -81,12 +93,12 @@ public class MainViewModel extends BaseViewModel {
UserStore.getInstance(context).setConfig(config);
}

public void checkBubble(final Context context) {
DataRepository.getRepositoryInstance().checkBubble(context);
public MutableLiveData<StatusResource<List<Network>>> checkBubbleOnce(final Context context) {
return DataRepository.getRepositoryInstance().checkBubbleOnce(context);
}

public MutableLiveData<List<Network>> getBubbleCheckLiveData(final Context context) {
return DataRepository.getRepositoryInstance().getBubbleCheckLiveData();
public MutableLiveData<StatusResource<List<Network>>> checkBubbleContinuously(final Context context) {
return DataRepository.getRepositoryInstance().checkBubbleContinuously(context);
}

public MutableLiveData<StatusResource<byte[]>> getCertificateData(final Context context, final String uuid) {


+ 16
- 13
ui/src/main/res/values/strings.xml View File

@@ -144,7 +144,7 @@
<string name="module_installer_initial">The experimental kernel module can improve performance</string>
<string name="module_installer_not_found">No modules are available for your device</string>
<string name="module_installer_title">Download and install kernel module</string>
<string name="module_installer_working">Downloading and installing</string>
<string name="module_installer_working">Downloading and installing...</string>
<string name="module_version_error">Unable to determine kernel module version</string>
<string name="mtu">MTU</string>
<string name="multiple_tunnels_summary_off">Turning on one tunnel will turn off others</string>
@@ -178,7 +178,7 @@
<string name="shell_exit_status_read_error">Shell cannot read exit status</string>
<string name="shell_marker_count_error">Shell expected 4 markers, received %d</string>
<string name="shell_start_error">Shell failed to start: %d</string>
<string name="success_application_will_restart">Success. The application will now restart</string>
<string name="success_application_will_restart">Success. The application will now restart...</string>
<string name="toggle_all">Toggle All</string>
<string name="toggle_error">Error toggling WireGuard tunnel: %s</string>
<string name="tools_installer_already">wg and wg-quick are already installed</string>
@@ -226,44 +226,46 @@
<string name="biometric_prompt_private_key_title">Authenticate to view private key</string>
<string name="biometric_auth_error">Authentication failure</string>
<string name="biometric_auth_error_reason">Authentication failure: %s</string>

<!--Bubble constants-->
<string name="bubble_name">Bubble Name:</string>
<string name="user_name">User Name:</string>
<string name="password">Password:</string>
<string name="sign_in">Sign In</string>
<string name="don_t_have_a_bubble">Don\'t have a Bubble?</string>
<string name="start_new_bubble">Start New Bubble</string>
<string name="this_device_status">This Device Status:</string>
<string name="disable_apps">Disable Apps</string>
<string name="turnInternet">Please turn on internet connection</string>
<string name="progress_bar_text">Loading</string>
<string name="hostname_not_valid">Hostname not valid</string>
<string name="you_can_find_your_bubble_name_in_your_email">You can find your Bubble name in your email</string>
<string name="enter_your_bubble_name">Enter your Bubble Name</string>

<string name="password">Password:</string>
<string name="sign_in">Sign In</string>
<string name="my_bubble">Bubble Connection</string>
<string name="bubble_status">MY BUBBLE</string>
<string name="running">Running</string>
<string name="starting">Starting</string>
<string name="restoring">Restoring</string>
<string name="this_device_status">This Device Status:</string>
<string name="not_connected">Not Connected</string>
<string name="connect">CONNECT</string>
<string name="disable_apps">Disable Apps</string>
<string name="turnInternet">Please turn on internet connection</string>
<string name="progress_bar_text">Loading</string>
<string name="not_connected_bubble">Not Connected ...</string>
<string name="connected_bubble">Connected!</string>
<string name="failed_bubble">Failed</string>
<string name="disconnect">DISCONNECT</string>
<string name="hostname_not_valid">Hostname not valid</string>
<string name="login_failed">Login Failed</string>
<string name="cerificate_install">"Please install a certificate"</string>
<string name="certificate_install">Please install a certificate</string>
<string name="success">Success</string>
<string name="your_password">Your password</string>
<string name="email_you_used_to_sign_up">Email you used to Sign Up</string>
<string name="email">Email:</string>
<string name="you_can_find_your_bubble_name_in_your_email">You can find your Bubble name in your email</string>
<string name="enter_your_bubble_name">Enter your Bubble Name</string>
<string name="your_private_bubble">You are safe in your Bubble!</string>
<string name="network_not_found">Sorry, friends. Network not currently available.</string>
<string name="error_title">Sadface. We have \n encountered \n an error.</string>
<string name="contact_support">CONTACT SUPPORT</string>
<string name="log_out">Log out</string>
<string name="bubble_app_name">Bubble</string>
<string name="empty_message">"Email and password should not be empty"</string>
<string name="empty_message">Email and password should not be empty</string>
<string name="building_bubble_title">We Are Building</string>
<string name="private_bubble">Your Private Bubble!</string>
<string name="no_account_yet">No account yet? <u>Create one.</u></string>
@@ -275,4 +277,5 @@
<string name="enter_verification_code">Enter Verification 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="certificate_name">Bubble Certificate</string>
</resources>

Loading…
Cancel
Save