@@ -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) { | |||
@@ -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() | |||
) | |||
} | |||
} |
@@ -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(); | |||
} | |||
@@ -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); | |||
} | |||
@@ -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); | |||
} | |||
@@ -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}"; | |||
} |
@@ -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 | |||
} |
@@ -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); | |||
} |
@@ -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)); | |||
} | |||
} | |||
} | |||
@@ -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."); | |||
} | |||
} | |||
} |
@@ -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(); | |||
} | |||
} |
@@ -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()); | |||
} | |||
} | |||
} |
@@ -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); | |||
} | |||
} |
@@ -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; | |||
} | |||
} | |||
} |
@@ -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) { | |||
@@ -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> |